本节将详细介绍Django模版系统的语法。Django模版语言致力于在性能和简单性上取得平衡。
如果你有过其它编程背景,或者使用过一些在HTML中直接混入程序代码的语言,那么你需要记住,Django的模版系统并不是简单的将Python嵌入到HTML中。
模版是纯文本文件,可以生成任何基于文本的文件格式,比如HTML,XML,CSV等。
下面是一个小模版,它展示了一些基本的元素。
{% extends "base_generic.html" %} {% block title %}{{ section.title }}{% endblock %} {% block content %} <h1>{{ section.title }}</h1> {% for story in story_list %} <h2> <a href="{{ story.get_absolute_url }}"> {{ story.headline|upper }} </a> </h2> <p>{{ story.tease|truncatewords:"100" }}</p> {% endfor %} {% endblock %}
变量: {{ variable }}。
当模版引擎遇到一个变量,它将从上下文context
中获取这个变量的值,然后用值替换掉占位符。
变量的名字中可以包含字母数字以及下划线("_"),但是不能有空格或标点符号。
圆点在模板系统中有着特殊的地位,是万能的魔法师。
当模版系统渲染变量的时候遇到点("."),它将以下面的顺序查询这个圆点具体代表的功能:
如果你使用的变量不存在,模版系统将插入string_if_invalid
选项的值,默认是''(空字符串)。
以下划线开头的变量会被拒绝访问,因为在Python中,它们被看作私有变量。
过滤器看起来是这样的:{{ var_name | lower }}
。
使用管道符号(|
)来应用过滤器。
lower
过滤器的作用是将文本转换成小写。
过滤器可以链式调用。一个过滤器的输出应用于下一个过滤器,作为输入。例如:{{ text | escape | linebreaks }}
就是一个常用的过滤器链,它首先转义文本内容,然后把换行符转成<p>
标签。
一些过滤器带有参数。 过滤器的参数看起来像是这样: {{ bio|truncatewords:30 }}
。 这将只显示bio变量的前30个词。
过滤器参数包含空格的话,必须用引号包起来。例如,使用逗号和空格去连接一个列表中的元素,你需要使用{{ list|join:", " }}
。
Django提供了大约六十个内置的模版过滤器,很多时候你想要的功能,它都已经提供了。请经常查看这些过滤器,去发现新大陆吧。下面是几个例子,更多的介绍在后面的章节:
为false值或者空变量提供默认值,像这样:
{{ value|default:"nothing" }}
如果 value
没有提供或者为空,那么他将显示为“nothing
”。
返回值的长度。它对字符串和列表都起作用。
{{ value|length }}
如果value是['a', 'b', 'c', 'd']
,那么输出4。
格式化为“人类可读”文件大小单位(即'13 KB',4.1 MB','102 bytes'等)。
{{ value|filesizeformat }}
如果value是123456789,输出将会是117.7MB。
我们还可以创建自定义的模板过滤器和标签,这是最终极的武器。
标签看起来像是这样的: {% tag %}
。
标签比变量复杂得多,有些用于在输出中创建文本,有些用于控制循环或判断逻辑,有些用于加载外部信息到模板中供以后的变量使用。
一些标签需要开始和结束标签(即 {% 标签 %} ... 标签 内容 ... {% 结束标签 %}
)。
Django自带了大约24个内置标签。下面介绍几个常用的标签:
循环对象中的每个元素。需要结束标签{% endfor %}
。例如,显示athlete_list
中提供的运动员列表:
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul>
计算一个表达式,并且当表达式的值是“True”时,显示块中的内容。需要{% endif %}
结束标签。整体逻辑非常类似Python的if、elif和else,如下所示。:
{% 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 %}
在上面的例子中,如果athlete_list
不是空的,运动员的数量将显示为{{ athlete_list|length }}
。否则,如果athlete_in_locker_room_list
不为空,将显示“Athletes should be out…”。如果两个列表都是空的,将显示“No athletes.” 。
还可以在if标签中使用过滤器和多种运算符:
{% if athlete_list | length > 1 %} Team: {% for athlete in athlete_list %} ... {% endfor %} {% else %} Athlete: {{ athlete_list.0.name }} {% endif %}
需要注意,大多数模版过滤器都返回字符串类型,所以使用过滤器做整数类型的比较通常是错误的,但length是一个例外。
继承和复写模版,类似Python的类继承和重写机制,请参看后面的章节。
要注释模版中一行的部分内容,使用注释语法:{# #}
。
例如,下面的模版将被渲染为'hello':
{# greeting #}hello
注释可以包含任何模版内的代码,有效的或者无效的都可以。 像这样:
{# {% if foo %}bar{% else %} #}
以上是单行注释(在{# .... #}
中,不允许有新行)。
如果需要注释掉模版中的多行内容,请使用comment标签。
Django模版引擎中最强大、最复杂的部分就是模版继承了。
模版继承允许你创建一个包含基本“骨架”的父亲模版,它包含站点中的共有元素(比如顶部栏和底部脚注),并且可以定义能够被子模版覆盖的blocks。
我们通过下面的例子,来介绍模版继承的概念:
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css"> <title>{% block title %}My amazing site{% endblock %}</title> </head> <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> {% endblock %} </div> <div id="content"> {% block content %}{% endblock %} </div> </body> </html>
这个模版,通常被命名为base.html
,它定义了一个可以用于两列排版页面的简单HTML骨架。
子模版首先需要做的是继承父模板base.html
,然后复写、填充,或者说实现其中的blocks。
block是在子模版中可能会被覆盖掉的代码块。在上面的例子中,定义了三个可以被子模版重写的block标签,分别是title、content和siderbar。
下面是一个子模版的例子:
{% extends "base.html" %} {% block title %}My amazing blog{% endblock %} {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %}
extends标签是这里的关键。它告诉模版引擎,这个子模版“继承”了一个父模版。当模版系统处理这个模版时,首先会去加载父模版的内容,也就是“base.html”。
加载过程中,模版引擎会发现base.html
中的三个block标签,并用子模版中的内容来替换这些block。 根据blog_entries
的值,最终的HTML是这样的:
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>My amazing blog</title> </head> <body> <div id="sidebar"> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </div> <div id="content"> <h2>Entry one</h2> <p>This is my first entry.</p> <h2>Entry two</h2> <p>This is my second entry.</p> </div> </body> </html>
请注意,上面例子中的子模版并没有定义sidebar block
,这种情况下,将继承使用父模版中的内容。父模版的{% block %}
标签中的内容总是被用作默认内容。
你可以将block简单地理解为Python类中定义的方法和属性,可以被子类重写覆盖。
Django支持多层次的继承!常用方式是类似下面的三级结构:
base.html
模版,用来控制整个站点的主要视觉和体验。base_SECTIONNAME.html
模版。 例如base_news.html
,base_sports.html
。这些模版都继承base.html
,并且包含了各自特有的样式和设计。上面的方式可以使代码得到最大程度的复用,并且使得添加内容到共享的内容区域更加简单,例如app范围内的导航条。
下面是使用继承的一些相关说明:
{% extends %}
标签,它必须是模版中的第一个标签! {% block %}
标签越好。子模版不强制重写父模版中所有的blocks,所以可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。{% block %}
中。{{ block.super }}
变量。如果想要在父block中新增内容而不是完全覆盖它,这将非常有用。使用{{ block.super }}
插入的数据不会被自动转义,因为父模板中的内容已经被转义。{% block %}
之外使用模板标签的as
语法创建的变量,不能在块内使用。 例如,下面的模板不会显示任何内容:
{% trans "Title" as title %} {% block content %}{{ title }}{% endblock %}
{% endblock %}
标签添加名字,像这样:{% block content %} ... {% endblock content %}
在大型模版中,这有助于你清楚的看到哪一个{% block %}
标签被关闭了。
{% block %}
有很高的优先级,它经常会不管外围的控制标签,而强行起效。比如下面的例子:{% if change_title %} {% block title %}Hello!{% endblock title %} {% endif %}
即使change_title
的值为False,if标签内部的title块,依然会被替换。
当从模版中生成HTML文件时,总会存在各种风险,比如xss代码注入等恶意攻击。比如下面的模版片段:
Hello, {{ name }}
它看起来像是无害的,只是用来显示用户的名字,但是设想一下,如果用户像下面这样输入他的名字,会发生什么:
<script>alert('hello')</script>
使用这个名字的值,模版将会被渲染成这样:
Hello, <script>alert('hello')</script>
这意味着浏览器会弹出一个JavaScript警报框!
类似的,如果名字包含一个 '<' 符号(比如下面这样),会发生什么呢?
<b>username
这将会导致模版被渲染成这样:
Hello, <b>username
这会导致网页的其余部分被粗体化!
显然,我们不能盲目信任用户提交的数据,并且直接将它们插入到网页中,这种类型的安全问题被叫做跨站脚本攻击(Cross Site Scripting)(XSS,为避免和CSS同名,改为X)。
为了安全考虑,我们有两个选择:
<
会转换为<
>
会转换为>
'
(单引号)转换为'
"
(双引号)会转换为"
&
会转换为&
强烈建议:将第二种功能做为默认打开的设置,不要关闭它!
凡事都有正反两面。有时,模板变量含有一些你打算渲染成原始HTML的数据,你并不想转义这些内容。 例如,你可能会在数据库中储存一些HTML代码,并且直接在模板中嵌入它们。或者,你可能使用Django的模板系统来生成不是HTML的文本 ,而是比如邮件内容。也就是说,我们在某些时候要关闭自动转义功能,怎么办呢?
对于单个变量:
使用safe过滤器来关闭变量上的自动转义:
This will be escaped: {{ data }} This will not be escaped: {{ data|safe }}
safe是safe from further escaping
或者can be safely interpreted as HTML
的缩写。请确保你知道自己在用safe过滤器干什么!在上面的例子中,如果data是<b>
,输出会是:
This will be escaped: <b> This will not be escaped: <b>
对于模板代码块:
要控制一段代码的自动转义,请将这部分区域包裹在autoescape
标签中,像这样:
{% autoescape off %} Hello {{ name }} {% endautoescape %}
autoescape
标签接受on或者off作为它的参数,可以嵌套,还可以内部反转。下面是一个模板的示例:
Auto-escaping is on by default. Hello {{ name }} {% autoescape off %} This will not be auto-escaped: {{ data }}. Nor this: {{ other_data }} {% autoescape on %} Auto-escaping applies again: {{ name }} {% endautoescape %} {% endautoescape %}
自动转义标签autoescape还会作用于扩展(extend)了当前模板的模板,以及通过include标签包含的模板,就像所有block标签那样。 看下面的例子:
# base.html文件 {% autoescape off %} <h1>{% block title %}{% endblock %}</h1> {% block content %} {% endblock %} {% endautoescape %} # child.html文件 {% extends "base.html" %} {% block title %}This & that{% endblock %} {% block content %}{{ greeting }}{% endblock %}
由于在base模板中关闭了自动转义功能,它也会在child模板中关闭,导致当greeting变量含有<b>Hello!</b>
字符串时,会渲染HTML。
<h1>This & that</h1> <b>Hello!</b>
过滤器的字符串参数:
之前我们展示过,过滤器的参数可以是字符串:
{{ data|default:"This is a string literal." }}
要注意,所有这种字符串参数在插入模板时都不会进行任何自动转义。原因是,Django认为模板的作者可以控制字符串字面值的内容,所以可以确保在模板编写时文本经过正确转义。白话讲就是,你个程序员对自己传递的参数心里要有数!
也即是说你应该这样编写:
{{ data|default:"3 < 2" }}
而不是:
{{ data|default:"3 < 2" }} {# 错误的做法#}
大多数python对象上的方法调用同样可用于模板中。
这意味着模板能够访问到的不仅仅是对象的属性(比如字段名称)和视图中传入的变量,还可以执行对象的方法。 例如,Django ORM提供了entry_set
语法用于查找关联到外键的对象集合。 如果模型comment有一个外键关联到模型task,可以根据task遍历其所有的comments,像这样:
{% for comment in task.comment_set.all %} {{ comment }} {% endfor %}
与之类似,QuerySets提供了count()方法来计算含有对象的总数。因此,你可以像这样获取所有关于当前任务的评论总数:
{{ task.comment_set.all.count }}
当然,还可以调用显式定义在模型上的方法:
# models.py class Task(models.Model): def foo(self): return "bar"
template.html
{{ task.foo }}
注意,Django有意限制了模板语言的处理能力,不能在模板中给方法传递参数。Django认为,数据应该在视图中处理,然后传递给模板用于展示。
对于如下的模型:
from django.db import models # Create your models here. class Student(models.Model): name = models.CharField(max_length=128) class Course(models.Model): name = models.CharField(max_length=128) students = models.ManyToManyField('Student')
模型Course有一个多对多字段指向Student模型。
假设编写了一个如下的视图:
def test(request): course = models.Course.objects.get(pk=1) return render(request, 'course.html', locals())
获取了id为1的course对象,并将它传递给course.html模版,模版代码如下:
{% for student in course.students.all %} <p>{{ student.name }}</p> {% endfor %}
首先通过course.students.all
,查寻到course对象关联的students对象集,然后用for标签循环它,获取每个student对象,再用student模型的定义,访问其各个字段的属性。
对于反向查询,从student往course查,假设有如下的视图:
def test2(request): student = models.Student.objects.get(pk=1) return render(request, 'student.html', locals())
获取了id为1的student对象,并将它传递给student.html模版,模版代码如下:
{% for course in student.course_set.all %} {{ course.name }} {% endfor %}
通过student.course_set.all
,反向获取到student实例对应的所有course对象,然后再for标签循环每个course,调用course的各种字段属性。
对于外键ForeignKey,其用法基本类似。只不过正向是obj.fk
,且只有1个对像,不是集合。反向则是obj.fk_set
,类似多对多。
某些第三方库提供了自定义的标签和过滤器。想要在模板中使用它们,首先要确保已经在INSTALLED_APPS
中注册了它(下面假设我们已经注册了django.contrib.humanize
),之后在模板中使用load
标签:
{% load humanize %} {{ 45000|intcomma }}
上面的例子中, load标签加载了humanize
应用的标签库,之后我们可以使用它的intcomma
过滤器。
load标签可以同时接受多个库名称,由空格分隔。 例如:
{% load humanize i18n %}
自定义库和模板继承:
当你加载一个自定义标签或过滤器库时,标签或过滤器只在当前模板中有效--并不是带有模板继承关系的任何父模板或者子模版中都有效。白话说就是,你在父模板中可能加载了自定义标签,然并卵,你在子模版中还要再加载一次!
例如,如果一个模板foo.html
带有{% load humanize %}
,子模版(例如bar.html
,带有{% extends "foo.html" %}
)中不能直接使用humanize
的模板标签和过滤器。 子模版中需要再次添加{% load humanize %}
。可以参考Python的import语法。
这个特性是出于保持可维护性和逻辑性。
load代码尽量放在文件顶部,使用它之前。
很好!
精彩
打卡
def test(request): course = models.Course.objects.get(pk=1) return render(request, 'course.html', locals()) 刘老师,这里面的local()是什么意思啊?
locals() 函数会以字典类型返回当前位置的全部局部变量。
觉得学习应该是循序渐进,从易到难,这样一下子全部展开真的有点接受不了
这里相当于是速查手册了,你需要先按照“第一个Django应用”这一节的内容熟悉Django的流程和基本原理,然后再来看这里拓展,这样就好理解了,或者你可以百度找一篇django搭建博客实例,然后对照代码来这里学习。
后来自己做了一些项目之后,现在回过头来看,这章居然全部看懂了,好开心,谢谢刘老师。 建议刚开始学习的时候要不求甚解,不要纠结,了解怎么一回事,然后放在心里继续往下面看。 提几个小建议,翻译有点生硬。 在“七、自动转义HTML”里,可以告诉读者name的值为“”“”<script>alert('hello')</script>“”“”“”,而不是“如果用户像下面这样输入他的名字”,那样的话有些读者不知道这个script里的含义是什么。 “”八、方法调用 这部分内容,如果你掌握的极大提高你的模版语言能力。“”应该是如果你掌握,将极大提高你的模板语言能力。
模板中如果需要导入python包,能行吗?怎么做可以?
如果有能力二次开发Django,还是能做到的^_^
内容很丰富啊
内容写的真好,谢谢博主!
999
加油