Django模板语言进阶
1.模板继承
1.1 简介
模板继承允许你建立一个基本的"骨架"模板, 它包含了网站中所有常见的元素,并定义了可以被子模板覆盖的 块(blocks) 。示例:
假如父模板base.html如下
<!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>
它定义了一个简单的 HTML 骨架文档, 假设这是一个简单的两列页面。子模板的工作就是填充空的 块(block) 中的内容。
在这个例子中, block 标签定义了三个可以被子模板填充的块。block 标签告诉了模板系统哪些地方可能被子模板覆盖。例如,子模板可能如下:
{% 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 标签, 并且使用子模板的内容替换掉这些块。根据变量 blog_entries 的值, 输出可能看起来像这样:
<!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 %} 中的内容会被作为备用的内容,在子模板没有覆盖时就会被使用。
1.2 多重继承
模板继承可以是多重继承,多重继承常见的模式是:
- 创建一个
base.html
模板把控网站的整体风格。 - 为网站的每个子分类创建一个 base_SECTIONNAME.html 模板. 比如, base_news.html, base_sports.html。 这些模板都继承 base.html 模板。这些模板中包含特定的设计/风格。
- 为每一种类型的页面创建一个模板, 比如
news
article
或blog
内容. 这些模板扩展上一级模板的相应分类。
上述的关系可以用下图表示:
这样就能最大限度的重用模板代码,比如在所有页面通用的导航栏。
1.3 模板继承注意事项
-
**{% extends %}**必须位于模板的最开始, 如果在其他的部分声明, 则不生效。
-
在基础模板尽可能多的使用**{%block%}** ,子模板不需要定义所有父模板中的块, 所以你可以在若干的块中填充默认值, 然后定义之后需要自定义的块, 有更多的可用块总是更好的。
-
如果发现自己在许多模板中有重复内容的了, 这可能需要移动这些内容到父模板的
{% block %}
中。 -
如果需要得到父模板块中的内容, 可以用
{{ block.super }}
变量。 使用{{ block.super }}
插入的数据不会被自动转义 ,因为它已经被转义了。如果需要转义, 可以在父模板中转义。 -
使用模板标签在**{% block %}**块外部创建的变量不能在块内使用。例如,这个模板不渲染任何东西:
{% trans "Title" as title %} {% block content %}{{ title }}{% endblock %}
-
为了具有更好的可读性,也可以给**{% endblock %}**块标签定义一个名字。例如:
{% block content %} ... {% endblock content %}
在一个较长的模板中, 这个方法可以让你知道是哪一个
{% block %}
标签定义结束了。 -
不能在同一模板中定义多个具有相同名称的块标签。存在这个限制是因为**{%block%}标签是"双向"定义的。也就是说, 它不仅指定了子模板要填充父模板的哪个块, 也说明了父模板要引用哪些子模板块的内容。所以在子模板中有多个同名的{%block%}**标签时, 父模板就不知道到底要引用子模板中哪个块的内容了。
2.自动HTML转义
2.1 从模板直接生成HTML存在XSS风险
从模板生成HTML时, 总是有一种风险, 即一个变量将会影响生成的HTML字符。考虑这个模板片段:
Hello, {{ name }}
看起来可能没有什么风险,但是如果用户输入的名字为一个HTML代码:
<script>alert('hello')</script>
当**{{name}}**为这个值时,模板将呈现为:
Hello, <script>alert('hello')</script>
这将使浏览器弹出一个弹出一个JavaScript警告。同样的,考虑另外一种情况:
如果名字中包含一个 '<'
符号:
<b>username
对应的模板将为:
Hello, <b>username
这意味着在此之后的文字将呈现为粗体。
由此带来一个风险:
用户提交的数据是不可靠的且不应被直接插入到您的网页, 因为恶意用户可以使用这种潜在的漏洞做危害网站的事情。 这种类型的安全漏洞被称为 Cross Site Scripting (跨站脚本) (XSS) 攻击。
2.2 使用转义避免XSS风险
(1)使用escape标签
确保让不受信任的变量经过了 escape 过滤器 , 将危险的HTML字符替换为无害的HTML转义字符。但是这常常被忽略。
(2)使用自动转义
在Django中, 默认每个模板会自动转义输出的每一个变量标签。具体来说, 这五个字符会被转义:
<
被替换为<
>
被替换为>
'
(单引号) 被替换为'
"
(双引号) 被替换为"
&
被替换为&
这种行为是默认的。如果使用的是Django的模板系统, 自然拥有这种保护。
2.3 关闭自动转义
可以在站点,模板和变量三个层级关闭自动转义。
(1)对单个变量
要为一个单独的变量禁用自动转义, 使用 safe
过滤器:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
如果变量中包含“<b>”字符,输出也是“<br>”。
(2)对于模板文本块
要在模板中控制自动转义, 可以在整个模板 (或者模板的特定区域) 使用 autoescape 标签, 如:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
autoescape 标签接受 on 或 off 作为参数。如果需要在某个区域禁用自动转义,可以这样使用:
{% autoescape off %}
This will not be auto-escaped: {{ data }}.
Nor this: {{ other_data }}
{% autoescape on %}
Auto-escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
(3)自动转义的继承特性
如果使用自动转义的模板文本块中使用**{%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 %}
因为基模板中关闭了自动转义,子模板中也将关闭自动转义,所以在HTML渲染时,greeting
变量被输出为 <b>Hello!</b>
:
<h1>This & that</h1>
<b>Hello!</b>
(4)注意事项
模板的开发者并不需要对自动转义太过关注. 这是编写Python的开发者 (写视图和写自定义模板标签的人) 需要考虑的事. 所以, 你只需要做和模板有关的活。如果你不确定模板在何时会进行自动转义(或不进行), 那么就向所有需要转义的变量添加 escape 过滤器. 当自动转义被打开, escape 过滤器不会再次转义 – escape 过滤器不会影响自动转义的变量。
(5)字符串和自动转义
过滤器的参数可以是字符串,例如:
{{ data|default:"This is a string literal." }}
作为过滤器参数的字符串都不会自动转义, django认为它们已经经过safe 过滤。 这么做的原因是模板的开发者可以控制字符的输出, 所以他们应该确保正确的使用转义后的HTML字面值。
这意味着你应该这么写:
{{ data|default:"3 < 2" }}
而不是:
{{ data|default:"3 < 2" }}
3.模板中的对象方法调用
大多数附加到对象上的方法都可以在模板中调用,这使得模板可以从视图中传递过来的上下文变量中获取对象属性之外的值。
例如, Django ORM 提供了 “entry_set” 语法寻找一个与外键所关联的对象的集合。因此, 如果有一个叫 “comment” 的模型有外键指向了模型 “task”,你可以通过给定一个实际的 task 模板变量, 像这样循环输出与它相关联所有的 comment对象:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
如果你在一个模型中显示定义了一个方法,你也可以在对应的模板变量中引用它:
模型
class Task(models.Model):
def foo(self):
return "bar"
模板调用
{{ task.foo }}
注意:由于Django有意限制了模板中语言逻辑的处理, 所以不能在模板内调用对象方法时向其传递参数。数据应当在视图中计算完成后,再传递给模板。