每一个 Web 框架都需要一种很便利的方法用于动态生成 HTML 页面。 最常见的做法是使用模板。
模板包含所需 HTML 页面的静态部分,以及一些特殊的模版语法,用于将动态内容插入静态部分。
说白了,模板层就是如何往 HTML 文件中填入动态内容的系统。
Django 可以配置一个或多个模板引擎(语言),也可以不用引擎。
Django 自带一个称为 DTL(Django Template Language )的模板语言,以及另外一种流行的 Jinja2 语言(需要提前安装,pip install Jinja2)。
Django 为加载和渲染模板定义了一套标准的 API,与具体的后台无关。加载指的是,根据给定的模版名称找到模板然后预处理,通常会将它编译好放在内存中。渲染则表示,使用 Context 数据对模板插值并返回生成的字符串。
DTL 作为 Django 原生的模板系统,一直到 Django1.8,都是唯一的内置模板系统。如果没有特别重要的理由,需要选择另外一种模板系统的话,建议坚持使用 DTL。
Django 很多内部组件都使用了 DTL,例如 django.contrib.admin,如果你不想让它们罢工,或者花费大力气进行修改,不要放弃 DTL。
一、 配置引擎
模板引擎通过 settings 中的 TEMPLATES 设置来配置。这是一个列表,与引擎一一对应,每个元素都是一个引擎配置字典。由 startproject 命令生成的settings.py 会自定定义如下的值:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { # ... some options here ... }, }, ]
BACKEND:后端。
内置的后端有 django.template.backends.django.DjangoTemplates 和django.template.backends.jinja2.Jinja2。
OPTIONS 中包含了具体的后端设置。
由于绝大多数引擎都是从文件加载模板的,所以每种模板引擎都包含两项通用设置:
1.DIRS:定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件。
2.APP_DIRS:告诉模板引擎是否应该进入每个已安装的应用中查找模板。通常请将该选项保持为 True。
每种模板引擎后端都定义了一个惯用的名称作为应用内部存放模板的子目录名称。(例如 Django 为它自己的模板引擎指定的是 ‘templates’,为 jinja2 指定的名字是‘jinja2’)。尤其是,django 允许你有多个模板引擎后端实例,且每个实例有不同的配置选项。 在这种情况下你必须为每个配置指定一个唯一的 NAME .
DTL 引擎的 OPTIONS 配置项中接受以下参数:
1.'autoescape':一个布尔值,用于控制是否启用 HTML 自动转义功能。默认为 True。
2.context_processors: 以"."为分隔符的 Python 调用路径的列表。默认是个空列表。
3.'debug':打开/关闭模板调试模式的布尔值。默认和 setting 中的 DEBUG 有相同的值。
4.'loaders':模板加载器类的虚拟 Python 路径列表。默认值取决于 DIRS 和APP_DIRS 的值。
5.string_if_invalid:非法变量时输出的字符串。默认为空字符串。
6.file_charset:用于读取磁盘上的模板文件的字符集编码。默认为 FILE_CHARSET的值。
7.'libraries':用于注册模板引擎。 这可以用于添加新的库或为现有库添加备用标签。
8.'builtins':以圆点分隔的 Python 路径的列表。
二、 简单的用法
django.template.loader 中定义了两个函数以加载模板。
get_template(template_name,using = None)
该函数使用给定的名称查找和加载模板,并返回一个 Template 对象。
模板的查找和加载机制取决于每种后端引擎和配置,如果想使用指定的模板引擎进行查找,请将模板引擎的 NAME 赋给 get_template 的 using 参数。
select_template(template_name_list,using = None)
和 get_template()相似, 只不过它使用包含模板名称的列表作为参数。
由 select_template()和 get_template()返回的 Template 对象都必须提供一个 render()方法,如下所示:
Template.render(context=None, request=None)
通过给定的 context 对该模板进行渲染。
如果提供了 context,那么它必须是一个 dict 对象。如果要提供 request 参数 ,必须使用 HttpRequest 对象。
针对下面的 TEMPLATES 配置,对模版文件的搜索顺序和路径如下:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ '/home/html/example.com', '/home/html/default', ], }, { 'BACKEND': 'django.template.backends.jinja2.Jinja2', 'DIRS': [ '/home/html/jinja2',
],
},
]
如果你调用函数 get_template('story_detail.html'), Django 将按以下顺序查找story_detail.html:
/home/html/example.com/story_detail.html('django'引擎)
/home/html/default/story_detail.html('django'引擎)
/home/html/jinja2/story_detail.html('jinja2'引擎)
如果你调用函数 select_template(['story_253_detail.html','story_detail.html']),Django按以下顺序查找:
/home/html/example.com/story_253_detail.html('django'引擎)
/home/html/default/story_253_detail.html('django'引擎)
/home/html/jinja2/story_253_detail.html('jinja2'引擎)
/home/html/example.com/story_detail.html('django'引擎)
/home/html/default/story_detail.html('django'引擎)
/home/html/jinja2/story_detail.html('jinja2'引擎)
注意:Django 查找到任何一个匹配的模板后便停止搜寻,所以这是个类似 url 搜索的短路操作!
强调:前面我们介绍过,建议在每个 APP 的的模版子目录下都建立一个子目录来唯一对应这个 APP。这样做可以增强你的 APP 的可用性。 将所有的模版文件放在根模版目录下会引发混淆。
要在一个子目录内加载模板,像下面这样:
get_template('news/story_detail.html')
如果结合上面例子中的 TEMPLATES 配置,这将会尝试按顺序查找并加载下列模板︰
/home/html/example.com/news/story_detail.html('django'引擎)
/home/html/default/news/story_detail.html('django'引擎)
/home/html/jinja2/news/story_detail.html('jinja2'引擎)
另外,为了减少加载模板、渲染模板等重复工作,django 提供了处理这些工作的快捷函数。
render_to_string(template_name, context=None, request=None,using=None)[source]
render_to_string()会像 get_template()一样加载模板并立即调用 render()方法。 它需要以下参数。
1.TEMPLATE_NAME:要加载的模板的名称或列表。
2.context:要用作模板的上下文进行渲染的数据字典,也就是你要插入的动态数据字典。
3.request:可选的 HttpRequest 对象。
4.using:指定使用的模板引擎 NAME。 搜索模板将仅限于该引擎。
用法示例:
from django.template.loader import render_to_string
rendered = render_to_string('my_template.html', {'foo': 'bar'})
三、基本语法
Django 模板语言(DTL)的语法包括四种结构。
1. 变量
变量的值来自 context 中的数据字典, 类似于字典对象的 keys 到 values 的映射关系。
变量是被}}和{{括起来的部分,例如:
My first name is {{ first_name }}. My last name is {{ last_name }}.
当模版引擎遇到一个变量,它将从上下文 context 中获取这个变量的值,然后用值替换掉它本身。假如有一个上下文{'first_name': 'John', 'last_name': 'Doe'},模板渲染后的真实值为:
My first name is John. My last name is Doe.
变量的命名包括任何字母数字以及下划线("_")的组合。点(".")也有可能会在变量名中出现,不过它有特殊的含义。最重要的是,变量名称中不能有空格或标点符号。
当模版系统遇到点("."),它将以这样的顺序查询这个圆点具体代表的功能:
1.字典查询(Dictionary lookup)
2.属性或方法查询(Attribute or method lookup)
3.数字索引查询(Numeric index lookup)
如果你使用的变量不存在,模版系统将插入 string_if_invalid 选项的值,默认设置为''(空字符串)。
2. 标签
模版语言中的标签类似 Python 中的函数,功能多样,使用灵活。可以输出内容、控制结构,甚至可以访问其他的模板标签。
标签是由%}和{%来定义的,例如:
{% csrf_token %} # csrf 令牌标签
大部分标签都接受参数:
{% cycle 'odd' 'even' %} # 循环使用'odd'和'even'
部分标签需要使用起始和闭合标签,典型代表为 for 循环标签和 if 判断标签:
{% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %}
Django 自带了大约 24 个内置的模版标签。下面是一些常用的标签:
2.1. for 循环标签
循环对象中每个元素。需要结束标签{% endfor %} 。例如,显示 athlete_list中提供的运动员列表:
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}li> {% endfor %} ul>
2.2. if,elif 和 else 标签
计算一个表达式,并且当表达式的值是“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 是一个例外。
2.3. block 和 extends 标签
继承和复写模版。类似 Python 的类继承和重写机制。
3. 过滤器
过滤器看起来是这样的:{{ name|lower }}。使用管道符号(|)来应用过滤器。该过滤器将文本转换成小写。
过滤器可以“链接”。一个过滤器的输出应用于下一个过滤器。例如:
{{ text|escape|linebreaks }}就是一个常用的过滤器链,它首先转移文本内容,然后把文本行转成
标签。
一些过滤器带有参数。 过滤器的参数看起来像是这样: {{ bio|truncatewords:30 }}。这将显示 bio 变量的前 30 个词。
过滤器参数包含空格的话,必须用引号包起来。例如,使用逗号和空格去连接一个列表中的元素,你需要使用{{ list|join:", " }}。
Django 提供了大约六十个内置的模版过滤器,很多时候你想要的功能,它都已经提供了,有需要的时候就去查一下吧。下面是一些常用的模版过滤器:
3.1. default
为 false 或者空变量提供默认值,像这样:
{{ value|default:"nothing" }}
3.2. length
返回值的长度。它对字符串和列表都起作用。
{{ value|length }}
如果 value 是['a', 'b', 'c', 'd'],那么输出 4。
3.3. filesizeformat
格式化为“人类可读”文件大小单位(即'13 KB',4.1 MB','102 bytes'等)。
{{ value|filesizeformat }}
如果 value 是 123456789,输出将会是 117.7MB。
4. 注释
模版语言的注释看起来像这样:
{# this won't be rendered #} # 单行注释
{% comment %}标签提供多行注释功能。
附:
Django 内置标签总览
可以查询下表来总览 Django 的内置标签:
标签 | 说明 |
autoescape | 自动转义开关 |
block | 块引用 |
comment | 注释 |
csrf_token | CSRF 令牌 |
cycle | 循环对象的值 |
debug | 调试模式 |
extends | 继承模版 |
filter | 过滤功能 |
firstof | 输出第一个不为 False 的参数 |
for | 循环对象 |
for … empty | 带 empty 说明的循环 |
if | 条件判断 |
ifequal | 如果等于 |
ifnotequal | 如果不等于 |
ifchanged | 如果有变化,则.. |
include | 导入子模版的内容 |
load | 加载标签和过滤器 |
lorem | 生成无用的废话 |
now | 当前时间 |
regroup | 根据对象重组集合 |
resetcycle | 重置循环 |
spaceless | 去除空白 |
templatetag | 转义模版标签符号 |
url | 获取 url 字符串 |
verbatim | 禁用模版引擎 |
widthratio | 宽度比例 |
with | 上下文变量管理器 |
Django 内置过滤器总览
可以查询下表来总览 Django 的内置过滤器:
过滤器 | 说明 |
add | 加法 |
addslashes | 添加斜杠 |
capfirst | 首字母大写 |
center | 文本居中 |
cut | 切除字符 |
date | 日期格式化 |
default | 设置默认值 |
default_if_none | 为 None 设置默认值 |
dictsort | 字典排序 |
dictsortreversed | 字典反向排序 |
divisibleby | 整除判断 |
escape | 转义 |
escapejs | 转义 js 代码 |
filesizeformat | 文件尺寸人性化显示 |
first | 第一个元素 |
floatformat | 浮点数格式化 |
force_escape | 强制立刻转义 |
get_digit | 获取数字 |
iriencode | 转换 IRI |
join | 字符列表链接 |
last | 最后一个 |
length | 长度 |
length_is | 长度等于 |
linebreaks | 行转换 |
linebreaksbr | 行转换 |
linenumbers | 行号 |
ljust | 左对齐 |
lower | 小写 |
make_list | 分割成字符列表 |
phone2numeric | 电话号码 |
pluralize | 复数形式 |
pprint | 调试 |
random | 随机获取 |
rjust | 右对齐 |
safe | 安全确认 |
safeseq | 列表安全确认 |
slice | 切片 |
slugify | 转换成 ASCII |
stringformat | 字符串格式化 |
striptags | 去除 HTML 中的标签 |
time | 时间格式化 |
timesince | 从何时开始 |
timeuntil | 到何时多久 |
title | 所有单词首字母大写 |
truncatechars | 截断字符 |
truncatechars_html | 截断字符 |
truncatewords | 截断单词 |
truncatewords_html | 截断单词 |
unordered_list | 无序列表 |
upper | 大写 |
urlencode | 转义 url |
urlize | url 转成可点击的链接 |
urlizetrunc | urlize 的截断方式 |
wordcount | 单词计数 |
wordwrap | 单词包裹 |
yesno | 将 True,False 和 None,映射成字符串‘yes’,‘no’,‘maybe’ |
四、模板继承
Django 模版引擎中最强大也是最复杂的部分就是模版继承了。模版继承允许你创建一个包含基本“骨架”的父模版,它包含站点中的共有元素,并且可以定义能够被子模版覆盖的 blocks。
通过下面这个例子,理解模版继承的概念:
<html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>{% block title %}My amazing site{% endblock title%}title> head> <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Homea>li> <li><a href="/blog/">Bloga>li> ul> {% endblock sidebar %} div> <div id="content"> {% block content %}{% endblock content%} div> body> html>
这个模版,通常被命名为 base.html,它定义了一个可以用于两列排版页面的简单HTML 骨架。
“子模版”需要做的是先继承父模板 base.html,然后复写、填充,或者说实现其中的blocks。
block 是在子模版中可能会被覆盖掉的位置。在上面的例子中,block 标签定义了三个可以被子模版内容填充的 block,分别是 title、content 和 siderbar。
子模版可能看起来是这样的:
{% extends "base.html" %} {% block title %}My amazing blog{% endblock title%} {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}h2> <p>{{ entry.body }}p> {% endfor %} {% endblock content%}
extends 标签是这里的关键。它告诉模版引擎,这个模版“继承”了另一个模版。当模版系统处理这个模版时,首先会去加载父模版,也就是“base.html”。
加载过程中,模版引擎将注意到 base.html 中的三个 block 标签,并用子模版中的内容来替换这些 block。 根据 blog_entries 的值,最终输出可能看起来是这样的:
<html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>My amazing blogtitle> head> <body> <div id="sidebar"> <ul> <li><a href="/">Homea>li> <li><a href="/blog/">Bloga>li> ul> div> <div id="content"> <h2>Entry oneh2> <p>This is my first entry.p> <h2>Entry twoh2> <p>This is my second entry.p> div> body> html>
请注意,上面例子中的子模版并没有定义 sidebar block,这种情况下,将使用父模版中的内容。父模版的{% block %}标签中的内容总是被用作默认内容。
下面是使用继承的一些相关说明:
1.如果在模版中使用{% extends %}标签,它必须是模版中的第一个标签,必须放在文件首行!
2.在 base 模版中设置越多的{% block %}标签越好。子模版不必定义全部父模版中的 blocks,所以可以在大多数 blocks 中填充合理的默认内容,然后,只定义你需要的那一个。
3.如果发现你自己在复制大量重复的模版内容,那意味着你应该把重复的内容移动到父模版中的一个{% block %}中。
4.如果需要获取父模板中的 block 的内容,可以使用{{ block.super }}变量。如果想要在父 block 中新增内容而不是完全覆盖它,这将非常有用。
5.在{% block %}之外创建的变量使用模板标签的 as 语法,不能在块内使用。
例如,下面的模板不会显示任何内容:
{% trans "Title" as title %}
{% block content %}{{ title }}{% endblock %}
1.为了更好的可读性,可以给{% endblock %}标签一个名字,
像这样:{% block content %} ... {%endblock content %}
在大型模版中,这有助于你清楚的看到哪一个{% block %}标签被关闭了。
2.最后,请注意不能在一个模版中定义多个相同名字的 block 标签
1.创建一个 base.html 模版,用来控制整个站点的主要视觉和体验。
2.为站点的每一个 app,创建一个 base_SECTIONNAME.html 模版。 例如base_user.html,base_goods.html。这些模版都继承 base.html,并且包含了各自特有的样式和设计。
3.为每一个页面类型,创建独立的模版。 这些模版继承对应 app 的模版。上面的方式可以使代码得到最大程度的复用,并且使得添加内容到共享的内容区域更加简单,例如 app 范围内的导航条。
五、CSRF
CSRF(Cross-site request forgery)跨站请求伪造,是一种常见的网络攻击手段。
Django 为我们提供了防范 CSRF 攻击的机制。
1、基本使用
默认情况下,使用 django-admin startproject xxx 命令创建工程时,CSRF 防御机制就已经开启了。如果没有开启,请在 MIDDLEWARE 设置中添加'django.middleware.csrf.CsrfViewMiddleware'。
对于 GET 请求,一般来说没有这个问题,CSRF 通常是针对 POST 方法的!
在含有 POST 表单的模板中,需要在其