Django 模板 === [toc] 模板按照我的理解,就是讓html中內容不固定,讓html內容已後端的方式動態起來(雖然前端mvvm框架也也開始有模板概念,所以廣義說模板概念不限於後端)。但是html基礎的內容還是是固定的。模板通過類編程的模板語法,可以將html模板中的動態內容,通過後端程式 ...
目錄
Django 模板
模板按照我的理解,就是讓html中內容不固定,讓html內容已後端的方式動態起來(雖然前端mvvm框架也也開始有模板概念,所以廣義說模板概念不限於後端)。但是html基礎的內容還是是固定的。模板通過類編程的模板語法,可以將html模板中的動態內容,通過後端程式的計算傳入核心數據,最後通過模板語法得到一個完整的html。模板的構造核心就是:模板語法和上下文數據(渲染引擎的全局數據和後端代碼傳入的數據);模板的驅動就是模板引擎(如Jinja2,django內置的DTL)。模板語法的數據來自於上下文數據,使得模板可以動態的生成html內容,關鍵讓類似內容的構造更加高效,如for迴圈渲染列表。模板語法還提供模板與模板間存在關係:繼承關係和包含關係。模板間的關係時的開髮網頁減少大量的冗餘內容。
後端使用模板,多用於開發訪問量較小的後臺管理系統。
模板語法
模版文件中使用的variables會被上下文字典中的對應的key的值所替代。
模版文件中使用的tags會被引擎執行一段相應的邏輯。
邏輯語法
- {% if condition %} {% else %} 條件
- {% for %} {% endfor%} 迭代
函數式過濾器
- 說明: filter是用於轉變 variable 和 tag's arguments 的
- 例如: {{ django|title }} 其上下文 {'django': 'the web framework'}
- 渲染後:'The Web Framework'
- filter參數:有些filter是可以傳入參數的,參數傳入方式是filter:argument;
- 例如: {{ datetime|date:"Y-m-d" }}
內置filter
內置filter: 參見官檔索引
功能tag
- 語法: {% tag %}
- tag用法: 不同tag有自己的處理邏輯,包括用戶自定義的tag,並且tag可以接受參數,有些tag有開始和結束tag對。
- {% csrf_token %} 無參數
- {% cycle 'odd' 'even' %} 有參數
- {% if user.is_authenticated %} Hello, {{ user.username }}. {% endif %} 有開始和結束tag對的。
- 說明:tag有內置,也可自定義。
註釋
- 語法:{# this won't be rendered #}
- 作用:就是註釋,不會被渲染。
- 多行註釋利用tag: {% comment %} 多行註釋內容 {% endcomment %}
- 說明: 為什麼不用html的註釋,因為模版語言不止用於html文本,其它文本也可以,所以在模版語言這一層來進行註釋才行。註意,註釋不能嵌套。
內置tag
內置tag:參見官檔索引
導入三方tag and filter(load)
{% load static %} 載入static app中templatetags目錄下的static文件中定義的tag。
load同時也會導入tag和filter
過濾器和功能tag的區別
Tags的功能比filter要複雜的多,因為tag幾乎可以做任何事情,包括最重要的渲染模版inclusion_tag。而filter主要是對數據進行處理。
自定義tag和filter
- 前提:最常見是自定義的tag是在對應的app目錄下的創建的,所以要將app註冊到settings的installed_apps列表中。
- 在app目錄下創建templatetags目錄,名字必須固定,這個是預設查找載入指定自定義tag和filter文件的地方,併在目錄創建__init__.py文件。(是django預設的finder查找邏輯限制了這個名字)
- 創建py文件,自定義tag就放在文件中。文件名一定要小心,不要和其它app註冊的衝突了。自定義filter和tag,如:
# my_tags.py
from django import template
register = template.Library() # register變數名固定
@register.filter # 自定義filter
def multi_tag(v1, v2):
return v1 * v2
@register.simple_tag # 自定義simple tag
def tag_add(v1, v2):
return v1 + v2
- 自定義好自己的tags和filters,重要的一步就是將他們擴展使用到template引擎中,讓他們生效就要使用{% load %} tag.
- 在模板文件中添加load標簽後,需要重啟服務。這點很關鍵啊!還有load後面的自定義tag保存的文件,不用引號,直接load後面跟文件名就行了
- 在模版中使用自定義tag和filter。模版間繼承關係,是不會繼承{% load %} 標簽的,所以每個模版中要使用自定義的tag和filter都需要再次{% load %}一次。
上下文數據
- 語法: {{ variable }}
- 用法: My first name is {{ first_name }}. My last name is {{ last_name }}.
- 非全局變數,需要提供的上下文字典(或者叫環境或者叫作用域):{'first_name': 'John', 'last_name': 'Doe'}
- 通過render後產生的結果:My first name is John. My last name is 'Doe'.
如果變數指向的也是一個字典:那麼使用dotted可以訪問了字典中的值:{{ my_dict.key }} {{ my_object.attribute }} {{ my_list.0 }}
全局數據
內置:官檔
如request對象自動傳入
傳入數據
render('inde.html', {已字典結構傳入})
模板間關係
繼承關係
通過{% extends '父模板.hmtl' %}'
包含關係
通過{% include '插入的模板.html' %} , 說明插入的模板可以使用數據
inclusion_tag關係
inclusion_tag 通過自定義tag形式,tag綁定了一個模板,tag函數處理邏輯放回一個上下文字典供綁定的模板渲染。這種也相當於是模板的包含關係,只不過是通過一個自定義的inclusion_tag進行封裝。且讓插座與插頭對接更為明確,就是提供給tag提供參數,參數就是對接的規範了。由於是tag的形式,tag函數代碼可以訪問後臺的所有數據。
inclusion_tag 示例詳解
下麵代碼將一個模版通過使用自定義的類型為inclusion tag即進行渲染使用:
這個自定義tag是怎麼做到的呢?
首先,我們利用這個tag的場景是:多個url頁面都要用到相同的頁面佈局內容。如:博客系統中的個人站點的用戶文章列表,標簽列表,公告;這些對於這個用戶的站點內容都是一樣的。這些共同的東西要怎麼才能重覆利用呢?對於學習了template繼承知識的同學,可能想到的是使用繼承關係既可以了。繼承是沒錯,但是相同部分的內容,要提供給模版語言的數據還是要給予的,不同的是這些數據在各自的視圖view函數中,要去重覆的獲取數據,這些重覆的獲取數據的代碼,在這些視圖之間都是一樣的。雖然模版得到了繼承,但是模版要用到的數據還是造成了重覆代碼。要解決這個問題方式一:可以將獲取數據的代碼,封裝到一個函數代碼塊中,這樣能解決重覆問題。這是利用基於模版的繼承為主要思想的思路,因為模版必須繼承才行。
上面提到的模版繼承思路,有一個局限性就是耦合度太高,必須繼承模版。有沒有什麼方法不用繼承模版就可以實現相同頁面塊的即插即用(繼承方式無法即插即用)。django的一個自定義tag類型,給我們提供了一種即插即用的思路,這種思路是基於模版語言的tag對應一個python函數邏輯的思想。只需要自定義一個tag,tag就可以在任何的模版中插入使用。tag要做的就是返回一個渲染了的在前面提到的重覆頁面就行了。(這個就類似與include內置tag功能的一樣,不同的是,include的頁面是死的頁面;而這裡自定義的tag是可以利用模版語言結合上下文數據,動態的渲染出這個即插即用的頁面)。這個指定tag就是django的inclusion tags。
inlucsion tags 的使用需要準備兩個東西:一個即插即用的template,另一個就是渲染模版的上下文context data數據了。這個數據就是我們第一種思想中提到的要通過一個封裝函數獲取的數據。其實參數就是通過被插入的主模版能夠給我們提供的數據了。即插即用,解耦的思想很棒!下麵過一個列子來實踐這個即插即用的思想。
友情提醒一句:利用這個思想,首先是目的和使用場景,前端頁面要重覆使用有且並且要重覆的頁面的上下文數據獲取較多比較麻煩且重覆
開始示例:
- 模版stuff_list.html:
{# 公告 #}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">公告</h3>
</div>
<div class="panel-body">
<ul class="panel-list" type="none">
<li><span>用戶別名: {{ user_obj.username }}</span></li>
<li><span>園齡: {{ age_days }} 天</span></li>
<li><span>隨筆數: {{ user_obj.article_set.count }} 篇</span></li>
</ul>
</div>
</div>
{#隨筆分類#}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">隨筆分類</h3>
</div>
<div class="list-group">
{% for item in article_category_queryset %}
<a href="" class="list-group-item">{{ item.category__title }} ({{ item.count }})</a>
{% endfor %}
</div>
</div>
{#標簽#}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">我的標簽</h3>
</div>
<div class="list-group">
{% for item in article_tag_queryset %}
<a href="" class="list-group-item">{{ item.title }} ({{ item.count }})</a>
{% endfor %}
</div>
</div>
{#歸檔#}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">隨筆歸檔</h3>
</div>
<div class="list-group">
{% for item in article_archive_queryset %}
<a href="" class="list-group-item">{{ item.y_m }} ({{ item.count }})</a>
{% endfor %}
</div>
</div>
- 自定義的tag文件my_tags_filters.py:
重覆獲取的上下問數據就在該文件中函數中實現,返回一個上下文數據字典。在利用渲染裝飾器來裝飾這個函數,裝飾器函數要帶入要渲染的模版文件。相當於就是裝飾器給我們做渲染模版作用,我們的自定義函數來獲取查詢數據,返回渲染需要的上下文數據就行。
from django import template
from myblog import models
from django.utils import timezone
from django.db.models import Count
register = template.Library()
@register.inclusion_tag('myblog/stuff_list.html') # 綁定我們的模板了
def stuff_list(user_obj): # 參數需要一個,這裡就是用戶對象
# 計算園齡 以天為單位
curr_time = timezone.now()
create_time = user_obj.create_time
interval_timedelta = curr_time - create_time
age_days = interval_timedelta.days
user_articles = models.Article.objects.filter(user=user_obj)
# 分類
article_category_queryset = user_articles.values('category__pk').annotate(
count=Count('pk')).values('category__title', 'count')
# 標簽
# article_tag_queryset = user_articles.values('tag__pk').annotate(count=Count('pk')).values('tag__title', 'count') 這個計算出來是一個{'None': 6} 把None做了一個分類,下麵分組方式就是一個[]的queryset。兩種看來還是不一樣。
article_tag_queryset = models.Tag.objects.filter(blog=user_obj.blog).values('pk').annotate(
count=Count('article__pk')).values('title', 'count')
print(article_tag_queryset) ###
article_archive_queryset = user_articles.extra(select={'y_m': "date_format(create_time, '%%Y-%%m')"}).values(
'y_m').annotate(count=Count('pk')).values('y_m', 'count')
print(article_archive_queryset) ###
# 這就是即插即用模版的上下文數據;通過一個user_obj參數我們就得到了要渲染到綁定模板中的上下文數據了。
return {'user_obj': user_obj,
'age_days': age_days,
'article_category_queryset': article_category_queryset,
'article_tag_queryset': article_tag_queryset,
'article_archive_queryset': article_archive_queryset,}
- 使用我們的inclusion_tag了,將my_tags_filters.py 放入到app目錄下的templatetags目錄下,然後再要使用的模板中進行{% load my_tags_filters %} 就可以了。然後使用tag {% stuff_list site_user %} 註意要傳遞我們的參數。
小結
- 從模板關係可以發散出:其實繼承是非常好的內容重用減少冗餘的設計,同時包含關係,也可以說是可插拔模式,插入即用,還可復用。包含關係多是用在畫面構建方面,如設計圖,html模板,畫作等。
- 什麼時候繼承,什麼時候包含:繼承是框架,包含是插拔插件,包含是對繼承的補充。繼承局限更強,包含可跨繼承。比如說,一個父模板,所有的子模版都是在繼承父類的基礎上修改覆蓋。而包含可以在多個父模板中使用,突破了必須在一個父模板下的限制,但是包含不是隨便插入,是類似插座和插頭的概念,被插入模板需要提供給插入模板相關數據的。。
- 金字塔結構:通過模板引擎解析模板語法,根據傳入上下文環境渲染出動態內容。模板語法。傳入數據。
tag和filter。模板關係。全局參數和傳入參數。
內置tag_filter和自定義tag_filter,tag功能強於filter。繼承,include,inclusion_tag。request/auth。