Django搭建我的博客:自定義模板過濾器和標籤

如今咱們已經很熟悉Django的MTV模式了。模板(template)負責如何去展現數據,而視圖(view)負責篩選出正確的數據。所以一般來講邏輯都是放到視圖中的,但模板也須要一些和表示相關的邏輯:好比循環展現(如{% for ... %})、或者以某種特定格式輸出(如{{ ...|date:'Y-m-d' }})等,這些功能都是靠模板的**過濾器(filters)標籤(tags)**實現的。html

Django的模板語言包含了不少內置的過濾器和標籤,設計目的是知足應用須要佔位邏輯需求。但有的時候這些通用的功能知足不了你的某些需求,這時候就須要自定義過濾器和標籤來實現了。python

前置條件

要在Django中使用模板過濾器或標籤,就首先得註冊它們。git

註冊方法以下:github

  • 在APP中新建名爲templatetags的目錄(方便起見,教程選擇了article這個APP)
  • 在此目錄中新建名爲__init__.py的空文件,使得此目錄被視做一個Python的包
  • 在此目錄中新建python文件(好比my_filters_and_tags.py),就能夠在裏面愉快的寫代碼啦

完成後的目錄結構以下:django

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官方文檔


相關文章
相關標籤/搜索