模板引擎
在传统的表现层MVC框架中,模板引擎是用于生成动态HTML页面的组件,也是动态网站的基础。模板引擎允许开发者将数据与页面结构分离,通过模板在视图层和表现层之间建立联系。这篇笔记我们介绍Django中模板引擎的用法。
注:随着时代的发展,“表现层MVC”和“动态网站”都已经是十分过时的概念了,以目前的业界标准来看,除非你是一个只会Python的纯菜鸟或计算机考古学家,否则无论个人小型项目还是大型企业级项目都不推荐使用老式MVC开发方式,这种开发模式心智负担高、开发缓慢、后续维护困难且不利于团队职责划分。目前Web开发在经历前后端分离后开始朝着同构CSR/SSR的路线前进,有关这些最新的技术细节可以参考Web前端/Web客户端编程/React和Web前端/Web客户端编程/NextJS相关章节。
从视图到模板
前面章节我们曾使用过render()函数,它具有从视图函数传递数据到模板的功能,这里我们继续了解深入相关的细节。我们创建一个例子工程,目录结构如下。
|_demo01 # APP子模块
|_ views.py # 视图函数
|_ templates # 模板文件夹
|_ index.html
demo01/views.py
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {
'msg': 'Hello, world!'
})
视图函数index()中,我们使用render()函数返回了一个模板,它的第1个参数是请求对象;第2个参数是模板文件名,Django约定模板文件位于模块的templates文件夹下,虽然模板文件夹路径可以自定义配置,但还是建议直接按照这个约定使用;第3个参数被称为模板上下文,它是一个字典对象,其中包含要在模板中显示的数据字段。
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
{{ msg }}
</body>
</html>
模板文件中,大部分内容都是普通的HTML,但我们还使用了{{ ... }}标记,这是Django模板引擎的语法,它会被模板引擎识别并替换为对应的数据字段。
settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
在工程配置文件settings.py中我们可以找到和模板相关的配置,其中配置基本无需修改,具体配置字段的意义参考官方文档即可。
Django模板语法
Django模板具有特定的语法,这里我们简单介绍一下。
显示数据字段
Django模板中,显示数据字段使用双大括号标识。
<div>{{ msg }}</div>
实际上,被显示的内容可以是字符串,也可以是一个对象的属性,甚至可以是一个返回字符串的方法,下面是一个例子。
from django.shortcuts import render
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return 'Bark! Bark! Bark!'
def index(request):
dog = Dog('Brian')
return render(request, 'index.html', {
'dog': dog,
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
<ul>
{{ dog.name }}
{{ dog.bark }}
</ul>
</body>
</html>
代码中,Dog类有字符串类型的name属性以及返回字符串的bark()方法,模板中分别显示了这些内容。
for循环渲染
Django模板支持for循环渲染,它常用于遍历列表,下面是一个例子,代码中将列表渲染为了<ul>。
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {
'list': ['apple', 'banana', 'orange'],
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
<ul>
{% for l in list %}
<li>{{ l }}</li>
{% endfor %}
</ul>
</body>
</html>
if条件渲染
Django模板中的if条件渲染能在渲染阶段实现条件判断,下面是一个例子。
from django.shortcuts import render
def index(request):
hidden = request.GET.get('hidden')
if not hidden:
hidden = '0'
return render(request, 'index.html', {
'msg': 'I love u',
'hidden': hidden
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
<ul>
{% if hidden == '1' %}
{{ msg }}
{% endif %}
</ul>
</body>
</html>
模板代码中,只有判断hidden值为1的时候才会输出信息。
include模板包含
include指令可以在一个模板中包含另一个模板,这可以用于复用部分模板片段,实际开发中我们可以将页面的头部、页脚等区块封装为模板片段,供其它页面模板引入,下面是一个例子。
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
{% include 'header.html' %}
</body>
</html>
header.html
<div>Header</div>
代码中,index.html模板内引入了另一个模板header.html,Django会将后者的文本替换到主页模板中合并输出。
模板过滤器
Django的模板引擎支持很多内置的过滤器,他们可以用于“修饰”数据字段,下面是一个例子。
from django.shortcuts import render
from datetime import datetime
def index(request):
return render(request, 'index.html', {
'today': datetime(2018, 1, 1, 0, 0, 0)
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
{{ today|date:'Y-m-d H:i:s' }}
</body>
</html>
模板中,我们使用了date过滤器,它用于输出Y-m-d H:i:s格式日期。
除了date过滤器,另一个常用的过滤器是safe,它用于输出HTML富文本。
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {
'msg': '<b>Hello World</b>',
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index Page</title>
</head>
<body>
{{ msg|safe }}
</body>
</html>
出于安全考虑,Django模板引擎会对所有输出的文本进行HTML标签转义操作,safe过滤器能够阻止这种默认行为,让文本以HTML形式渲染出来。当然,使用safe过滤器时一定要特别小心,渲染用户随意输入的信息是绝对不行的,因为这存在明显的XSS漏洞,你必须手动保证仅有需要的标签被渲染,而不需要的标签如<script>需要被严格的排除。
引用静态资源
实际开发中,模板肯定会引用JavaScript、CSS等前端脚本和样式文件,这些内容是存于服务器上的静态文件,而非动态生成的数据。Django中有静态资源的默认存放路径,位于项目根目录/模块名/static/模块名/下,在Django模板中引入这些静态资源的方式如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
{% load static %}
<link href="{% static 'app1/bootstrap.min.css' %}" rel="stylesheet" type="text/css"/>
...
</head>
...
</html>
模板中使用static能够自动帮我们生成静态文件的引入路径,开发调试时,测试服务器也会正确的处理静态文件。但要注意,生产部署时我们还是需要在Nginx中手动配置静态文件的URL路径和文件位置STATIC_ROOT的。
实际上,我的习惯是一个工程会把所有静态文件集中存放在一个文件夹中,比如工程根目录/static/,这需要在settings.py中进行额外的配置。
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'collect_static')
STATIC_URL配置了静态文件的URL路径,由测试服务器或部署后的Nginx响应该URL,返回静态文件。
STATICFILES_DIRS配置了包含静态资源的文件夹在工程中的位置,我们编写的CSS、JS等文件都放在其中。
STATIC_ROOT指定的是部署时collectstatic的输出路径,它会将我们编写的静态资源和依赖包中的静态资源(比如admin面板)合并复制到指定文件夹中,部署时Nginx的静态资源路由需要指向这里。这个文件夹可以用绝对路径指定到项目外,因为这个路径和开发没有任何关系,只有部署时才会用到。
默认错误页
我们的应用在出错时应该返回给用户一个错误页面,Django中定义这个错误页面非常简单,直接在模板文件夹中建立404.html、500.html等模板就可以了。不过在开发过程中,因为settings.py中配置了DEBUG = True,因此开发调试时不会看到这些定制的错误页面,而是Django的错误调试页面。

当我们将其修改为DEBUG = False时,就可以看到我们定制的错误页面了。