在之前用两个项目简单的先让Django项目跑起来之后,现在要开始进阶学习Django的每一个组成部分及内容.在编程自学到现在,也具有了初步阅读文档的能力,市面上的Django学习资源比较少(其他框架也差不多),说明想要用好框架,基本还是要靠自学.Django的1.11官方文档是非常好的教材,以后硬读英文文档也是必须的功力了.
MTV和MVC模型
Django设计网站的思路是采用类似MVC模型的MTV模型,其本质上是一样的,都是把数据,数据处理,展示三部分分开用不同的模块处理.这是一种设计理念和架构模式,具体到细节实现上,每个功能归属在MTV和MVC下有轻微的区别,不过不影响大局.
MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),具有耦合性低、重用性高、生命周期成本低等优点。阮一峰对此有篇文章介绍.
Django的MTV模型和四大部分
Django框架的设计模式借鉴了MVC框架的思想,也是分成三部分,来降低各个部分之间的耦合性。
Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和View(视图),也就是MTV框架。
Django的模式也遵循了MVC的理念,用户的操作用控制器(包括url分发器和函数(注意这里的views.py对应的其实是控制过程,是处理数据的业务逻辑,并不是MVC里的V))来接收,处理之后传送指令给模型用于获得数据和结果,再返回给视图部分展示给用户.从上图可以看出Django的四大模块与MVC模型的对应关系如下:
- C--urls分发器(路由系统,属于MVC里的C部分),Django内叫做路由系统
- C--视图系统(业务逻辑,属于MVC里的C部分),Django内叫做视图函数
- V--模板系统(对用户展示,相当于MVC里的V),Django内叫做templates
- M--模型(存取数据,在业务逻辑和数据库之间充当桥梁),Django内叫做models
Django进阶部分的学习,就是针对每个模块更深入的学习,以及还要学习其他技术和框架在Django内部的使用.
Django模板系统
Django的模板系统指的是,将HTML文件看做是一个模板,按照一定的规则构建模板,可以区分模板内静态和动态的内容.在需要给浏览器返回一个页面之前,Django按照业务逻辑,对模板进行处理操作,通过模板生成真正需要传送给浏览器的具体页面.对于可以复用的模板与每次从该模板生成并传送给浏览器的具体页面,二者的关系就像是类和对象的关系一样,每一个具体页面都是通过模板生成,带有各自不同的数据.
Django的模板系统具体实现是通过在模板中按照模板语言规则进行书写,并在渲染页面的时候向渲染函数传入对应数据实现的.其中的关键就是模板语言.
模板语言的官方文档地址在此.
模板语言符号
模板内的所有模板语言部分,都要被包裹在特定的模板语言符号内部.不处于模板语言符号内部的部分,Django会将其认为是普通HTML字符串,处于模板语言符号内部的才会进行处理.模板语言符号有两种:
第一种是{{ var }},用于单纯的标识符,表示变量.注意,由于变量是python传入的,所以变量的命名最好遵循和python一样的标识符规范.由于我们的后端是python,所以渲染函数传入的参数可以是任意的python对象,可以是数值,字符串,序列,对象,序列和对象的组合等.如果传入列表,可以用列表.0来获取第一个元素,以此类推.如果传入对象,则可以用对象.属性 方式来获取属性.如果调用方法,则不需要加括号.如果是传单个的东西,可以发现,在python内处理好再传入模板和在模板内处理,没有很大区别.
第二种是{% %},这个语句用于带有逻辑的语句,很多带有逻辑的语句,都有对应的结束逻辑的语句,在开始和结束之间的HTML部分,都算作被模板语言包裹.相比第一种单纯表示变量的模板语句,带有逻辑的语句在处理需要替换多个标签内容的时候更加方便,如果在python中处理,则很难控制标签的实际数量.注意,这里说的逻辑语句不仅指for in if 等判断,还包括一些特殊的带参数的指令.在带有逻辑的语句中,依然可以使用变量,使用的方法和第一种方法一样.
举几个例子说明模板处理变量的方法.在view.py内向一个页面传入列表,字典和对象来实验:
def template_test(request):
list_to_template = ["三鹅", "四筒", '伍德']
dict_to_template = {"1st": "三鹅",
"2nd": "四筒",
"3rd": "伍德"}
class Person:
def __init__(self,name,age):
self.name = name
self.age= age
def __str__(self):
return "".format(self.name,self.age)
def show_age(self):
return self.age
return render(request, 'test.html', {"list": list_to_template,
"dict": dict_to_template,
"jenny": jenny,
})
页面比较简单:
<p>{{ list }}</p>
<p>{{ list.0 }} {{ list.1 }}</p>
<p>{{ dict.2nd }} {{ dict.3rd }}</p>
<hr>
<p>{{ jenny }}</p>
<p>{{ jenny.show_age }}</p>
运行的结果是:
['三鹅', '四筒', '伍德']
三鹅 四筒
四筒 伍德
-----------------------------------------
<Person name:jenny, age:18>
18
结果说明在模板里:
- 列表可以通过0,1等索引来取得对应位置的值
- 字典通过键取值,采用字典.键名的方法,并不是使用方括号
- 虽然2nd这种不符合python的标识符属性,但可以写在模板里
- 如果传入一个对象,会用其__str__方法的结果去替换字符串
- 对于对象的方法,调用的时候不加括号.Django默认只能支持不带参数的方法,如果带参数,则需要自行编写扩展
内建模板语言语句
Django有一些模板语言的关键字,就和python的关键字类似.模板语言的语句就是由关键字和标识符构成的.
模板语言的关键字主要分为两类,一类叫做filter,用于变量相关操作.
一类叫做tag,用于逻辑控制.
filter类
这个filter类指的是一种共通的写法,即:
{{ 变量名|filter_name:参数 }}
其中变量名就是普通的变量名,之后接一个管道符,filter_name为一些特殊的关键字.后边紧接冒号和参数.注意,管道符和冒号前后不要有空格,整条语句紧密排在一起.
filter类的主要作用是,在显示变量的基础上额外进行一些操作,下边来看看filter类之内可以使用哪些关键字(这些关键字是不能够在filter类以外使用的)
filter关键字 |
作用 |
{{ value|add:"2" }} |
会把2加到value上,这个add可以支持+运算符的对象,比如字符串,列表,可以转换成数字的字符串等.一般python里无法相加的对象,这里也不被支持. |
{{ value|addslashes }} |
在value字符串其中的引号之前加上转义符"\" |
{{ value|capfirst }} |
首字母大写 |
{{ value|center:"15" }} |
一般靠CSS来进行居中,而不是用此方法硬编码 |
{{ value|cut:" " }} |
从value中去掉所有的参数,例子表示去掉空格.也不常用,最好先用python处理好.参数如果是多个字符,表示去掉该字符序列.大小写敏感. |
date和time |
详细看这里.传入的必须是一个datetime对象,指定字符串格式来显示日期 |
{{ value|default:"nothing" }} |
如果不传入值,默认显示参数,比较常用 |
{{ value|default_if_none:"nothing" }} |
这个表示当value为None的时候,显示参数,其他情况显示value的传入值 |
{{ value|dictsort:"name" }} |
需要value为一个字典,参数作为key,按照参数的顺序排序后显示,常用在遍历字典的时候和if搭配使用. |
{{ value|dictsortreversed:"name" }} |
和dictsort一样用法,只不过是逆向排序 |
{{ value|divisibleby:"3" }} |
判断value能不能被参数整除,例子里如果value是27,则这个地方在页面上会显示True,否则显示False.注意参数不能为0否则出错,如果不传value或者value不支持除号运算也出错.不常用 |
{{ value|escape }} |
表示转义,将尖括号,引号等转义,只作用于字符串,如果value是传入其他值,无影响.还有一种用法是自动转义,在讲语句关键字的时候会提到 |
{{ value|filesizeformat }} |
将value表示的数值,用表示文件大小的字节,KB,MB来显示 |
{{ value|first }} |
value是一个列表,返回列表的第一个元素,常用. |
{{ value|get_digit:"2" }} |
获取从右边开始第几位的数,参数表示索引,最右边的索引为1,如果value是12345,例子里的结果是4,即从右边数第二位数 |
{{ value|join:" // " }} |
和python的join方法类似. |
{{ value|last }} |
取列表的最后一个元素. |
{{ value|length }} |
取长度,长度的意思和python里一样.注意,如果不传值,结果是0 |
{{ value|length_is:"4" }} |
判断value的长度是否与参数一致 |
{{ value|linebreaks }} |
这个的意思是对HTML里的换行(\加上一个回车)自动替换成<br />,然后将整个value包裹在p标签内 |
{{ value|linebreaksbr }} |
这个与上一个的相同之处在于替换br标签,但不会把value包裹在p标签内. |
{{ value|linenumbers }} |
如果value是多行,自动在每行开头显示序号 |
{{ value|ljust:"10" }} |
参数为总宽度,在这个宽度内左对齐value的内容,不常用 |
{{ value|lower }} |
将value全部改成小写 |
{{ value|make_list }} |
类似于python的内置方法list(),会将字符串拆开作为列表 |
{{ value|phone2numeric }} |
按照手机键盘的对应顺序,将字母转换为数字. |
{{ num_messages|pluralize }}. |
这个很有意思,是根据num_messages的数量,决定是否显示指定的复数后缀,默认是s.经常用在显示提醒数量的提示上 |
{{ value|random }} |
value是一个列表,随机返回列表里的一个元素 |
"{{ value|rjust:"10" }}" |
右对齐,凡是涉及到外观的尽量少用,交给CSS去控制 |
{{ value|safe }} |
在正常情况下,Django对于模板变量的内容,默认是自动进行转义的.到了浏览器那里,便不会将该内容按照HTML来解释.加上safe表示无需转义,则交给浏览器会按照HTML来解释.如果一个单独的变量需要按照HTML解释,就用此种方法,如果整个页面都不需要转义,会采用转义语句里的autoescape |
{{ some_list|safeseq|join:", " }} |
对序列使用,可以将序列里的每一个都不转义.还可以和其他filter关键字搭配,比如例子中的不转义然后连接起来. |
{{ some_list|slice:":2" }} |
对序列进行切片,语法与python语法完全相同,支持负数索引.传入的变量只要是python里支持切片操作的对象即可. |
{{ value|slugify }} |
处理value,把空格变成减号,非ASCII转换成ASCII支持的字符,去掉所有不支持的字符,全部转换为小写,去掉开头和结尾的空格.显示经过上述处理之后的字符串. |
{{ value|stringformat:"E" }} |
这个是按照python的字符串C风格格式化显示内容.语法和python一样,只不过不需要加%号 |
{{ value|striptags }} |
从value字符串里尝试去掉所有XHTML标签. |
{{ value|time:"TIME_FORMAT" }} |
和data一样,参数用于格式化输出内容,value是一个datetime对象. |
{{ old_time|timesince:new_time }} |
是指经过了多少时间,两个变量都要是时间对象 |
{{ future|timeuntil:now }} |
是指从now日期到future还有多长时间.这个和上边的区别在于语义不同,都表示时间差 |
{{ value|title }} |
每个单词的首字母大写 |
{{ value|truncatechars:9 }} |
截断字符串,参数为长度,截断后的字符串最后三位默认被省略号"..."替换 |
{{ value|truncatechars_html:9 }} |
如果value是被包在一个标签里的,这个方法只会截断内部的数据,标签被保留 |
{{ value|truncatewords:2 }} |
这个是按照参数的数量截断单词,例子表示只保留开始的两个单词,后边用省略号替代 |
{{ value|truncatewords_html:2 }} |
和上边的类似,仅截断标签内内容,保留标签 |
{{ value|upper }} |
value全部大写 |
{{ value|wordcount }} |
统计单词的数量,不是字符的数量. |
{{ value|yesno:"yeah,no,maybe" }} |
value对于True,False和None的一个映射,如果不指定第三个参数,则None和False都返回第二个参数的内容.详细看此 |
{{ value|upper }} |
和data一样,参数用于格式化输出内容,value是一个datetime对象. |
自定义filter
通过filter的效果,以及其中很多功能与python一样的情况,可以猜想filter也可以用python函数编写实现自定义.filter实际上确实可以自行通过python函数编写.
{{ 变量名|filter_name:参数 }},实际上就是将变量名和参数传递给filter_name名称的函数.这个函数接受的变量可以是任意的python对象,参数可以有默认值或者忽略,返回值最好是一个字符串或者支持被打印到模板语句处的值.下边来编写一个自定义的filter函数.
自定义filter函数存放位置:
APP名称下边建立一个package(不是普通目录),名字叫做templatetags.其中建立一个py文件,名字可以自定义,这里叫myfilter.py.在其中编写自己的filter函数.
编写filter函数:
filter函数是有固定格式的:
# 开头两行是固定的,导入模板,生成一个注册器,用于自己的filter注册在模板内使用.
from django import template
register = template.Library()
# 编写好函数之后,用装饰器,注册一个filter函数名,名字可以不和实际的函数名相同
@register.filter(name="add_s")
def add_suffix(arg, suffix="-Django"):
return arg + suffix
至此在后端的程序代码编写完毕,还需要在模板内进行导入和使用
在模板内使用自定义filter函数
{% load myfilter %}
{{ value|add_s }}
{{ value|add_s:"-Apache" }}
第一行是先要导入自己的文件,如果在前边按照规定建立了文件,此时可以找到自己的filter函数所在的py文件.之后就像正常filter一样使用即可.例子里打印出:
Mysite-Django
Mysite-Apache
总结:自定义编写filter函数的第一个参数永远是模板内管道符前边的变量,第二个参数是模板内的参数,可以有也可以没有.不能有第三个变量.filter函数必须使用装饰器注册,然后在模板内先引入再使用.
tag关键字
tag关键字主要用于逻辑控制.涉及到tag关键字的模板语句,都需要用{% %}包裹起来.
autoescape
用于设置自动转义开启与否,包裹一段需要转义或者不转义的HTML内容,如果其中有filter设置了safe,则safe优先生效,使用方法:
{% autoescape on %}
{{ body }}
{% endautoescape %}
comment
被comment语句包裹的内容都被视为注释,不会被实际渲染,comment标签不能嵌套.comment的开始标签里还可以加上自定义的字符串用于提示注释的内容:
<p>Rendered text with {{ pub_date|date:"c" }}</p>
{% comment "Optional note" %}
<p>Commented out text with {{ create_date|date:"c" }}</p>
{% endcomment %}
cycle
cycle后边可以跟多个变量,是一种循环,不断的从最左边循环到最右边的变量,然后再从头开始,就像while循环一样.经常和其他循环进行搭配,用于反复在某些数量的内容上进行操作.
比较经典的是表格的奇数行与偶数行应用不同的样式.
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
这里的row1,row2写死了,也可以用变量来替代,或者混合变量与常量均可,如果想要在循环中拿到目前cycle所取的值,可以加上as 变量名:
<tr>
<td class="{% cycle 'row1' 'row2' as rowcolors %}">...</td>
<td class="{{ rowcolors }}">...</td>
</tr>
<tr>
<td class="{% cycle rowcolors %}">...</td>
<td class="{{ rowcolors }}">...</td>
</tr>
这里的意思是将"row1" "row2"的当前循环值赋给了rowcolors, rowcolors的值此时是row1,然后到了第二个cycle 语句,就回去循环rowcolor对象的下一个值,也就是row2.
注意上边的写法,td标签的class属性直接等于了cycle语句,这说明用了 as 之后,cycle是立刻去返回了第一个值.是不是可以类似for循环,只有在启动循环的时候,才去获取第一个值,这样可以让cycle语句更加清晰.答案就是在as后边的变量名后边写上 silent.
{% for obj in some_list %}
{% cycle 'row1' 'row2' as rowcolors silent %}
<tr class="{{ rowcolors }}">{% include "subtemplate.html" %}</tr>
{% endfor %}
与 cycle搭配使用的还有{% resetcycle %},表示让cycle重新从第一个值开始循环.经常用在展示完一部分内容,需要开始一片新内容的时候使用.比如两段表格采用同一个类控制,每个表格开始的时候都重设cycle以使样式统一.
csrf_token
这个标签用于防止跨站请求伪造。
需要在form表单的内部写上{% csrf_token %}.
firstof
用于判断若干个变量,返回第一个不是False的值,如果都是False,还可以指定一个返回值,不指定则什么都不显示.
{% firstof var1 var2 var3 "fallback value" %}
还可以用as 来代表返回的那个值:
{% firstof var1 var2 var3 as value %}
for
for 逻辑非常重要,基本上展示页面都会用到for逻辑.基础的for逻辑由启动for循环和终止的结构组成,结构内部的模板语言和HTML字符串都会被循环执行,例如经典的生成列表的循环:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
如果需要反向迭代,在for开始语句最后加上reversed,例如:
{% for obj in list reversed %}
for 循环和python类似,也支持拆解序列赋值,比如迭代一个字典:
{% for key, value in data.items %}
{{ key }}: {{ value }}
{% endfor %}
这里需要注意的就是字典的items方法是不能加括号的.
在for循环的内部,有一些特定的变量可以使用,在for循环外边无法单独使用.
变量名称 |
描述 |
forloop.counter |
当前循环的索引值,从1开始,经常用于生成序号展示用. |
forloop.counter0 |
当前循环的索引值,从0开始,一般用于在页面内继续用该索引操作变量 |
forloop.revcounter |
当前循环的倒序索引值,从1开始,经常用于展示或者计算数量. |
forloop.revcounter0 |
当前循环的倒序索引值,从0开始. |
forloop.first |
这是一个布尔值,判断当前循环是不是第一个.经常与if搭配使用 |
forloop.last |
这是一个布尔值,判断当前循环是不是最后一个,经常与if搭配使用. |
forloop.parentloop |
如果有嵌套循环,这个指的是本层循环的外层循环.无嵌套循环则不显示 |
for循环内部,还可以引入{% empty %},表示当当前循环的内容无法显示的时候,显示替代的内容:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% empty %}
<li>Sorry, no athletes in this list.</li>
{% endfor %}
</ul>
这种逻辑用if也能够实现,但用这种方式比较简单.
if
if是非常基础和重要的分支语句.经常用在页面需要展示或者不展示一些元素的判断中.if语句的基本格式如下:
{% if athlete_list %}
Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
Athletes should be out of the locker room soon!
{% else %}
No athletes.
{% endif %}
使用方法和python的if逻辑相同.紧接着if语句是第一分支,每个elif后边是独立分支,eles是收尾分支,唯一不同的是一定要用 endif 来结束if判断.
if内部可以使用逻辑运算符 and or not 以及关系运算符 ==, !=, <, >, <=, >=, in, not in, is, is not.运算符的语义和python是完全相同的.
唯一区别是if语句不能够进行连续判断,只能够两个两个的判断.
if语句中(其实是所有模板语言语句中),被判断的条件可以和filter类搭配使用,比如判断一个序列的长度,没有必要再让后端单独传一个长度进来,直接可以写成:
{% if value|length > 10 %}
<p>Length is greater than 10</p>
{% else %}
<p>Length is less than 10</p>
{% endif %}
ifchanged
ifchanged用于检测变量在循环内是否改变,一旦改变,就执行一个分支,不改变就执行另外一个分支:
<h1>Archive for {{ year }}</h1>
{% for date in days %}
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %}
需要用 endifchanged 来结束判断.
ifchanged可以同时判断多个参数:
{% for date in days %}
{% ifchanged date.date %} {{ date.date }} {% endifchanged %}
{% ifchanged date.hour date.date %}
{{ date.hour }}
{% endifchanged %}
{% endfor %}
ifchanged还可以用else语句在不变化的情况下也执行一些语句.
{% for match in matches %}
<div style="background-color:
{% ifchanged match.ballot_id %}
{% cycle "red" "blue" %}
{% else %}
gray
{% endifchanged %}
">{{ match }}</div>
{% endfor %}
load
读入自定义的tag或者filter函数,在刚才编写自定义filter函数的时候已经用到过,需要先编写函数并在模板组件内注册,之后在模板内通过load语句导入代码文件才可以使用自定义内容.
now
用给定的字符串格式化当前的时间.注意这个时间是页面生成时候一次性显示的,不是动态时间.例子:
It is {% now "jS F Y H:i" %}
还可以指定now的结果为某个变量,在其他地方使用:
{% now "Y" as current_year %}
{% blocktrans %}Copyright {{ current_year }}{% endblocktrans %}
resetcycle
与cycle合用,让cycle重头开始循环.
spaceless
让被包裹的HTML部分实际渲染时去掉标签之间的空白.注意,是标签之间的空白,而不是text部分的空白:
{% spaceless %}
<p> <strong> Hello </strong></p>
{% endspaceless %}
这个处理结果不会去掉Hello两侧的空白,只会将p标签和strong标签之间的空白去掉.
templatetag
这实际上是给模板语言的标签转义.由于模板语言在显示{{}}等符号的时候,并没有类似反斜杠一样的转义符号,因此这个tag是专门将模板特殊符号进行转义的.加上不同的参数表示不同的符号:
参数 |
对应符号 |
openblock |
{% |
closeblock |
%} |
openvariable |
{{ |
closevariable |
}} |
openbrace |
{ |
closebrace |
} |
opencomment |
{# |
closecomment |
#} |
实际使用的时候加上参数使用即可:
{% templatetag openblock %} url 'entry_list' {% templatetag closeblock %}
with
类似python的上下文管理,在with和endwith之间,可以定义一个临时变量并且使用这个临时变量.
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
url
这个是用来反向解析URL的别名用于获取原始URL.使用非常广泛.使用方法是:
{% url url_name %}
这个位置的tag就会被在URLconf文件中设置好的别名url_name对应的URL替代.在后边讲解路由系统的时候会专门提到反向解析.url tag的使用可以避免硬编码,经常被使用.
上边是常用的在模板内部建立逻辑的模板语言,还有一些和母版相关的tag专门学习.
母版及相关tag
母版就是把一个类里边公用的部分提取出来做成一个母版,之后渲染具体页面的时候,先继承母版,再替换掉母版里需要替换的部分,生成具体页面.母版和实际生成的页面之间的关系,就像是类和对象的关系一样.既然有了母版可以继承,也可以在渲染的时候导入其他页面在当前的上下文环境内进行渲染.
模板语言里针对母版的继承,替换,以及导入其他页面有专门的tag语言标签.
extends 与 block
来看一个典型的母版:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
{% block page-css %}
{% endblock %}
</head>
<body>
<h1>这是母板的标题</h1>
{% block page-main %}
{% endblock %}
<h1>母板底部内容</h1>
{% block page-js %}
{% endblock %}
</body>
</html>
母版其实就是一个搭好了骨架的web页面,其中用block 将需要替换的部分标记成变量,在继承母版的页面里,用相同变量名称的block标签来包裹实际替换到母版内的部分.
一般母版会设计出如下几个部分进行替换:
- 通用静态文件引用
- 个性化CSS文件引用
- 导航条
- 侧边栏
- 页面主体部分
- 页脚部分
编写具体页面的时候,用extends来继承母版,然后用block来表示实际要替换的内容,就针对例子中的母版,编写具体页面如下:
{% extends 'mother.html' %}
{% block page-css %}
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
{% endblock %}
{% block page-main %}
<p class="text-center">这是页面的主体部分,是从子版替换过来的</p>
{% endblock %}
{% block page-js %}
<p class="text-danger text-center">这是页面的底部</p>
{% endblock %}
<p>这是页面外边的部分</p>
继承母版的标签用extends,需要在页面顶部引入.注意,引入以后,整个页面就具有了母版的的全部内容.在extends标签之后写的普通HTML内容,已经处于body标签之外,就不会被正常显示出来,例子中的最下边的p标签就不会显示出来.
在继承了母版之后,对于需要替换的地方,包裹在block标签内,其中的变量名,与母版内的部分对应.这样就完成了替换.
对于页面元素变化不大的一系列页面,只要编写好母版和对应规则,在编写子页面的时候,只需要集中精力编写子页面特有的部分即可.
include
与extends继承母版不同的是,include是引入一个组件.比如导航条或者页脚经常也不写死在母版里,而是在编写子页面的时候,用对应的block套着include标签来替换其中的内容.
导入进来的页面不用是完整的HTML页面,只要是存放了组件本身的HTML代码即可.
沿用上边的例子,页脚部分单独写在footer.html文件内部如下:
<div class="row text-center text-danger"><p>我是从footer.html导入的页脚部分</p></div>
此时需要修改子页面的内容为:
{% block page-js %}
{% include "footer.html" %}
{% endblock %}
与之前可以达到一样的效果.
静态文件路径变量
在继承母版和组件的时候,经常遇到的问题是导入静态文件的路径是写死的,这样就降低了模板的通用性.只需要在模板内引入static变量用于表示当前环境的静态文件目录,即可将static变量与文件名拼接来得到动态的静态文件路径.对于上边替换母版引入CSS的部分可以改写为如下语句:
{% block page-css %}
{% load static %}
<link rel="stylesheet" href="{% static "bootstrap/css/bootstrap.min.css" %}">
{% endblock %}
如果需要反复引用,还可以给拼接后的路径通过 as 设置成一个变量来调用:
{% block page-css %}
{% load static %}
<link rel="stylesheet" href="{% static "bootstrap/css/bootstrap.min.css" as bootstrap %}">
<link rel="stylesheet" href="{{ bootstrap }}">
{% endblock %}
可见通过设置静态路径变量,可以有效的提高模板的通用性.
通过母版的继承,导入组件的功能,实现模板骨架,通用组件,个性化数据部分互相分离,降低耦合程度.在需要生成页面的时候,通过模板语言将各个部分最终组成一个页面发送给浏览器.这就是Django的模板模块.
自定义tag函数
自定义simple_tag函数
在之前自定义了filter函数,filter函数的局限性很大,只有两个参数而且第一个参数已经固定.如何编写参数更多,更自由的模板函数,就要用到自定义simple_tag的方法.
自定义simple_tag的准备工作和自定义filter函数是相同的,都需要在app的目录下建立templatetags的包,然后在其中建立自定义名称的py文件用于写函数.也都需要导入注册器来用装饰器装饰函数.
# mytag.py
from django import template
register = template.Library()
@register.simple_tag(name="mysum")
def my_sum(*args):
temp = ""
for arg in args:
temp += arg
return temp
在mytag.py文件中自定义了一个求和的函数,并且用装饰器注册成名字为"mysum"的tag,之后在模板中使用:
{% load mytag %}
{% mysum value1 value2 value3 value4 %}
给value1-4传入4个字符串,就可以拼接起来了,实际上原来的函数支持不限制个数的变量,所以simple_tag的使用范围比filter要广.在很多复杂的应用场景中,一般是必须要编写自定义的simpletag来使用的.
自定义inclusion_tag函数
inclusion_tag的函数不是直接体现结果,先得到函数的返回值,然后将返回值传给一个html模板,取得该模板生成的内容之后,再返回到当前inclusion标签的位置.
可以说,include标签引入一段模板,而inclusion_tag引入的是一段传入数据生成后的HTML代码.
举个例子:在上一个例子中的mytag.py文件内部继续写一个注册为inclusion_tag类型的函数,注意这次的参数不是给函数起别名,而是一个html文件
# mytag.py
@register.inclusion_tag('result.html')
def show_results(n):
n = 1 if n < 1 else int(n)
data = ["第{}项".format(i) for i in range(1, n+1)]
return {"data": data}
result.html的内容是:
<ul>
{% for choice in data %}
<li>{{ choice }}</li>
{% endfor %}
</ul>
在模板内插入inclusion_tag标签的写法是:
{% load mytag %}
{% mysum value1 value2 value3 value4 %}
{% show_results 10 %}
这个时候模板显示的结果是10个项目的列表,其工作原来就是将10传入自定义的show_results函数,得到一个字典,键名为data,值是一个第1项到第10项的列表,然后把这个字典传入给result.html,通过模板循环语句得到了实际的有10个项目的ul,再将此时实际的result.html其中的HTML语句插入到当前位置.
相比自定义filter函数,自定义simple_tag和inclusion_tag的使用范围要广的多,很多需求通过自定义模板函数来解决是比较简便且不容易出bug的方法.