您好,登錄后才能下訂單哦!
現在我們已經很熟悉Django的MTV模式了。模板(template)負責如何去展示數據,而視圖(view)負責篩選出正確的數據。因此通常來說邏輯都是放到視圖中的,但模板也需要一些 和表示相關的邏輯 :比如循環展示(如 {% for ... %}
)、或者以某種特定格式輸出(如 {{ ...|date:'Y-m-d' }}
)等,這些功能都是靠模板的 過濾器(filters) 和 標簽(tags) 實現的。
Django的模板語言包含了很多內置的過濾器和標簽,設計目的是滿足應用需要占位邏輯需求。但有的時候這些通用的功能滿足不了你的某些需求,這時候就需要自定義過濾器和標簽來實現了。
前置條件
要在Django中使用模板過濾器或標簽,就首先得 注冊 它們。
注冊方法如下:
完成后的目錄結構如下:
article/ __init__.py views.py models.py # 新增目錄 templatetags/ __init__.py # 空文件 my_filters_and_tags.py # 即將寫代碼的地方 ...
請注意:
前置條件就完成了,接下來我們看看如何寫一個模板過濾器。
模板過濾器
過濾器 filter 的表現形式為緊跟在上下文后面的管道符 | ,管道符后面是filter的名稱: {{ ...|filter_name }} 。有的filter還可以帶有參數: {{ ...|filter_name:var }}
。
注意過濾器名稱的冒號后面不能有空格。
filter 這個名字可能會讓你誤認為它只是用來篩選某些特定數據的,但實際上它遠不止這點功能。它可以改變上下文的最終展示效果,也可以將上下文通過運算輸出為特定的值。
小試牛刀
要成為一個可用的 filter ,文件中必須包含一個名為 register
的模塊級變量,它是一個 template.Library
實例,所有的 filters 均在其中注冊。所以在 my_filter_and_tags.py
文件中輸入以下內容:
article/templatetags/my_filter_and_tags.py from django import template register = template.Library()
接下來就可以像寫普通的Python函數一樣寫過濾器了:
article/templatetags/my_filter_and_tags.py from django import template register = template.Library() @register.filter(name='transfer') def transfer(value, arg): """將輸出強制轉換為字符串 arg """ return arg @register.filter() def lower(value): """將字符串轉換為小寫字符""" return value.lower()
filter可以通過裝飾器進行注冊。若注冊裝飾器中攜帶了 name 參數,則其值為此filter的名稱;若未攜帶,則函數名就是filter的名稱。
filter必須是有一到兩個參數的Python函數。第一個參數是上下文本身,第二個參數則由filter提供。舉個栗子,在過濾器 {{ var|foo:"bar" }}
中,變量 var 為第一個參數,變量 bar 則作為第二個參數。
調用這些filter的方法是在模板文件中用 {% load ... %}
將filter文件的名稱加載進去,像這樣:
# 任意模板文件中 {% load my_filters_and_tags %} {{ 'ABC'|transfer:'cool' }} # 輸出:'cool' {{ 'ABC'|lower }} # 輸出: 'abc'
更人性化的時間
了解完filter的使用方法后,下面來寫點更實用的功能。
對人類這種生物來說, 相對時間 通常比 絕對時間 要更容易閱讀。 發表于 3天前 可以輕易得知此文章剛發表不久;而 發表于 2019年8月10日 你還得想想今天到底幾號來著。
因此寫一個顯示相對日期的 time_since_zh 過濾器:
article/templatetags/my_filter_and_tags.py ... from django.utils import timezone import math # 獲取相對時間 @register.filter(name='timesince_zh') def time_since_zh(value): now = timezone.now() diff = now - value if diff.days == 0 and diff.seconds >= 0 and diff.seconds < 60: return '剛剛' if diff.days == 0 and diff.seconds >= 60 and diff.seconds < 3600: return str(math.floor(diff.seconds / 60)) + "分鐘前" if diff.days == 0 and diff.seconds >= 3600 and diff.seconds < 86400: return str(math.floor(diff.seconds / 3600)) + "小時前" if diff.days >= 1 and diff.days < 30: return str(diff.days) + "天前" if diff.days >= 30 and diff.days < 365: return str(math.floor(diff.days / 30)) + "個月前" if diff.days >= 365: return str(math.floor(diff.days / 365)) + "年前"
代碼功能很簡單,就是將文章發布時間和當前時間作比較,然后返回適當的字符串。
修改文章列表模板文件中與發布時間相關的代碼,把剛寫的filter用上:
templates/article/list.html ... {% load my_filters_and_tags %} ... <!-- 舊代碼 {{ article.created|date:'Y-m-d' }} --> <!-- 新代碼 --> {{ article.created|timesince_zh }} ...
效果如下:
實際上Django內置了一個 timesince 過濾器,只不過顯示日期是英文的,不夠友好。
模板標簽
模板標簽(tag)比過濾器更復雜,功能也更強大。
標簽 tag 的表現形式為 {% tag_name ... %} ,比如我們非常熟悉的內置標簽 {% url ... %} 、 {% static ... %} 等。如果內置標簽滿足不了你的需求,Django 提供了很多快捷方式,簡化了編寫絕大多數類型的標簽過程。
簡單標簽
simple_tag 就是最重要的標簽類型。標簽的注冊方法跟過濾器非常類似:
@register.simple_tag def change_http_to_https(url): new_url = url.replace('http://', 'https://') return new_url
調用時同樣記得在模板文件中用 {% load... %}
引入。用法你應該猜得到: {% change_http_to_https ... %}
,這個標簽的作用是將http鏈接替換為https鏈接。
用 Django-allauth 進行微博登錄,默認返回的用戶頭像是 http 鏈接(雖然微博有 https 版本的頭像)。如果你的站點已經升級為 https 了,又不想花時間去研究微博的接口,那么這個標簽就可以派上用場了。
順帶一說, Django-allauth 第三方登錄的頭像 url 保存在 User.socialaccount_set.all.0.get_avatar_url
中。
下面這個例子可以返回指定格式的時間字符串:
import datetime @register.simple_tag def current_time(format_string): return datetime.datetime.now().strftime(format_string)
調用時你可以將其保存為模板變量,以便你在期望的位置多次調用:
{% current_time "%Y-%m-%d %I:%M %p" as the_time %} <p>The time is {{ the_time }}.</p> <p>Again, the time is {{ the_time }}.</p>
模板標簽也可以訪問當前的上下文,只需要在注冊標簽時傳入 takes_context
參數:
@register.simple_tag(takes_context=True) def current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string)
注意,第一個參數必須是 context 。
與過濾器不同的是,標簽可以接受任意數量的位置或關鍵字參數。例如:
@register.simple_tag def my_tag(a, b, *args, **kwargs): warning = kwargs['warning'] profile = kwargs['profile'] ... return ...
在模板中調用時,任意數量的、以空格分隔的參數會被傳遞給模板標簽。與 Python 中類似,關鍵字參數的賦值使用等號(" = "),且必須在位置參數后提供:
{% my_tag 123 "abcd" book.title warning=message profile=user.profile %}
包含標簽
包含標簽可以讓另一個模板為當前模板渲染數據。聽起來比較拗口,還是通過例子來理解。
假設現在有一個需求,是要在文章詳情頁面中,顯示所有相關評論的發布時間。因此在 my_filter_and_tags.py
中寫入:
my_filter_and_tags.py ... @register.inclusion_tag('article/tag_list.html') def show_comments_pub_time(article): """顯示文章評論的發布時間""" comments = article.comments.all() return {'comments': comments}
函數傳入的參數可以是模板中的上下文變量。函數體內部取得了所有相關評論的查詢集,然后把結果 comments 返回。注意返回結果是進入到 tag_list.html
這個模板中去了,因此新建它并寫入:
templates/article/tag_list.html <ul> {% for comment in comments %} <li> {{ comment.created }} </li> {% endfor %} </ul>
然后在文章詳情頁面的模板中,隨便找一個位置寫入:
templates/article/detail.html ... {% load my_filters_and_tags %} ... {% show_comments_pub_time article %}
刷新詳情頁面,順利的話就能看到所有評論的發表時間都展示出來了。
包含標簽的另一個應用場景就是各種按鈕了。有的按鈕看上去長得都差不多,但是根據頁面不同會有不同的功能,這時候也可以用包含標簽來實現。
總之,包含標簽可以將常用的模板代碼打包成小組件,方便重復利用。
目前的博客項目中暫時還用不到包含標簽,所以放心的刪除上面的代碼吧。
總結
以上所述是小編給大家介紹的Django自定義模板過濾器和標簽的實現方法,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復大家的!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。