Django模板语言详解

本节将介绍Django模版系统的语法。Django模版语言致力于在性能和简单性上取得平衡。css

若是你有过其它编程背景,或者使用过一些在HTML中直接混入程序代码的语言,那么你须要记住,Django的模版系统并非简单的将Python嵌入到HTML中。html

1、模板

模版是纯文本文件,能够生成任何基于文本的文件格式,好比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 %} 

2、变量

变量看起来就像是这样: {{ variable }}。django

当模版引擎遇到一个变量,它将从上下文context中获取这个变量的值,而后用值替换掉它自己。编程

变量的命名包括任何字母数字以及下划线("_")的组合。点(".")也有可能会在变量名中出现,不过它有特殊的含义。最重要的是,变量名称中不能有空格或标点符号api

当模版系统遇到点("."),它将以这样的顺序查询这个圆点具体表明的功能:浏览器

  • 字典查询(Dictionary lookup)
  • 属性或方法查询(Attribute or method lookup)
  • 数字索引查询(Numeric index lookup)

若是你使用的变量不存在,模版系统将插入string_if_invalid选项的值,默认设置为''(空字符串)。安全

注意,像{{ foo.bar }}这种模版表达式中的“bar”,若是在模版上下文中存在,将解释为一个字面意义的字符串而不是使用变量bar的值 。app

3、过滤器

过滤器看起来是这样的:{{ name|lower }}。使用管道符号(|)来应用过滤器。该过滤器将文本转换成小写。

过滤器能够“连接”。一个过滤器的输出应用于下一个过滤器。例如:{{ text|escape|linebreaks }}就是一个经常使用的过滤器链,它首先转移文本内容,而后把文本行转成<p>标签。

一些过滤器带有参数。 过滤器的参数看起来像是这样: {{ bio|truncatewords:30 }}。 这将显示bio变量的前30个词。

过滤器参数包含空格的话,必须用引号包起来。例如,使用逗号和空格去链接一个列表中的元素,你须要使用{{ list|join:", " }}

Django提供了大约六十个内置的模版过滤器,不少时候你想要的功能,它都已经提供了,常常查看这些过滤器,发现新大陆吧。下面是一些经常使用的模版过滤器:

1. default

为false或者空变量提供默认值,像这样:

{{ value|default:"nothing" }}

2. length

返回值的长度。它对字符串和列表都起做用。

{{ value|length }}

若是value是['a', 'b', 'c', 'd'],那么输出4。

3. filesizeformat

