模板引擎

在传统的表现层MVC框架中,模板引擎是用于生成动态HTML页面的组件,也是动态网站的基础。模板引擎允许开发者将数据与页面结构分离,通过模板在视图层和表现层之间建立联系。这篇笔记我们介绍Django中模板引擎的用法。

注:随着时代的发展,“表现层MVC”和“动态网站”都已经是十分过时的概念了,以目前的业界标准来看,除非你是一个只会Python的纯菜鸟或计算机考古学家,否则无论个人小型项目还是大型企业级项目都不推荐使用老式MVC开发方式,这种开发模式心智负担高、开发缓慢、后续维护困难且不利于团队职责划分。目前Web开发在经历前后端分离后开始朝着同构CSR/SSR的路线前进,有关这些最新的技术细节可以参考Web前端/Web客户端编程/ReactWeb前端/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.html500.html等模板就可以了。不过在开发过程中,因为settings.py中配置了DEBUG = True,因此开发调试时不会看到这些定制的错误页面,而是Django的错误调试页面。

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

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。