Django搭建個人部落格:自定義模板過濾器和標籤
現在我們已經很熟悉Django的MTV模式了。模板(template)負責如何去展示資料,而檢視(view)負責篩選出正確的資料。因此通常來說邏輯都是放到檢視中的,但模板也需要一些和表示相關的邏輯:比如迴圈展示(如{% for ... %}
)、或者以某種特定格式輸出(如{{ ...|date:'Y-m-d' }}
)等,這些功能都是靠模板的**過濾器(filters)和標籤(tags)**實現的。
Django的模板語言包含了很多內建的過濾器和標籤,設計目的是滿足應用需要佔位邏輯需求。但有的時候這些通用的功能滿足不了你的某些需求,這時候就需要自定義過濾器和標籤來實現了。
前置條件
要在Django中使用模板過濾器或標籤,就首先得註冊
註冊方法如下:
- 在APP中新建名為
templatetags
的目錄(方便起見,教程選擇了article
這個APP) - 在此目錄中新建名為
__init__.py
的空檔案,使得此目錄被視作一個Python的包 - 在此目錄中新建python檔案(比如
my_filters_and_tags.py
),就可以在裡面愉快的寫程式碼啦
完成後的目錄結構如下:
article/
__init__.py
views.py
models.py
# 新增目錄
templatetags/
__init__.py # 空檔案
my_filters_and_tags.py # 即將寫程式碼的地方
...
複製程式碼
請注意:
- 目錄必須位於已註冊的APP中,這是出於安全性的考慮
- 新建目錄後,必須手動重啟伺服器,裡面的過濾器和標籤才能生效
前置條件就完成了,接下來我們看看如何寫一個模板過濾器。
模板過濾器
過濾器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官方檔案。
- 有疑問請在杜賽的個人網站留言,我會盡快回復。
- 或Email私信我:[email protected]
- 專案完整程式碼:Django_blog_tutorial