格式化为“人类可读”文件大小单位(即'13 KB',4.1 MB','102 bytes'等)。

{{ value|filesizeformat }}

若是value是123456789,输出将会是117.7MB。

咱们能够建立自定义的模板过滤器和标签,这是最终极的武器。

4、标签

标签看起来像是这样的: {% tag %}

标签比变量复杂得多,有些用于在输出中建立文本,有些用于控制循环或判断逻辑,有些用于加载外部信息到模板中供之后的变量使用。

一些标签须要开始和结束标签(即 {% 标签 %} ... 标签 内容 ... {% ENDTAG %})。

Django自带了大约24个内置的模版标签。下面是一些经常使用的标签:

1. for循环标签

循环对象中每一个元素。须要结束标签{% endfor %} 。例如,显示athlete_list中提供的运动员列表:

<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul> 

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是一个例外。

3. block和extends标签

继承和复写模版。相似Python的类继承和重写机制。

5、注释

要注释模版中一行的部份内容,使用注释语法:{# #}

例如,下面的模版将被渲染为'hello':

{# greeting #}hello

注释能够包含任何模版内的代码,有效的或者无效的均可以。 像这样:

{# {% if foo %}bar{% else %} #}

以上是单行注释(在{# .... #}中,不容许有新行)。

若是须要注释掉模版中的多行内容,请使用comment标签。

6、模板继承

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标签订义了三个能够被子模版内容填充的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的值,最终输出可能看起来是这样的:

<!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 %}标签中的内容老是被用做默认内容。

Django还支持多级继承!经常使用方式是相似下面的三级结构:

  • 建立一个base.html模版,用来控制整个站点的主要视觉和体验。
  • 为站点的每个app,建立一个base_SECTIONNAME.html模版。 例如base_news.html,base_sports.html。这些模版都继承base.html,而且包含了各自特有的样式和设计。
  • 为每个页面类型,建立独立的模版,例如新闻内容或者博客文章。 这些模版继承对应app的模版。

上面的方式可使代码获得最大程度的复用,而且使得添加内容到共享的内容区域更加简单,例如app范围内的导航条。

下面是使用继承的一些相关说明:

  • 若是在模版中使用{% extends %}标签,它必须是模版中的第一个标签,必须放在文件首行!

  • 在base模版中设置越多的{% block %}标签越好。子模版没必要定义所有父模版中的blocks,因此能够在大多数blocks中填充合理的默认内容,而后,只定义你须要的那一个。多一点钩子总比少一点好。

  • 若是发现你本身在复制大量重复的模版内容,那意味着你应该把重复的内容移动到父模版中的一个{% block %}中。

  • 若是须要获取父模板中的block的内容,可使用{{ block.super }}变量。若是想要在父block中新增内容而不是彻底覆盖它,这将很是有用。使用{{ block.super }} 插入的数据不会被自动转义,由于父模板中的内容已经被转义。

  • {% block %}以外建立的变量使用模板标签的as语法,不能在块内使用。

例如,下面的模板不会显示任何内容:

{% trans "Title" as title %} {% block content %}{{ title }}{% endblock %}
  • 为了更好的可读性,能够给{% endblock %}标签一个取名字,像这样:

    {% block content %} ...

在大型模版中,这有助于你清楚的看到哪个{% block %}标签被关闭了。

  • 最后,请注意不能在一个模版中定义多个相同名字的block标签。

7、自动转义HTML

当从模版中生成HTML文件时,总会存在各类风险,好比xss代码注入等恶意攻击。好比下面的模版片断:

Hello, {{ name }}

首先,它看起来像是无害的,用来显示用户的名字,可是设想一下,若是用户像下面这样输入他的名字,会发生什么:

<script>alert('hello')</script> 

使用这个名字的值,模版将会被渲染成这样:

Hello, <script>alert('hello')</script> 

这意味着浏览器会弹出一个JavaScript警报框!

相似的,若是名字包含一个 '<' 符号(好比下面这样),会发生什么呢?

<b>username

这将会致使模版被渲染成这样:

Hello, <b>username

这会致使网页的其他部分被粗体化!

显然,用户提交的数据都被不该该被盲目的信任,而且被直接插入到网页中,由于一个怀有恶意的用户可能会使用这样的漏洞来作一些坏事。 这种类型的安全问题被叫作跨站脚本攻击(Cross Site Scripting)(XSS)。

为避免这个问题,有两个选择:

  • 第一,对每一个不被信任的值运行escape过滤器,这将把潜在的有害的HTML字符转换成无害的字符串。在Django最初的几年里,这是默认的解决方案,但问题是它将责任放在开发人员/模板做者身上,以确保转义了全部内容,并且很容易忘记转义数据。
  • 第二,利用Django的自动HTML转义功能。默认状况下,Django中的每一个模板会自动转义每一个变量。也就是说,下面五个字符将被转义:

    • <会转换为&lt;
    • >会转换为&gt;
    • '(单引号)转换为&#39;
    • "(双引号)会转换为&quot;
    • &会转换为&amp;

强烈建议:将第二种功能作为默认打开的设置,不要关闭它!

可是,凡事都有正反两面。有时,模板变量含有一些你打算渲染成原始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: &lt;b&gt; 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 &amp; that{% endblock %} {% block content %}{{ greeting }}{% endblock %} 

因为自动转义标签在base模板中关闭,它也会在child模板中关闭,致使当greeting变量含有<b>Hello!</b>字符串时,会渲染HTML。

<h1>This &amp; that</h1> <b>Hello!</b> 

过滤器的字符串参数:

以前咱们展现过,过滤器的参数能够是字符串:

{{ data|default:"This is a string literal." }}

要注意,全部这种字符串参数在插入模板时都不会进行任何自动转义。缘由是,模板的做者能够控制字符串字面值的内容,因此能够确保在模板编写时文本通过正确转义。白话讲,就是,你个程序员对本身传递的参数内心要有数!

也便是说你应该这样编写:

{{ data|default:"3 &lt; 2" }}

而不是:

{{ data|default:"3 < 2" }} {# 错误的作法#}

8、方法调用

这部份内容,若是你掌握的极大提升你的模版语言能力。

大多数对象上的方法调用一样可用于模板中。这意味着模板可以访问到的不只仅是对象的属性(好比字段名称)和视图中传入的变量,还能够执行对象的方法。 例如,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的ORM操做。

9、多对多调用

对于以下的模型:

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,相似多对多。

10、使用自定义标签和过滤器

某些应用提供了自定义的标签和过滤器。想要在模板中使用它们,首先要确保该应用已经在INSTALLED_APPS 中(好比在下面的例子中,咱们添加了'django.contrib.humanize'),以后在模板中使用load标签:

{% load humanize %} {{ 45000|intcomma }}

上面的例子中, load标签加载了humanizeapp的标签库,以后咱们可使用它的intcomma过滤器。

若是你开启了django.contrib.admindocs,能够查询admin站点中的文档,查看你安装的自定义库列表。

load标签能够同时接受多个库名称,由空格分隔。 例如:

{% load humanize i18n %}

自定义库和模板继承:

当你加载一个自定义标签或过滤器库时,标签或过滤器只在当前模板中有效--并非带有模板继承关系的任何父模板或者子模版中都有效。白话说就是,你在父模板中可能加载了自定义标签,然并卵,你在子模版中还要再加载一次!

例如,若是一个模板foo.html带有{% load humanize %},子模版(例如,带有{% extends "foo.html" %})中不能访问humanize模板标签和过滤器。 子模版须要再添加本身的{% load humanize %}

这个特性是出于保持可维护性和逻辑性的目的。

相关文章
相关标签/搜索