Django-入门指南-二-

117 阅读1小时+

Django 入门指南(二)

原文:Beginning Django

协议:CC BY-NC-SA 4.0

三、Django 模板

Django 模板定义了在视图方法处理完请求后发送给最终用户的布局和最终格式。在本章中,您将学习 Django 模板使用的语法、Django 模板可用的配置选项,以及各种 Django 模板构造(例如过滤器、标签、上下文处理器),这些构造允许您创建精细的布局并将格式应用于呈现给最终用户的内容。

Django 模板语法

虽然有超过 100 个内置的构造可以帮助你构建 Django 模板——所有这些你都将在本章中学习到——但是首先,这些是你需要认识的最重要的语法元素:

  • {{output_variable}}。-开头和结尾用双花括号括起来的值表示变量的输出。Django 视图、url 选项或上下文处理器将变量传递到模板中。在模板中,您可以使用{{}}来输出变量的内容,以及使用 Python 的点符号来输出变量的更深层次的元素(例如,字段、键、方法)。例如,{{store.name}}告诉 Django 模板输出store变量的name,其中store可以是一个object,而name可以是一个字段,或者store可以是一个字典,而name可以是一个键。
  • {% tag %}。-用百分号括起来的大括号括起来的值称为标记。Django 标签提供了包装在简单语法表示中的复杂格式化逻辑。
  • variable|filter。-竖线|后声明的值称为过滤器。Django 过滤器提供了一种将格式化逻辑应用于单个变量的方法。

Django 模板中除了这三种变体之外的任何其他语法都被“照原样”处理。这意味着如果一个模板声明了超文本标记语言(HTML)标题<h1>Welcome!</h1>,用户将得到一个大的 HTML 标题。就这么简单。

但是让我们来看看一个不太明显的 Django 模板语法行为,它很重要,你必须马上理解,因为它是几乎所有与 Django 模板相关的事物中反复出现的主题。

自动转义:HTML 和安全方面的错误

Django 项目在 Web 上运行,所以默认情况下所有模板都被假定为产生 HTML。虽然这是一个合理的假设,但它并不合理,除非你面临以下两种情况之一:

  • 你不能确保 Django 模板产生有效的 HTML,事实上可能会产生危险的标记。
  • 您希望 Django 模板生成非 HTML 内容,比如逗号分隔值(CSV)、可扩展标记语言(XML)或 JavaScript 对象符号(JSON)。

那么,您怎么可能在 Django 模板中引入无效的 HTML 甚至危险的内容呢?这是互联网,来自其他用户或提供商的内容可能会出现在您的 Django 模板中,从而导致问题(例如,用户提交的数据、第三方服务、来自数据库的内容)。

问题不在于你直接放在 Django 模板中的内容——因为你输入的内容是有效的——问题在于通过变量、标签、过滤器和上下文处理器放置的动态内容,它有可能来自任何地方。让我们使用以下变量对此进行进一步分析:

store_legend = "<b>Open since 1965!</b>"

js_user_date = "<script>var user_date = new Date()</script>"

如果带有这种内容的变量进入 Django 模板,并且您试图输出它们,它们会被逐字输出。store_legend不会作为 HTML 粗体语句输出,而是由<b></b>包围的语句。类似地,js_user_date不会产生带有用户浏览器本地日期的 JavaScript 变量,而是逐字输出<script>语句。

这是因为在默认情况下,Django 会自动转义动态结构(即变量、标签、过滤器和上下文处理器)中的内容。表 3-1 显示了字符 Django 默认自动转义。

表 3-1。

Characters Django auto-escapes by default

| 原始字符 | 逃到 | | --- | --- | | < | < | | > | > | | (单引号) | ' | | “(双引号) | " | | & | & |

正如您在表 3-1 中看到的,Django 自动转义包括将潜在冲突甚至危险的字符——在 HTML 的上下文中——转换成等价的可视表示,也称为转义字符。 1

之所以这样做,是因为恶意用户或未经检查的来源可以轻松生成包含表 3-1 左列字符的内容,这可能会破坏用户界面或执行恶意 JavaScript 代码。所以 Django 为了安全起见会出错,并自动将表 3-1 中的字符换成等价的可视化表示。虽然你当然可以禁用表 3-1 中字符的自动转义,但这必须显式完成,因为这代表着安全风险。

虽然自动转义对于 HTML 输出来说是一个很好的安全预防措施,但这将我们带到假设 Django 总是生成 HTML 的第二点。如果 Django 模板必须输出 CSV、JSON 或 XML 内容,而像<>'(单引号)、"(双引号)和&这样的字符对内容消费者有特殊的意义,并且不能使用等效的视觉表示,那么会发生什么呢?在这种情况下,您还需要显式禁用 Django 强制的默认自动转义行为。

因此,无论您想通过 Django 模板中的变量输出实际的 HTML,还是输出 CSV、JSON 或 XML,而 Django 没有对这些内容应用 HTML 安全实践,您都需要处理 Django 自动转义。

在 Django 模板中有各种方法来控制自动转义(例如,全局地,单独的变量,单独的过滤器),你将在本章的学习中了解到这些。但是自动转义是 Django 模板中的一个永恒主题,还有以下相关术语:

  • 安全。-如果 Django 模板构造被标记为安全,这意味着表 3-1 中没有字符被转义。换句话说,安全等于“我知道我在做什么”输出内容“原样”。
  • 逃跑。-如果 Django 模板构造被标记为转义,这意味着表 3-1 中的字符被转义。换句话说,escape 等于“确保没有潜在危险的 HTML 字符被输出,使用等效的可视化表示。”
  • 自动逃生开/自动逃生关(安全)。-如果 Django 模板使用 auto-escape on,这意味着该范围内的 Django 模板构造应该对表 3-1 中的字符进行转义。如果 Django 模板使用自动转义关闭,这意味着该范围内的 Django 模板构造应该“按原样”输出,而不是对表 3-1 中的字符进行转义。

就这样,我们结束了关于 Django 自动逃脱这个相当枯燥但重要的话题的谈话。接下来,让我们探索 Django 模板的各种配置选项。

Django 模板配置

默认情况下,由于settings.py中的TEMPLATES变量,所有 Django 项目上都启用了 Django 模板。清单 3-1 展示了 Django 项目中的默认TEMPLATES值。

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',
            ],
        },
    },
]
Listing 3-1.Default Django template configuration in settings.py

BACKEND变量表示项目使用 Django 模板。DIRSAPP_DIRS变量告诉 Django 在哪里定位 Django 模板,这将在下一节解释。OPTIONS中的context_processors字段告诉 Django 为 Django 项目启用哪些上下文处理器。简而言之,上下文处理器提供了跨所有 Django 模板共享数据的能力,而不需要在 Django 视图中以零碎的方式定义它。

本章后面的部分描述了默认 Django 上下文处理器提供的数据,以及如何编写自己的上下文处理器来共享所有 Django 模板上的定制数据。

模板搜索路径

Django 根据变量DIRSAPP_DIRS中的值决定在哪里寻找模板。正如您在清单 3-1 中看到的,Django 默认为空的DIRS值,并将APP_DIRS变量设置为真。

设置为TrueAPP_DIRS变量告诉 Django 在名为templates的 Django 应用子文件夹中寻找模板——如果你从未听说过 Django 应用的概念,请看第一章,它描述了这个概念。

APP_DIRS行为有助于将应用的模板包含到应用的结构中,但请注意,模板搜索路径不知道应用的名称空间。例如,如果你有两个应用都依赖于一个名为index.html的模板,并且这两个应用在views.py中都有一个将控制权返回给index.html模板的方法(例如render(request,'index.html'),那么这两个应用都将使用INSTALLED_APPS中最顶层声明的应用的index.html,因此只有一个应用将使用预期的index.html

清单 3-2 中展示的第一组文件夹显示了两个 Django 应用,它们具有这种潜在的模板布局冲突。

# Templates directly under templates folder can cause loading conflicts
+---+-<PROJECT_DIR_project_name_conflict>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-templates-+
    |                        |
    |                        +-index.html
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-templates-+
                             |
                             +-index.html

# Templates classified with additional namespace avoid loading conflicts
+---+-<PROJECT_DIR_project_name_namespace>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-templates-+
    |                        |
    |                        +-about-+
    |                                |
    |                                +-index.html
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-templates-+
                             |
                             +-stores-+
                                      |
                                      +-index.html

Listing 3-2.Django apps with templates dirs with potential conflict and namespace qualification

为了解决这个潜在的模板搜索冲突,推荐的做法是在每个templates目录中添加一个额外的子文件夹作为名称空间,如清单 3-2 中的第二组文件夹所示。

通过这种方式,您可以使用这个额外的命名空间子文件夹将控件重定向到模板,以避免任何歧义。因此,要将控制权发送给about/index.html模板,您应该声明render(request,'about/index.html'),要将控制权发送给stores/index.html,您应该声明render(request,'stores/index.html')

如果您希望禁止这种允许从这些内部应用子文件夹加载模板的行为,您可以通过将APP_DIRS设置为FALSE来实现。

定义 Django 模板的一个更常见的方法是在应用结构之外建立一个或多个文件夹来保存 Django 模板。为了让 Django 找到这样的模板,可以使用清单 3-3 中所示的DIRS变量。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),
                 '%s/dev_templates/' % (PROJECT_DIR),],
        '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',
            ],
        },
    },
]

Listing 3-3.
DIRS definition

with relative path in settings.py

正如您在清单 3-3 中看到的,您可以在DIRS变量中声明各种目录。Django 在DIRS值中寻找模板,然后在应用的templates文件夹中寻找模板——如果APP_DIRSTRUE——直到找到匹配的模板或者抛出TemplateDoesNotExist错误。

还要注意清单 3-3 中的DIRS值依赖于由PROJECT_DIR变量动态确定的路径。当您在不同的机器上部署 Django 项目时,这种方法很有帮助,因为该路径是相对于顶级 Django 项目目录的(即,settings.py和主urls.py文件所在的位置),并且不管 Django 项目安装在哪里(例如,/var/www//opt/website/C://website/),该路径都会动态调整。

无效的模板变量

默认情况下,Django 模板在包含无效变量时不会抛出错误。这是因为与 Django admin 相关的设计选择也使用了 Django 模板。

虽然在大多数情况下这不是一个大问题,但是对于调试任务来说,这可能会令人沮丧,因为 Django 不会通知您拼写错误或未定义的变量。例如,您可以输入{{datee}}而不是{{date}},Django 通过输出一个空字符串''来忽略这一点,您也可以忘记在视图方法中将一个变量值传递给一个模板,Django 也静默地输出一个空字符串'',即使您可能已经在模板中定义了它。

要让 Django 在遇到 Django 模板中的无效变量时通知您,您可以使用string_if_invalid选项。清单 3-4 中显示的string_if_invalid的第一个配置选项输出一个可见字符串,而不是空字符串''

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),'%s/dev_templates/' % (PROJECT_DIR),],
        'APP_DIRS': True,
        'OPTIONS': {
            'string_if_invalid': "**** WARNING INVALID VARIABLE %s ****",
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Listing 3-4.
Output warning message

for invalid template variables with string_if_invalid

正如您在清单 3-4 中看到的,string_if_invalid被赋予了字符串"**** WARNING INVALID VARIABLE %s ****"。当 Django 遇到一个无效变量时,它用这个字符串替换出现的变量,其中的%s变量被替换为无效的变量名,这让您可以很容易地定位哪里和哪些变量是无效的。

string_if_invalid选项的另一个配置选项是在遇到无效变量时执行更复杂的逻辑。例如,清单 3-5 展示了在发现无效变量的情况下,如何引发错误以使模板无法呈现。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

class InvalidTemplateVariable(str):
    def __mod__(self,other):
        from django.template.base import TemplateSyntaxError
        raise TemplateSyntaxError("Invalid variable : '%s'" % other)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),'%s/dev_templates/' % (PROJECT_DIR),],
        'APP_DIRS': True,
        'OPTIONS': {
            'string_if_invalid': InvalidTemplateVariable("%s"),
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Listing 3-5.
Error generation

for invalid template variables with string_if_invalid

在清单中,3-5 string_if_invalid被赋予使用%s输入变量的InvalidTemplateVariable类,它代表无效的变量名——就像清单 3-4 中的前一个例子一样。

InvalidTemplateVariable类很有趣,因为它继承了str(string)类的行为,并使用了 Python __mod__(模)魔法方法实现。虽然__mod__(模)魔术方法适合数字运算,但在这种情况下,它很有用,因为传入的字符串使用了%(模)符号,这使得__mod__方法运行。在__mod__方法中,我们只是用无效的变量名引发TemplateSyntaxError错误来暂停模板的执行。

Caution

Django admin 可能会被自定义的string_if_invalid值破坏。

由于某些显示的复杂程度,Django 管理模板特别依赖默认的string_if_invalid输出空字符串''。事实上,这种string_if_invalid默认行为通常被认为是一种“特性”,就像它被认为是一种“缺陷”或“烦恼”一样

因此,如果您使用清单 3-4 或清单 3-5 中的一种方法来覆盖string_if_invalid,请注意您很可能会损坏或中断 Django 管理页面。如果您依赖 Django admin,那么您应该只使用这些技术来调试项目的模板。

调试输出

当您使用顶级DEBUG=True设置运行 Django 项目并出现错误时,Django 模板会输出一个非常详细的页面,以使调试过程更容易——参见第五章了解关于DEBUG变量的更多细节,特别是“Django settings.py用于真实世界”一节

默认情况下,Django 模板重用顶级的DEBUG变量值来配置模板调试活动。在幕后,这个配置是通过TEMPLATES变量的OPTIONS内的调试字段设置的。图 3-1 说明了DEBUG=True时错误页面的样子。

A441241_1_En_3_Fig1_HTML.jpg

图 3-1。

Django error page when DEBUG=True automatically sets template OPTION to ‘debug’:True

正如你在图 3-1 中看到的,Django 打印了模板的位置,以及模板本身的一个片段,以便于定位错误。该模板信息是由'debug':True选项生成的,该选项是基于顶级DEBUG变量设置的。但是,您可以显式地将调试选项设置为False,如清单 3-6 所示,在这种情况下,错误页面将没有任何模板细节,只有回溯信息,如图 3-2 所示。

A441241_1_En_3_Fig2_HTML.jpg

图 3-2。

Django error page when DEBUG=True and explicit OPTION ‘debug’:False

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),'%s/dev_templates/' % (PROJECT_DIR),],
        'APP_DIRS': True,
        'OPTIONS': {
            'debug':False,
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Listing 3-6.Option with debug equals False omits template details

自动退出

默认情况下,Django 模板使用安全惯例来自动转义某些字符——如表 3-1 所述——包含在动态生成的结构中(例如变量、标签、过滤器)。自动转义将可能破坏用户界面或产生危险结果的字符转换成安全的表示形式,这个过程在本章的第一节中有所描述。

然而,你可以在所有 Django 模板上全局禁用自动转义——并且故意渲染< as as >等...-OPTIONS中的autoescape字段如清单 3-7 所示。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),'%s/dev_templates/' % (PROJECT_DIR),],
        'APP_DIRS': True,
        'OPTIONS': {
            'autoescape':False,
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Listing 3-7.Option with auto-escape equals False omits auto-escaping on all Django templates

值得一提的是,在每个 Django 模板上禁用自动转义的另一种方法——如清单 3-7 所示——是有选择地禁用自动转义。您可以使用{% autoescape off %}标签来禁用 Django 模板的一部分的自动转义,或者使用safe过滤器来禁用单个 Django 模板变量的自动转义。

如果你决定禁用所有 Django 模板的自动转义,如清单 3-7 所示——坦白地说,如果你打算只使用 HTML,由于潜在的安全风险,我不建议这样做——如果需要,你也可以再次精确地启用自动转义。您可以使用{% autoescape on %}标签对 Django 模板的一部分启用自动转义,或者使用escape过滤器对单个 Django 模板变量进行转义。

文件字符集

Python 项目中使用的文件通常在顶部声明一个基于 Python PEP-263 规范的编码值(如# -*- coding: utf-8 -*-),2,以确保文件中的字符被正确解释。在 Django 模板中,你不用这种方式定义底层文件的编码,而是在项目的settings.py文件中定义。

有两种方法可以声明 Django 模板的编码字符:显式地作为TEMPLATES变量中OPTIONSfile_charset字段的一部分,或者通过settings.py中的顶级FILE_CHARSET变量。在OPTIONS中的file_charset中的显式声明优先于FILE_CHARSET赋值,但是file_charset的值默认为FILE_CHARSET,其本身默认为 utf-8 (Unicode)编码。

所以默认情况下,Django 模板编码被指定为 utf-8 或 Unicode,这是软件中使用最广泛的编码之一。尽管如此,如果您决定将数据合并到与 utf-8 不兼容的 Django 模板中(例如,带有重音符号的西班牙元音,如编码为 ISO-8859-1 的á或é,或日本汉字字符,如漢或者字编码为 JIS)您必须在项目的settings.py文件中定义FILE_CHARSET值——或者直接在TEMPLATESOPTIONSfile_charset字段中定义——这样 Django 模板数据才能被正确解释。

Django 模板可以被赋予 Python 标准编码值中的任何编码值。 3

自动访问定制模板标签/过滤器模块

Django 模板可以访问一系列内置的标签和过滤器,不需要任何设置步骤。但是,如果您计划使用第三方模板标签/过滤器模块或者编写自己的模板标签/过滤器模块,那么您需要在每个 Django 模板上设置带有{% load %}标签(例如{% load really_useful_tags_and_filters %})的访问,如果您需要访问几十或几百个模板上的特定标签/过滤器,这个过程可能会很烦人。

要自动访问第三方模板标签/过滤器或您自己的模板标签/过滤器,就像它们是内置标签/过滤器一样(即,不需要{% load %}标签),您可以使用OPTIONS中的builtins字段,如清单 3-8 所示。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),'%s/dev_templates/' % (PROJECT_DIR),],
        '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',
            ],
            'builtins': [
                 'coffeehouse.builtins',
                 'thirdpartyapp.customtags.really_useful_tags_and_filters',
            ],
        },
    },
]

Listing 3-8.Option with builtins to gain automatic access to tags/filters on all templates

正如您在清单 3-8 中看到的,builtins字段接受一个模块列表,该列表包含用于内置处理的标签/过滤器。在这种情况下,coffeehouse.builtins代表一个名为coffeehouse的项目下的builtins.py文件——它包含定制的标签/过滤器。而thirdpartyapp.customtags.really_useful_tags_and_filters是一个带有标签/过滤器的第三方包,我们也想在 Django 模板中访问它,而不需要使用{% load %}标签。

第三方模板标签/过滤器模块和自定义模板标签/过滤器模块的另一个默认行为是,它们需要使用其原始标签/名称作为参考,而后者还需要将其放在注册的 Django 应用中名为templatetags的文件夹内。这两个默认行为可以用OPTIONS中的libraries字段覆盖,如清单 3-9 所示。

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['%s/templates/' % (PROJECT_DIR),'%s/dev_templates/' % (PROJECT_DIR),],
        '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',
            ],
            'libraries': {
                 'coffeehouse_tags': 'coffeehouse.tags_filters.common',
            },
        },
    },
]

Listing 3-9.Option with libraries to register tags/filters with alternative label/name and under any project directory

清单 3-9 'coffeehouse_tags': 'coffeehouse.tags_filters.common'中的 libraries 语句告诉 Django 从coffeehouse项目的tags_filters文件夹中加载common.py文件——包括定制标签/过滤器——并通过coffeehouse_tags引用(例如{% load coffeehouse_tags %})使模板可以访问它。使用清单 3-9 中的方法,您可以在 Django 项目中的任何地方放置定制的标签/过滤器模块,并且为定制的标签/过滤器模块——或者第三方标签/过滤器模块——分配一个替代的参考值,而不是它们原来的标签/名称。

模板加载器

在前面的“模板搜索路径”一节中,我描述了 Django 如何使用DIRSAPP_DIRS变量搜索模板,这是 Django 模板配置的一部分。然而,我有意忽略了与这个模板搜索过程相关的一个更深层的方面:每个搜索机制都由一个模板加载器支持。

模板加载器是一个 Python 类,它实现了搜索和加载模板所需的实际逻辑。表 3-2 展示了 Django 中可用的内置模板加载器。

表 3-2。

Built-in Django template loaders

| 模板加载器类 | 描述 | | --- | --- | | django . template . loaders . file system . loader | 在 DIRS 变量中声明的目录中搜索并加载模板。当 DIRS 不为空时,默认启用。 | | django . template . loaders . app _ directory。装货设备 | 从 INSTALLED_APPS 中声明的所有应用中名为 templates 的子目录中搜索并加载模板。当 APP_DIRS 为真时,默认情况下启用。 | | django . template . loaders . cached . loader | 从文件系统或应用目录加载程序加载模板后,从内存缓存中搜索模板。 | | django . template . Loader . location mem . loader .模板. loader | 从 Python 字典加载模板后,从内存缓存中搜索模板。 |

正如你所看到的,表 3-2 中的两个 Django 模板加载器是由DIRSAPP_DIRS变量自动设置的。然而,表 3-2 中的任何模板加载器都可以使用TEMPLATESOPTIONS中的loaders字段进行明确设置。

创建可重复使用的模板

模板倾向于具有在多个实例中同等使用的公共部分。例如,无论一个项目有 5 个还是 100 个模板,所有模板的页眉和页脚部分很少改变。其他模板部分,如菜单和广告,也属于这种在多个模板中保持不变的内容类别。所有这些都会导致多个模板的重复,这可以通过创建可重用的模板来避免。

使用可重用的 Django 模板,您可以在单独的模板上定义公共部分,并在其他模板中重用它们。此过程使创建和管理项目模板变得容易,因为单个模板更新会影响所有模板。

可重用的 Django 模板还允许您定义页面块来逐页覆盖内容。此过程使项目的模板更加模块化,因为您定义了顶级块来建立整体布局并逐页定义内容。

让我们朝着构建可重用的 Django 模板迈出第一步,探索 Django 内置的{% block %}标签。清单 3-10 展示了一个名为base.html的模板的第一行,带有几个{% block %}标签。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>{% block title%}Default title{% endblock title %}</title>
    <meta name="description" content="{% block metadescription%}{% endblock metadescription %}">
    <meta name="keywords" content="{% block metakeywords%}{% endblock metakeywords %}">
Listing 3-10.Django template with {% block %} tags

注意清单 3-10 中的语法{% block <name>%}{% endblock <name> %}。每个{% block %}标签都有一个引用名。其他 Django 模板使用引用名来覆盖每个块的内容。

例如,HTML <title>标签中的{% block title %}标签定义了一个网页标题。如果另一个模板重用清单 3-10 中的模板,它可以通过覆盖标题块来定义自己的网页标题。如果未在模板上覆盖块,则块将接收块中的默认内容。对于title块,默认内容是Default title,对于metadescriptionmetakeywords块,默认内容是一个空字符串。

清单 3-10 中所示的相同机制可用于定义任意数量的块(例如,内容、菜单、页眉、页脚)。值得一提的是{% endblock <name> %}<name>参数是可选的,仅使用{% endblock %}来结束一个 block 语句是有效的;但是,前一种技术使得 block 语句的结束位置更加清晰,这在一个模板有多个块时尤其有用。

尽管可以通过 Django 视图方法或 url 请求直接调用清单 3-10 中的模板,但这种模板的目的是将其用作其他模板的基础模板。要重用 Django 模板,可以使用 Django 内置的{% extends %}标签。

{% extends %}标签使用语法{% extends <name> %}来重用另一个模板的布局。这意味着为了重用在文件base.html中定义的清单 3-10 中的布局,您使用语法{% extends "base.html" %}。此外,如果使用{% extends %}标签,它必须是 Django 模板中的第一个定义,如清单 3-11 所示。

{% extends "base.html" %}
{% block title %}Coffeehouse home page{% endblock title %}
Listing 3-11.Django template with {% extends %} and {% block %} tag

Tip

在{% extend %}标记语句中,值也可以使用相对路径(例如,“../base.html”),以及由视图传递的变量,该变量可以是字符串(例如,“master.html”)或视图中加载的模板对象。

注意清单 3-11 中第一个模板语句是如何{% extends "base.html" %}的。此外,注意清单 3-11 是如何用内容Coffeehouse home page定义{% block title %}标签的。清单 3-11 中的块覆盖了base.html模板中的标题栏。那么清单 3-11 中的 HTML <title>标签在哪里呢?没有,你也不需要。Django 自动重用来自base.html模板的布局,并在必要的地方替换块内容。

重用其他模板的 Django 模板倾向于使用有限的布局元素(例如 HTML 标签)和更多的 Django block语句来覆盖内容。这是有益的,因为正如我前面所概述的,它允许您一次建立整体布局,并在逐页的基础上定义内容。

Django 模板的可重用性可以多次出现。例如,您可以拥有模板 A、B 和 C,其中 B 要求重用 A,但是 C 要求重用 B 的一部分,唯一的区别是模板 C 需要使用{% extends "B" %}标签而不是{% extends "A"%}标签。但是由于模板 B 重用了 A,模板 C 也可以访问模板 A 中的相同元素。

当重用 Django 模板时,也可以从父模板访问块内容。Django 通过引用block.super公开父模板中的块内容。清单 3-12 展示了三个模板,展示了包含网页路径或“面包屑”的块的这种机制

# base.html template
<p>{% block breadcrumb %}Home{% endblock breadcrumb %}</p>

# index.html template
{% extends "base.html" %}
{% block breadcrumb %}Main{% endblock breadcrumb %}

# detail.html template
{% extends "index.html" %}
{% block breadcrumb %} {{block.super}} : Detail {% endblock breadcrumb %}

Listing 3-12.Django templates use of {{block.super}} with three reusable templates

清单 3-12 中的base.html模板用默认值Home定义了breadcrumb块。接下来,index.html模板重用base.html模板并用值Main覆盖breadcrumb块。最后,detail.html模板重用index.html模板并覆盖breadcrumb块值。但是,请注意最后一个块覆盖中的{{block.super}}语句。因为{{block.super}}breadcrumb块中,{{block.super}}告诉 Django 从父模板块中获取内容。

Django 模板中的另一个可重用功能是将一个 Django 模板包含在另一个 Django 模板中。Django 通过{% include %}标签支持这个功能。

{% include %}标签需要一个模板参数——类似于{% extend %}标签——它可以是硬编码的字符串引用(如{% include "footer.html" %})、模板的相对路径(如{% include "../header.html" %}),或者是视图传递的变量,可以是视图中加载的字符串或模板对象。

声明为{% include %}标签一部分的模板知道声明它们的模板中的上下文变量。这意味着如果模板 A 使用了{% include "footer.html" %}标签,模板 A 的变量就会自动地为footer.html模板所用。

包含性地,可以使用with关键字显式地为{% include %}语句提供上下文变量。例如,{% include "footer.html" with year="2013" %}语句使得year变量可以在footer.html模板中访问。{% include %}标签还支持使用with符号传递多个变量的能力(例如{% include "footer.html" with year="2013" copyright="Creative Commons" %})。

最后,如果您希望声明为{% include %}标签一部分的模板能够限制声明它们的模板对上下文变量的访问,那么您可以使用only关键字。例如,如果模板 B 使用{% include "footer.html" with year="2013" only %}语句,那么footer.html模板只能访问year变量,而不考虑模板 B 中可用的变量。类似地,{% include "footer.html" only %}语句将footer.html模板限制为没有变量,而不考虑使用该语句的模板中可用的变量。

内置上下文处理器

默认情况下,Django 模板可以访问各种变量。这消除了在每个 Django 视图方法中不断声明广泛使用的变量或作为 url 额外选项的需要。这些变量通过模板上下文处理器变得可用。

Django 模板上下文处理器在项目的settings.py文件中明确定义,在OPTIONS键内的TEMPLATES变量中。默认情况下,如清单 3-1 所示,Django 项目通过内置于 Django 的四个上下文处理器来启用。接下来,我将描述每个上下文处理器提供的数据变量。

Django 调试上下文处理器(django . template . context _ processors . debug)

Django 调试上下文处理器公开了有助于调试的变量。这个上下文处理器使得以下变量在所有 Django 模板上都可用:

  • debug。-基于settings.py文件中的DEBUG变量,包含真或假。
  • sql_queries。-包含由支持方法视图运行的数据库连接详细信息(例如,SQL 语句)。

Note

只有在 settings.py 中的 INTERNAL_IPS 变量中定义了请求 IP 地址时,Django 调试上下文处理器才会显示变量值。即使变量是在模板中声明的(例如{{debug}}或{{sql_queries}}),这种限制也只允许某些用户查看模板中的调试消息,而其他用户不会查看任何内容。

例如,要查看本地工作站上的 debug 和 sql_queries 值,请将 INTERNAL_IPS = ['127.0.0.1']添加到 settings.py 文件中。这告诉 Django 为来自 IP 地址 127.0.0.1 的请求显示这些变量值。

Django 请求上下文处理器(django . template . context _ processors . request)

Django 请求上下文处理器公开与请求(即 HTTP 请求)相关的变量。这个上下文处理器通过一个名为request的大型字典提供数据,该字典包括以下一些键值:

  • request.GET。-包含请求的 HTTP GET 参数。
  • request.POST。-包含请求的 HTTP POST 参数。
  • request.COOKIES。-包含请求的 HTTP COOKIES。
  • request.CONTENT_TYPE。-包含请求的 HTTP 内容类型标头。
  • request.META。-包含请求的 HTTP 元数据。
  • request.REMOTE_ADDR。-包含请求的 HTTP 远程地址。

Django 授权上下文处理器(django . contrib . auth . context _ processors . auth)

Django 身份验证上下文处理器公开了与身份验证逻辑相关的变量。这个上下文处理器使得 Django 模板中的以下变量可以访问:

  • user。-包含用户数据(例如,id、姓名、电子邮件、匿名用户)。
  • perms。-包含用户应用权限(例如,用户在django.contrib.auth.context_processors.PermWrapper对象中可以访问的真、假或显式应用权限)。

Django 消息上下文处理器(django . contrib . messages . context _ processors . messages)

Django 消息上下文处理器公开与 Django 消息框架相关的变量,在第二章中介绍。消息在 Django 视图方法中添加到消息框架中,然后在 Django 模板中公开。这个上下文处理器使得 Django 模板中的以下变量可以访问:

  • messages。-包含通过 Django 视图方法中的 Django 消息框架添加的消息。
  • DEFAULT_MESSAGE_LEVELS。-包含消息级别名称到其数值的映射(如{'DEBUG': 10, 'INFO': 20, 'WARNING': 30, 'SUCCESS': 25, 'ERROR': 40})。

其他内置的 Django 上下文处理器:i18n、媒体、静态、tz 和 CSRF 上下文处理器

之前的上下文处理器提供了所有 Django 项目模板所需的一些最常见的数据,这也是它们默认启用的原因。然而,这并不意味着它们是唯一内置的 Django 上下文处理器。事实上,还有五个内置的上下文处理器可以用来访问所有 Django 模板中的某些数据。

Django i18n 上下文处理器(django . template . context _ processors . i18n)

Django i18n 上下文处理器公开了与国际化逻辑相关的变量。这个上下文处理器使得 Django 模板中的以下变量可以访问:

  • LANGUAGES。-包含 Django 项目的可用语言。
  • LANGUAGE_CODE。-包含项目语言代码,基于settings.py文件中的LANGUAGE_CODE变量。
  • LANGUAGE_BIDI。-包含当前项目语言方向。对于从左到右的语言(例如,英语、法语、德语),它被设置为False;对于从右到左的语言(例如,希伯来语、阿拉伯语),它被设置为True
Django 媒体上下文处理器(django . template . context _ processors . media)

Django 媒体上下文处理器公开了一个与媒体资源相关的变量。这个上下文处理器使得 Django 模板中的以下变量是可访问的:

  • MEDIA_URL。-包含媒体 url,基于settings.py文件中的MEDIA_URL变量。
Django 静态上下文处理器(django . template . context _ processors . static)

Django 静态上下文处理器公开了一个与静态资源相关的变量。这个上下文处理器使得 Django 模板中的以下变量是可访问的:

  • STATIC_URL。-包含静态 url,基于settings.py文件中的STATIC_URL变量。

Tip

尽管静态上下文处理器是可访问的(也就是说,它没有被弃用),但它的功能已经过时,应该避免使用。您应该改用 staticfiles 应用。更多细节在第五章设置静态网页资源(图片、CSS、JavaScript)一节中提供。

Django tz 上下文处理器(django . template . context _ processors . tz)

Django tz 上下文处理器公开了一个与项目时区相关的变量。这个上下文处理器使得 Django 模板中的以下变量是可访问的:

  • TIME_ZONE。-包含项目的时区,基于settings.py文件中的TIME_ZONE变量。
Django CSRF 上下文处理器(django . template . context _ processors . csrf)

跨站点请求伪造(CSRF)上下文处理器将csrf_token变量添加到所有请求中。这个变量被{% csrf_token %}模板标签用来防止跨站点请求伪造。

尽管您可以访问任何 Django 模板中的csrf_token变量值,但是您几乎不需要(如果有的话)直接公开它,因为它被用作检测伪造请求的安全机制。第六章,涵盖了 Django 表单的主题,描述了它是什么以及 CSRF 如何与 Django 一起工作。

由于让csrf_token变量在所有请求上可用的安全重要性,CSRF 上下文处理器总是被启用——不管OPTIONS中的context_processors列表如何——并且不能被禁用。

自定义上下文处理器

当您在 view methods 或 url extra 选项中设置数据时,您这样做是为了访问各个 Django 模板上的数据。定制的 Django 上下文处理器允许您在所有 Django 模板上设置访问数据。

Django 自定义上下文处理器的结构就像一个常规的 Python 方法,带有一个返回字典的HttpRequest对象参数。上下文处理器的返回字典关键字表示模板引用和可在模板中访问的字典值数据对象(例如,字符串、列表、字典)。清单 3-13 展示了一个定制的 Django 上下文处理器方法。

def onsale(request):
    # Create fixed data structures to pass to template
    # data could equally come from database queries
    # web services or social APIs
    sale_items = {'Monday':'Mocha 2x1','Tuesday':'Latte 2x1'}
    return {'SALE_ITEMS': sale_items}
Listing 3-13.Custom Django context processor method

正如您在清单 3-13 中看到的,onsale方法有一个request参数——代表一个HttpRequest对象——并返回一个字典。本例中的字典有一个名为SALE_ITEMS的键和一个硬编码字典值。

然而,正如您可以在 Django 视图方法或 url 选项中设置任何类型的数据以传递给模板一样,自定义 Django 上下文处理器方法也可以从请求参数(例如,cookie、远程 IP 地址)中访问数据,甚至查询数据库并使这些数据对所有模板可用。

自定义上下文处理器方法可以放在任何项目文件或目录中。位置和命名约定并不重要,因为 Django 通过项目的settings.py文件中的TEMPLATES变量的OPTIONS中的context_processors变量来检测上下文处理器。我将把清单 3-13 中的上下文处理器方法放在stores app 子目录中一个名为processors.py的文件中。

一旦保存了自定义上下文处理器方法,就必须配置 Django 来定位它。清单 3-14 显示了context_processors变量的更新,包括了来自清单 3-13 的自定义上下文处理器方法。

'OPTIONS': {
    'context_processors': [
        'coffeehouse.stores.processors.onsale',
        'django.template.context_processors.debug',
        'django.template.context_processors.request',
        'django.contrib.auth.context_processors.auth',
        'django.contrib.messages.context_processors.messages',
    ],
}
Listing 3-14.Django template context processor definitions in context_processors in OPTIONS of TEMPLATES

在清单 3-14 中,您可以看到coffeehouse.stores.processors.onsale声明,其中coffeehouse.stores表示 package.app 名称,processors是包含自定义上下文处理器的文件(即,stores 应用中的processors.py),而onsale是包含自定义上下文处理器逻辑的实际方法。

一旦在项目的settings.py文件中声明了上下文处理器,带有清单 3-13 中的SALE_ITEMS键的定制字典就可以用于所有的 Django 模板。

内置 Django 过滤器

Django 过滤器设计用于格式化模板变量。应用 Django 过滤器的语法是竖线字符|,在 Unix 环境中也称为“管道”(例如{{variable|filter}})。值得一提的是,可以在同一个变量上使用多个过滤器(例如,{{variable|filter|filter}})。

我将把每个内置的 Django 过滤器分成不同的功能部分,这样更容易识别它们。我将使用的功能类是日期、字符串、列表、数字、字典、空格和特殊字符、开发、测试和 URL。

Tip

您可以使用{% filter %}标签将 Django 过滤器应用于整个部分。如果在同一个部分中有一组变量,并且希望对所有变量应用相同的过滤器,那么使用{% filter %}标记比在每个变量上单独声明过滤器更容易。本章关于 Django 内置标签的下一节提供了关于{% filter %}标签的更多细节

日期

  • date。-date过滤器格式化 Python datetime对象,并且仅当变量是这种类型的 Python 对象时才起作用。date过滤器使用一个字符串来指定格式。例如,如果一个变量包含一个日期为 01/01/2018 的datetime对象,过滤器语句{{variable|date:"F jS o"}}输出 2018 年 1 月 1 日。日期过滤器的字符串语法基于表 3-3 中描述的字符。

Tip

如果没有为日期筛选器提供字符串参数(例如{{variable|date}}),则默认为“N j,Y”字符串,该字符串来自 DATE_FORMAT 的默认值。

Note

日期筛选器还可以接受预定义的日期变量{ { variable | DATE:" DATE _ FORMAT " } } 、{ { variable | DATE:" DATETIME _ FORMAT " }、{ { variable | DATE:" SHORT _ DATE _ FORMAT " % }或{ { variable | DATE:" SHORT _ DATETIME _ FORMAT " }。

预定义的日期变量本身也由基于表 3-3 中语法的日期字符串组成。例如,DATE_FORMAT 默认为“N j,Y”(例如,2018 年 1 月 1 日),DATETIME_FORMAT 默认为“N j,Y,P”(例如,2018 年 1 月 1 日上午 12 点),SHORT_DATE_FORMAT 默认为“m/d/Y”(例如,2018 年 1 月 1 日上午 12 点),SHORT_DATETIME_FORMAT 默认为“m/d/Y P”(例如,2018 年 1 月 1 日上午 12 点)。在项目的 settings.py 文件中,可以用不同的日期字符串重写每个日期变量。

表 3-3。

Django date and time format characters

| 基于标准的字符 | 描述 | | --- | --- | | c | 输出 ISO 8601 格式(例如,2015-01-02T10:30:00.000123+02:00 或 2015-01-02t 10:30:00.000123,如果日期时间没有时区[即,简单日期时间]) | | r | 输出 RFC 2822 格式的日期(例如,“星期四,2000 年 12 月 21 日 16:01:07 +0200”) | | U | 输出自 Unix 纪元日期-1970 年 1 月 1 日 00:00:00 UTC 以来的秒数 | | I(大写的 I) | 输出夏令时是否有效(例如,“1”或“0”) | | 基于小时的字符 | 描述 | | a | 输出'上午'或'下午' | | A | 输出' AM '或' PM ' | | f | 输出时间,12 小时制的小时和分钟,如果分钟为零,则不输出分钟(例如,“1”,“1:30”) | | g | 输出不带前导零的小时、12 小时格式(例如“1”到“12”) | | G | 输出不带前导零的 24 小时制小时格式(例如,0 到 23) | | h | 输出小时,12 小时格式(例如,“01”到“12”) | | H | 输出小时,24 小时格式(例如,“00”到“23”) | | 我 | 输出分钟数(例如“00”到“59”) | | P | 输出时间,12 小时制的小时、分钟和' a.m.'/'p.m . ',如果分钟为零,则不输出分钟,如果合适,则输出特殊情况字符串' midnight '和' noon '(例如,' a . m . 1 ',' 1:30 p.m . ',' midnight ',' noon ',' 12:30 p.m . ') | | s | 输出秒,带前导零的两位数(例如,“00”到“59”) | | u | 输出微秒数(例如,000000 到 999999) | | 时区字符 | 描述 | | e | 输出时区名称。可以是任何格式,或者可能返回空字符串,具体取决于日期时间的定义(例如,“”、“GMT”、“-500”、“美国/东部”) | | O | 以小时为单位输出时区与格林威治时间的差异(例如,“+0200”) | | T | 输出日期时间时区(例如' EST ',' MDT ') | | Z | 以秒为单位输出时区偏移量。UTC 以西的时区偏移量始终为负,而 UTC 以东的时区偏移量始终为正(例如-43200 到 43200) | | 日和周字符 | 描述 | | D | 输出星期几,文本,3 个字母(例如,“Thu”,“Fri”) | | L(小写 L) | 输出星期几,文本,长型(例如,“星期四”,“星期五”) | | S | 输出一个月中某一天的英文序号后缀,2 个字符(例如,“st”、“nd”、“rd”或“th”) | | w | 输出星期几,不带前导零的数字(例如,“0”代表星期日,“6”代表星期六) | | z | 输出一年中的某一天(例如,0 到 365) | | W | 输出一年中的周数,基于 ISO-8601 从星期一开始(例如,1,53) | | o | 输出周编号年份,对应于 ISO-8601 周编号(W)(例如,“1999”) | | 月份字符 | 描述 | | b | 输出文本月份,3 个字母,小写(例如'一月','二月') | | d | 输出一个月中的某一天,2 位数,带前导零(例如,“01”到“31”) | | j | 输出不带前导零的一个月中的某一天(例如,“1”到“31”) | | E | 输出月份,区域特定的替代表示,通常用于长日期表示(例如,波兰语区域的“listopada”,与“Listopad”相对) | | F | 输出月份,文本,长型(例如,“一月”,“二月”) | | m | 输出月份,带前导零的两位数(例如,“01”到“12”) | | M | 输出月份,文本,3 个字母(例如'一月','二月') | | n | 输出不带前导零的月份(例如“1”到“12”) | | 普通 | 以美联社风格输出月份缩写(例如,“一月”、“二月”、“三月”、“五月”) | | t | 输出给定月份的天数(例如,28 到 31) | | 年份字符 | 描述 | | L | 输出布尔值来判断是否是闰年(例如,真或假) | | y | 输出年份,两位数(例如“99”) | | Y | 输出年份,4 位数字(例如,“1999”) |

To literally output a date character in a string statement you can use the backslash character (e.g., {{variable|date:"jS \o\f F o"}} outputs 1st of January 2018, note the escaped \o\f)

  • time。-time过滤器格式化 Python datetime对象的时间部分。time过滤器类似于date过滤器,它使用一个字符串来指定时间格式。例如,如果一个变量包含一个时间为中午的datetime对象,那么过滤器语句{{variable|time:"g:i"}}输出 12:00。时间过滤器使用与时间相关的表 3-3 中所示的相同格式字符。

Tip

如果没有为日期筛选器提供字符串参数(例如{{variable|time}} ),则默认为“P”字符串,该字符串来自 TIME_FORMAT 的默认值。

Note

时间过滤器还可以接受预定义的时间变量{{variable|date:"TIME FORMAT"}。预定义时间也由基于表 3-3 中语法的时间字符串组成。例如,TIME_FORMAT 默认为“P”(例如,凌晨 4 点),这可以通过在项目的 settings.py 文件中定义 TIME_FORMAT 来覆盖。

  • timesince。-timesince过滤器输出一个datetime物体和当前时间之间经过的时间。timesince过滤器输出以秒、分、小时、天或周表示。例如,如果变量包含datetime对象 01/01/2018 12:00pm,当前时间为 01/01/2018 3:30pm,则语句{{variable|timesince}}输出 3 小时 30 分钟。timesince过滤器还可以通过附加第二个 datetime 对象参数(例如,{{variable|timesince:othervariable}})来计算两个datetime对象变量之间经过的时间,而不是默认的当前时间。
  • timeuntil。-timeuntil过滤器输出从当前时间到datetime对象所需的时间。timeuntil过滤器输出以秒、分、小时、天或周表示。例如,如果变量包含datetime对象 01/01/2018 10:00pm,并且当前时间是 01/01/2018 9:00pm,则语句{{variable|timeuntil}}输出 1 小时。timeuntil过滤器还可以通过附加第二个datetime对象参数(例如{{variable|timeuntil:othervariable}})来计算两个datetime对象变量之间需要经过的时间,而不是默认的当前时间。

字符串、列表和数字

  • add。-add过滤器增加数值。add过滤器可以添加两个变量或一个硬编码值和一个变量。例如,如果一个变量包含 5,那么过滤语句{{variable|add:"3"}}输出 8。如果值可以被强制转换成整数——就像上一个例子一样——add过滤器执行一次求和,如果不能,加法过滤器进行连接。对于包含“Hello”的字符串变量,过滤器语句{{variable|add:" World"}}输出 Hello World。对于包含['a ',' e ',' i']的列表变量和包含['o ',' u']的列表变量,过滤器语句{{variable|add:othervariable}}输出['a ',' e ',' I ',' o ',' u']。
  • default。-default过滤器用于在变量为假、不存在或为空时指定默认值。例如,如果一个变量在模板中不存在,包含 False 或者是一个空字符串('),过滤语句{{variable|default:"no value"}}输出no value
  • default_if_none。-默认过滤器用于指定变量为None时的默认值。例如,如果一个变量包含None,过滤语句{{variable|default_if_none:"No value"}}输出No value。注意如果一个变量包含一个空字符串(''),这不被认为是Nonedefault_if_none过滤器不输出它的参数值。
  • length。-length过滤器用于获取值的长度。例如,如果一个变量包含字符串latte,过滤语句{{variable|length}}输出 5。对于包含['a','e','i']的列表变量,过滤语句{{variable|length}}输出 3。
  • length_is。-length_is过滤器用于评估值的长度是否是给定参数的大小。例如,如果一个变量包含latte,那么标签和过滤器语句{% if variable|length_is:"7" %}的计算结果为假。对于包含['a','e','i']的列表变量,标签和过滤器语句{% if variable|length_is:"3" %}评估为真。
  • make_list。-make_list过滤器从一个字符串或数字创建一个列表。例如,对于过滤器和标签语句{% with mycharlist="mocha"|make_list %},mycharlist 变量被赋予列表['m ',' o ',' c ',' h ',' a']。对于包含 724 个过滤器和标签语句{% with myintlist=variable|make_list %}的整数变量,myintlist 被分配列表['7 ',' 2 ',' 4']。
  • yesno。-yesno过滤器将来自TrueFalseNone的变量值映射到字符串 yes、no、maybe。例如,如果一个变量评估为True,过滤器语句{{variable|yesno}}输出 yes,如果该变量评估为False,相同的语句输出 no,如果该变量评估为None,相同的语句输出 maybe。yesno过滤器也接受自定义消息作为参数。例如,如果一个变量的值为True,过滤语句{{variable|yesno:"yea,nay,novote"}}输出 yea,如果该变量的值为False,相同的语句输出 nay,如果该变量的值为None,相同的语句输出 novote。

民数记

  • divisibleby。-divisibleby过滤器返回一个布尔值,如果一个变量可以被一个给定值整除。例如,如果一个变量包含 20,过滤语句{{variable|divisibleby:"5"}}返回True
  • filesizeformat。-filesizeformat过滤器将多个字节转换成友好的文件大小字符串。例如,如果一个变量包含 250,过滤语句{{variable|filesizeformat}}输出 250 字节,如果它包含 2048,输出是 2 KB,如果它包含 2000000000,输出是 1.9 GB。
  • floatformat。-floatformat过滤器对浮点数变量进行舍入。floatformat过滤器可以接受正整数或负整数参数,将变量四舍五入到特定的小数位数。如果没有使用参数,floatformat过滤器会舍入到一个小数位,就像参数 where -1 一样。例如,如果变量包含 9.33253,过滤语句{{variable|floatformat}}输出 9.3,对于同一变量{{variable|floatformat:3}}输出 9.333,对于{{variable|floatformat:-3}}输出 9.333;如果变量包含 9.00000,过滤语句{{variable|floatformat}}输出 9,{{variable|floatformat:3}}输出 9.000,{{variable|floatformat:-3}}输出 9;如果变量包含 9.37000,过滤语句{{variable|floatformat}}输出 9.4,{{variable|floatformat:3}}输出 9.370,{{variable|floatformat:-3}}输出 9.370。
  • get_digit。-get_digit过滤器输出一个数字变量的数字,其中 1 是最后一个数字,2 是倒数第二个数字,依此类推。例如,如果变量包含 10257,过滤语句{{variable|get_digit:"1"}}输出 7,过滤语句{{variable|get_digit:"3"}}输出 2。如果变量或自变量不是整数,或者自变量小于 1,则get_digit过滤器输出原始变量值。
  • phone2numeric。-phone2numeric过滤器将电话号码中的助记字母转换成数字。例如,如果变量包含 1-800-DJANGO,过滤器语句{{variable|phone2numeric}}输出 1-800-352646。phone2numeric过滤器值不一定需要处理有效的电话号码,过滤器只是将字母转换成它们对应的电话键盘号码。

用线串

capfirst。-capfirst过滤器将字符串变量的第一个字符大写。例如,如果变量包含hello world,过滤语句{{variable|capfirst}}输出Hello world

  • cut。-cut过滤器从字符串变量中删除给定参数的所有值。例如,如果变量包含mocha latte,过滤语句{{variable|filter:"mocha"}}输出latte。对于同一个变量,过滤语句是{{variable|filter:" "}}输出mochalatte

  • linenumbers。-linenumbers过滤器将行号添加到由新行分隔的每个字符串值中。清单 3-15 展示了一个linenumbers过滤器的例子。

    # Variable definition
    Downtown
    Uptown
    Midtown
    
    # Template definition with linenumbers filter
    {{variable|linenumbers}}
    
    # Output
    1.Downtown
    2.Uptown
    3.Midtown
    
    Listing 3-15.Django linenumbers filter
    
    
  • lower。-lower过滤器将字符串变量的所有值转换成小写。例如,如果一个变量包含Hello World,过滤语句{{variable|lower}}输出hello world

  • stringformat。-stringformat过滤器使用 Python 字符串格式语法格式化一个值。 4 例如,如果一个变量包含7,则过滤语句{{variable|stringformat:"03d"}}输出007。注意stringformat过滤器不需要 Python 字符串格式语法中使用的前导%

  • pluralize。-pluralize过滤器根据参数值返回复数后缀。例如,如果变量drink_count包含 1,过滤语句"You have {{drink_count}} drink{{pluralize|drink_count}}"输出"You have 1 drink",如果变量包含 2,相同的过滤语句输出"You have 2 drinks"。默认情况下,复数过滤器使用字母 s,这是最常见的复数后缀。但是,您可以使用附加参数指定不同的单数和复数后缀。例如,如果store_count为 1,则过滤语句"We have {{store_count}} business{{store_count|pluralize:"es"}}"输出"We have 1 business",如果store_count为 5,则输出"We have 5 businesses"。另一个例子是过滤语句"We have {{resp_number}} responsibilit{{resp_number|pluralize:"y","ies"}}",如果resp_number为 1,则输出"We have 1 responsibility",如果resp_number为 3,则输出"We have 3 responsibilities"

  • slugify。-slugify过滤器将字符串转换成 ASCII 类型的字符串。这意味着字符串被转换为小写,删除非单词字符(字母数字和下划线),去除前导和尾随空格,以及将空格转换为连字符。例如,如果一个变量包含Welcome to the #1 Coffeehouse!,过滤语句{{variable|slugify}}输出welcome-to-the-1-coffeehouseslugify过滤器通常用于规范 URL 和文件路径的字符串。

  • title。-title过滤器将字符串变量的所有第一个字符值转换为大写。例如,如果一个变量包含hello world,过滤语句{{variable|title}}输出Hello World

  • truncatechars。-truncatechars过滤器将字符串截断成给定数量的字符,并附加一个省略号序列。例如,如果变量包含Coffeehouse started as a small store,过滤语句{{variable|truncatechars:20}}输出Coffeehouse started...

  • truncatechars_html。-truncatechars_html过滤器类似于truncatechars过滤器,但是能够识别 HTML 标签。这个过滤器是为 HTML 内容设计的,所以内容不会留下打开的 HTML 标签。例如,如果变量包含<b>Coffeehouse started as a small store</b>,过滤语句{{variable|truncachars_html:20}}输出<b>Coffeehouse start...</b>

  • truncatewords。-truncatewords过滤器将字符串截断成给定数量的单词,并附加一个省略号序列。例如,如果一个变量包含Coffeehouse started as a small store,过滤语句{{variable|truncatwords:3}}输出Coffeehouse started as...

  • truncatewords_html。-truncatewords_html过滤器类似于truncatewords过滤器,但是能够识别 HTML 标签。这个过滤器是为 HTML 内容设计的,所以内容不会留下打开的 HTML 标签。例如,如果变量包含<b>Coffeehouse started as a small store</b>,过滤语句{{variable|truncatwords_html:3}}输出<b>Coffeehouse started as...</b>

  • upper。-upper过滤器将字符串变量的所有值转换为大写。例如,如果一个变量包含Hello World,过滤语句{{variable|lower}}输出HELLO WORLD

  • wordcount。-wordcount过滤器对字符串中的单词进行计数。例如,如果变量包含Coffeehouse started as a small store,过滤语句{{variable|wordcount}}输出 6。

列表和词典

  • dictsort。-dictsort过滤器对字典列表进行排序,并返回一个按给定的关键参数排序的新列表。例如,如果一个变量包含[{'name':'Downtown','city':'San Diego'}, {'name':'Uptown','city':'San Diego'},{'name':'Midtown','city':'San Diego'}]过滤器和标签语句{% with newdict=variable|dictsort:"name" %},那么newdict变量被分配到列表[{'name':'Downtown','city':'San Diego'},{'name':'Midtown','city':'San Diego'},{'name':'Uptown','city':'San Diego'}]dictsort过滤器还可以通过指定索引号(例如,{ % with otherlist=listoftuples|dictsort:0 %})对元组列表或列表进行操作,以通过列表中每个元组的第一个元素进行排序。
  • dictsortreversed。-dictsortreversed过滤器对字典列表进行排序,并返回一个按给定关键参数反向排序的新列表。dictsortreversed过滤器的工作方式类似于dictsort,只是它以相反的顺序返回列表。
  • join。-join过滤器用一个字符串连接一个列表。连接过滤器就像 Python 的str.join(list)一样工作。例如,对于包含['a ',' e ',' I ',' o ',' u']的列表变量,过滤语句{{variable|join:"--"}}输出 a - e - i - o - u。
  • first。-first过滤器返回列表中的第一项。例如,对于包含['a ',' e ',' I ',' o ',' u']的列表变量,过滤语句{{variable|first}}输出一个
  • last。-last过滤器返回列表中的最后一项。例如,对于包含['a ',' e ',' I ',' o ',' u']的列表变量,过滤语句{{variable|last}}输出 u
  • random。-random过滤器返回一个列表中的随机项目。例如,对于包含['a ',' e ',' I ',' o ',' u']的列表变量,过滤语句{{variable|random}}可以输出 a,e,I,o 或 u
  • slice。-slice过滤器返回列表的片段。例如,对于包含['a ',' e ',' I ',' o ',' u']的列表变量,过滤语句{{variable|slice:":3"}}输出['a ',' e ',' i']。
  • unordered_list。-unordered_list从一个列表变量中输出一个 HTML 无序列表。清单 3-16 展示了一个无序列表过滤器的例子。
# Variable definition
["Stores",["San Diego",["Downtown","Uptown","Midtown"]]]

# Template definition with linenumbers filter
{{variable|unordered_list}}

# Output
<li>Stores
   <ul>
       <li>San Diego
          <ul>
   <li>Downtown</li>
   <li>Uptown</li>
   <li>Midtown</li>
  </ul>
       </li>
   </ul>
</li>

Listing 3-16.Django unordered_list filter

Caution

unordered_list 过滤器的第一级不包括开始或结束 HTML

间距和特殊字符

  • addslashes。-addslashes过滤器向所有引号添加斜线(即,它对引号进行转义)。当 Django 模板用于将数据导出到其他需要转义引号的系统(例如 CSV 文件)时,addslashes过滤器非常有用。例如,如果变量包含Today's news,过滤语句{{variable|addslashes}}输出Today\'s新闻。
  • center。-center过滤器中心对齐一个值并用额外的空白字符填充它,直到它到达给定的字符参数。例如,如果一个变量包含mocha,过滤语句{{variable|center:"15"}}输出。
  • "mocha"。(即,摩卡左边 5 个空格,摩卡 5 个空格,摩卡右边 5 个空格。
  • ljust。-ljust过滤器左对齐一个值并用额外的空白字符填充它,直到它到达给定的字符参数。例如,如果一个变量包含mocha,过滤语句{{variable|ljust:"15"}}输出。
  • "mocha"。(即 5 个空格用于摩卡,10 个空格填充)。
  • rjust。-rjust过滤器右对齐一个值并用额外的空白字符填充它,直到它到达给定的字符参数。例如,如果一个变量包含latte,过滤语句{{variable|rjust:"10"}}输出。
  • "latte"."(即 5 个空格填充,5 个空格给拿铁)。
  • escape。-escape过滤器从一个值中转义 HTML 字符。具体用escape滤镜:<转换为&lt;>转换为&gt;'(单引号)转换为'"(双引号)转换为&quot;&转换为&amp

Tip

如果在连续的变量上使用转义过滤器,用{% autoescape %}标记包装变量会更容易达到相同的结果。

  • escapejs。-escapejs过滤器将字符转义成通常用于 JavaScript 字符串的 Unicode 字符串。虽然escapejs过滤器不能保证字符串 HTML 的安全,但是它可以防止在使用模板生成 JavaScript/JSON 时出现语法错误。例如,如果一个变量包含mocha\r\n \'price:2.25,过滤语句{{variable|escapejs}}输出\u0022mocha\u000D\u000A \u0027price:2.25\u0022
  • force_escape。-force_escape过滤器从一个值中转义 HTML 字符,就像escape过滤器一样。不同的是force_escape被立即应用并返回一个新的转义字符串。当您需要多次转义或想要对转义结果应用其他过滤器时,这很有用。通常,你会使用escape滤镜。
  • linebreaks。-linebreaks过滤器用 HTML 标签替换纯文本换行符,单个换行符变成 HTML 换行符(<br/>),新的一行后面跟一个空行变成段落换行符(</p>)。例如,如果变量包含385 Main\nSan Diego, CA,过滤语句{{variable|linebreaks}}输出<p>385 Main<br/>San Diego, CA</p>
  • linebreaksbr。-linebreaksbr过滤器将所有文本变量换行符转换成 HTML 换行符(<br/>)。例如,如果变量包含385 Main\nSan Diego, CA,过滤语句{{variable|linebreaksbr}}输出385 Main<br/>San Diego, CA
  • striptags。-striptags过滤器从一个值中删除所有 HTML 标签。例如,如果一个变量包含<b>Coffee</b>house, the <i>best</i> <span>drinks</span>,过滤语句{{variable|striptags}}输出Coffeehouse, the best drinks

Caution

striptags 过滤器使用非常基本的逻辑来剥离 HTML 标签。这意味着一段复杂的 HTML 有可能没有完全去掉标签。这就是为什么通过 striptags 过滤器传递的变量中的内容会被自动转义,并且永远不应该被标记为安全的。

  • safe。-安全过滤器将字符串标记为不需要 HTML 转义。
  • safeseq。-safeseqsafe过滤器应用于列表的每个元素。它和其他操作列表的过滤器一起使用很有用,比如join过滤器(例如{{stores|safeseq|join:", "}}))。您不会直接在列表变量上使用safe过滤器,因为它会首先将变量转换成字符串,而不是处理列表的单个元素。
  • wordwrap。-wordwrap过滤器在给定的字符行长度参数处换行。清单 3-17 展示了一个wordwrap过滤器的例子。
# Variable definition

Coffeehouse started as a small store

# Template definition with wordwrap filter for every 12 characters
{{variable|wordwrap:12}}

# Output
Coffeehouse
started as a
small store

Listing 3-17.Django wordwrap filter

开发和测试

  • pprint。-pprint过滤器是 Python 的pprint.pprint()的包装器。pprint过滤器在开发和测试期间很有用,因为它输出对象的格式化表示。

资源定位符

  • iriencode。-iriencode过滤器将国际化资源标识符(IRI)转换成适合包含在 URL 中的字符串。如果您试图在 URL 中使用包含非 ASCII 字符的字符串,这是必要的。例如,如果一个变量包含?type=cold&size=large,过滤语句{{variable|iriencode}}输出?type=cold&amp;size=large
  • urlencode。-urlencode过滤器对 URL 中使用的值进行转义。例如,如果一个变量包含http://localhost/drinks?type=cold&size=large,过滤语句{{variable|urlencode}}输出http%3A//localhost/drinks%3Ftype%3Dcold%26size%3Dlargeurlenconde过滤器假设/角色是安全的。urlencode过滤器可以接受带有不应该转义字符的可选参数。当所有字符都应该转义时,可以提供空字符串(例如,{{variable|urlencode:""}}输出http%3A%2F%2Flocalhost%2Fdrinks%3Ftype%3Dcold%26size%3Dlarge)。
  • urlize。-urlize过滤器将文本 URL 或电子邮件地址转换成可点击的 HTML 链接。这个urlize过滤器作用于以 http://、https://或 www 为前缀的链接..urlize 过滤器生成的链接添加了一个rel="nofollow"属性。例如,如果一个变量包含Visit http://localhost/drinks,过滤语句{{variable|urlize}}输出Visit <a href="http://localhost/drinks" rel="nofollow">http://localhost/drinks</a>;如果变量包含Contact support@coffeehouse.com,过滤语句{{variable|urlize}}输出Contact <a href="mailto:support@coffeehouse.com">support@coffeehouse.com</a>
  • urlizetrunc。—urlizetrunc过滤器将文本 url 和电子邮件转换为可点击的 HTML 链接——就像urlize过滤器一样——除了它将 URL 截断为给定数量的包含省略号序列的字符。例如,如果变量包含Visit http://localhost/drinks,过滤语句{{variable|urlizetrunc:20}}输出Visit <a href="http://localhost/drinks" rel="nofollow">http://localhost/...</a>

Caution

urlize 和 urlizetrunc 筛选器应该只应用于纯文本变量。如果应用于带有 HTML 链接的变量,过滤逻辑将不会像预期的那样工作。

内置 Django 标签

Django 提供了几个内置标签,可以直接访问 Django 模板上的复杂操作。与对单个变量进行操作的 Django 过滤器不同,标记被设计成在没有变量的情况下产生结果,或者跨模板部分进行操作。

我将把这些内置标签分为不同的功能部分,这样更容易识别它们。我将使用的函数类是日期、表单、比较操作、循环、Python 和过滤器操作、空格和特殊字符、模板结构、开发和测试以及 URL。

日期

  • {% now %}。-{% now %}标签提供对当前系统时间的访问。{% now %}标签接受第二个参数来格式化系统日期。例如,如果语句{% now "F jS o" %}的系统日期为 2015 年 1 月 1 日,则标签输出为 2015 年 1 月 1 日。{% now %}标签的字符串语法基于表 3-3 中描述的 Django 日期字符。也可以使用as关键字通过变量重用该值(例如{% now "Y" as current_year %}和模板声明Copyright {{current_year}})。

Tip

{% now %}标记可以接受 Django 日期变量:{% now "DATE_FORMAT" %} 、{% now "DATETIME_FORMAT" %} 、{% now "SHORT_DATE_FORMAT" %}或{% now "SHORT_DATETIME_FORMAT"}。

日期变量本身也由日期字符串组成。例如,DATE_FORMAT 默认为“N j,Y”(例如,2015 年 1 月 1 日),DATETIME_FORMAT 默认为“N j,Y,P”(例如,2015 年 1 月 1 日,上午 12 点),SHORT_DATE_FORMAT 默认为“m/d/Y”(例如,2015 年 1 月 1 日),SHORT_DATETIME_FORMAT 默认为“m/d/Y P”(例如,2015 年 1 月 1 日,上午 12 点)。在项目的 settings.py 文件中,可以用不同的日期字符串重写每个日期变量。

形式

  • {% csrf_token %}。-{% csrf_token %}标签提供了一个字符串来防止跨站脚本。{% csrf_token %}标签仅用于 HTML <form>标签中。{% csrf_token %}标签的数据输出允许 Django 防止表单数据提交中的伪造请求(例如 HTTP POST 请求)。Django 表单一章中提供了关于{% csrf_token %}标签的更多细节。

比较操作

  • {% if %}{% elif %} {% else %}。-{% if %}标签通常与{% elif %}{% else %}标签结合使用,以评估多个条件。如果变量存在且不为空,或者如果变量持有一个True布尔值,则带有自变量变量的{% if %}标签评估为真。清单 3-18 展示了一系列{% if %}标签示例。

    {% if drinks %}             {% if drinks %}              {% if drinks %}
      We have drinks!                We have drinks              We have drinks
    {% endif %}                 {% else %}                   {% elif drinks_on_sale %}
                                    No drinks,sorry              We have drinks on sale!
                                {% endif %}                  {% else %}
                                                               No drinks, sorry
                                                             {% endif %}
    Listing 3-18.Django {% if %} tag with {% elif %} and {% else %}
    
    

Note

变量必须既存在又不为空才能计算为 true。仅存在且为空的变量的计算结果为 false。

  • {% if %}andornot操作符。-{% if %}标签还支持andornot操作符来创建更复杂的条件。这些运算符允许您比较是否有多个变量不为空(如{% if drinks and drinks_on_sale %}),是否有一个或另一个变量不为空(如{% if drinks or drinks_on_sale %}),或者是否有一个变量为空(如{% if not drinks %})。

  • {% if %}==!=<><=>=操作符。-{% if %}标签还支持等于、不等于、大于和小于运算符,以创建将变量与固定字符串或数字进行比较的条件。这些运算符允许您比较变量是否等于字符串或数字(如{% if drink == "mocha" %})、变量是否不等于变量或数字(如{% if store.id != 2 %})或变量是否大于或小于数字(如{% if store.id > 5 %})。

  • {% firstof %}。-{% firstof %}标记是一个简写标记,用于输出一组非空变量中的第一个变量。通过嵌套{% if %}标签可以实现{% firstof %}标签的相同功能。清单 3-19 展示了{ % firstof %}标签的一个示例,以及一组等价的嵌套{% if %}标签。

    # Firstof example
    {% firstof var1 var2 var3 %}
    
    # Equivalent of firstof example
    {% if var1 %}
        {{var1|safe}}
    {% elif var2 %}
        {{var2|safe}}
    {% elif var3 %}
        {{var3|safe}}
    {% endif %}
    
    # Firstof example with a default value in case of no match (i.e, all variables are empty)
    {% firstof var1 var2 var3 "All vars are empty" %}
    
    # Assign the firstof result to another variable
    {% firstof var1 var2 var3 as resultof %}
    # resultof now contains result of firstof statement
    
    Listing 3-19.Django {% firstof %} tag and equivalent {% if %}{% elif %}{% else %} tags
    
    
  • {% if <value> in %}{% if <value> not in %}。-{% if %}标签还支持innot in操作符来验证常量或变量的存在。例如,{% if "mocha" in drinks %}测试值"mocha"是否在drinks列表变量中,或者{% if 2 not in stores %}测试值2是否不在stores列表变量中。虽然innot in操作符通常用于测试列表变量,但是也可以测试字符串中是否存在字符(例如{% if "m" in drink %})。此外,还可以比较一个变量的值是否出现在另一个变量中(如{% if order_drink in drinks %})。

  • {% if <value> is <value> %}{% if <value> is not %}。-{% if %}标签还支持isis not操作符进行对象级比较。例如,{% if target_drink is None %}测试值target_drink是否是一个None对象,或者{% if daily_special is not True %}测试值daily_special是否不是True

  • {% if value|<filter> <condition> <value> %}。-{% if %}标签还支持直接对一个值应用过滤器,然后执行评估。例如,{% if target_drink_list|random == user_drink %}Congratulations your drink just got selected!{% endif %}在一个条件中直接使用random过滤器。

Parentheses are Not Allowed in If Tags: Operator Precedence Governs, Use Nested If Tags to Alter Precedence

比较运算符通常被聚合到单个语句中(例如,if...<...or...>...和...==...)并遵循一定的执行优先级。Django 遵循与 Python 相同的操作符优先级。 5 例如,语句{ % if drink in specials or drink == drink _ of _ the _ day % }的计算结果为((drink in specials)or(drink = = drink _ of _ the _ day)),其中首先运行内部括号运算,因为 in 和= =的优先级高于 or。

在 Python 中,可以通过在比较语句中使用显式括号来改变这种优先级。但是,Django 不支持在{% if %}标记中使用括号,您必须依赖操作符优先级或者使用嵌套的{% if %}语句来声明由显式括号产生的相同逻辑。

  • {% for %}{% for %}{% empty %}。-{% for %}标签遍历字典、列表、元组或字符串变量上的项目。{% for %}标签语法是{% for <reference> in <variable> %},其中reference在每次迭代中被赋予一个来自变量的新值。

根据变量的性质,可以有一个或多个引用(例如,对于列表一个引用{% for item in list %},对于字典两个引用{% for key,value in dict.items %})。此外,也可以用reversed关键字(例如{ % for item in list reversed %})反转循环顺序。{% for %}标签还支持{% empty %}标签,在循环中没有迭代的情况下(即主变量为空)会处理该标签。清单 3-20 展示了一个{% for %}和一个{% for %}{% empty %}循环示例。

<ul>                                 <ul>
{% for drink in drinks %}             {% for storeid,store in stores %}
 <li>{{ drink.name }}</li>            <li><a href="/stores{{storeid}}/">{{store.name}}</a></li>
{% empty %}                           {% endfor %}
 <li>No drinks, sorry</li>           </ul>
{% endfor %}
</ul>
Listing 3-20.Django {% for %} tag and {% for %} with {% empty %}

{% for %}标签还生成一系列变量来管理迭代过程,例如迭代计数器、第一次迭代标志和最后一次迭代标志。当您想要在给定的迭代中创建行为(例如,格式化、附加处理)时,这些变量会很有用。表 3-4 说明了{% for %}标签变量。

  • {% ifchanged %}。-{% ifchanged %}标签是在{% for %}标签中使用的特殊逻辑标签。有时,了解循环引用是否从一个迭代更改到另一个迭代(例如,插入新标题)会很有帮助。{% ifchanged %}标签的参数是循环引用本身(如{% ifchanged drink %}{{drink}} section{% endifchanged %})或引用的一部分(如{% ifchanged store.name %}Available in {{store.name}}{% endifchanged %})。{% ifchanged %}标签也支持使用{% else %}标签(例如{% ifchanged drink %}{{drink.name}}{% else %}Same old {{drink.name}} as before{% endifchanged %})。
  • {% cycle %}。-{% cycle %}标签被用在{% for %}标签中来迭代一组给定的字符串或变量。标签的主要用途之一是定义 CSS 类,这样每次迭代都会收到不同的 CSS 类。例如,如果你想给一个列表分配不同的 CSS 类,这样每一行以不同的颜色出现(例如,白色,灰色,白色,灰色),你可以使用<li class="{% cycle 'white' 'grey' %}">,这样在每次循环迭代中类值在白色和灰色之间交替。{% cycle %}标签可以顺序迭代任意数量的字符串或变量(例如{% cycle var1 var2 'red' %})。

表 3-4。

Django {% for %} tag variables

| 可变的 | 描述 | | --- | --- | | forloop .柜台 | 循环的当前迭代(1 索引) | | forloop.counter0 | 循环的当前迭代(索引为 0) | | forloop.revcounter | 从循环结束开始的迭代次数(1-索引) | | forloop.revcounter0 | 从循环结束开始的迭代次数(索引为 0) | | forloop.first | 如果是第一次通过循环,则为 True | | forloop.last | 如果是最后一次循环,则为真 | | 渐变.括号 | 对于嵌套循环,这是当前循环的父循环 |

默认情况下,{% cycle %}标签根据其封闭循环遍历其值(即,一个接一个)。但是在某些情况下,你可能需要在循环之外使用一个{% cycle %}标签或者明确声明一个{% cycle %标签如何前进。您可以通过用as关键字命名{% cycle %}标签来实现这种行为,如清单 3-21 所示。

<li class="{% cycle 'disc' 'circle' 'square' as bullettype %}">...</li>
<li class="{{bullettype}}">...</li>
<li class="{{bullettype}}">...</li>
<li class="{% cycle bullettype %}">...</li>
<li class="{{bullettype}}">...</li>
<li class="{% cycle bullettype %}">...</li>
# Outputs
<li class="disc">...</li>
<li class="disc">...</li>
<li class="disc">...</li>
<li class="circle">...</li>
<li class="circle">...</li>
<li class="square">...</li>
Listing 3-21.Django {% cycle %} with explicit control of progression

正如您在清单 3-21 中看到的,{% cycle %}标记语句最初产生第一个值,然后您可以继续使用循环引用名来输出相同的值。为了前进到循环中的下一个值,您用循环引用名再次调用{% cycle %}{% cycle %}标签的一个小副作用是它在声明的地方输出初始值,如果你打算把循环作为占位符或者在嵌套循环中使用,这可能会有问题。为了避免这种副作用,您可以在循环引用名称后使用silent关键字(例如,{ % cycle 'disc' 'circle' 'square' as bullettype silent %})。

  • {% resetcycle %}。—{% resetcycle %}标签用于将{% cycle %}标签重新初始化到其第一个元素。在返回到第一个值之前,{% cycle %}标签总是在它的整个值集上循环,这在嵌套循环的上下文中是有问题的。例如,如果您想要为嵌套组分配三个颜色代码(如{% cycle 'red' 'orange' 'yellow' %}),第一个组可以由两个元素组成,这两个元素用完了前两个循环值(如“红色”和“橙色”),这意味着第二个组从第三个颜色代码(如“黄色”)开始。为了让第二个组再次从第一个{% cycle %}元素开始,您可以在嵌套循环迭代完成后使用{% resetcycle %}标签,这样{% cycle %}标签就会返回到它的第一个元素。
  • {% regroup %}。-{% regroup %}标签用于将字典变量的内容重新排列到不同的组中。{% regroup %}标签避免了在{% for %}标签内创建复杂条件来实现所需显示的需要。{% regroup %}标签预先安排了字典的内容,使得{% for %}标签的逻辑更加简单。清单 3-22 展示了一个使用{% regroup %}标签及其输出的字典。
# Dictionary definition
stores = [
    {'name': 'Downtown', 'street': '385 Main Street', 'city': 'San Diego'},
    {'name': 'Uptown', 'street': '231 Highland Avenue', 'city': 'San Diego'},
    {'name': 'Midtown', 'street': '85 Balboa Street', 'city': 'San Diego'},
    {'name': 'Downtown', 'street': '639 Spring Street', 'city': 'Los Angeles'},
    {'name': 'Midtown', 'street': '1407 Broadway Street', 'city': 'Los Angeles'},
    {'name': 'Downton', 'street': '50 1st Street', 'city': 'San Francisco'},
]

# Template definition with regroup and for tags
{% regroup stores by city as city_list %}

<ul>
{% for city in city_list %}
    <li>{{ city.grouper }}
    <ul>
        {% for item in city.list %}
          <li>{{ item.name }}: {{ item.street }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}
</ul>

# Output
San Diego
    Downtown : 385 Main Street
    Uptown : 231 Highland Avenue
    Midtown : 85 Balboa Street
Los Angeles
    Downtown: 639 Spring Street
    Midtown: 1407 Broadway Street
San Francisco
    Downtown: 50 1st Street

Listing 3-22.Django {% for %} tag and {% regroup %}

Tip

{% regroup %}标记也可以使用筛选器或属性来获得分组结果。例如, 3-22 中的商店列表可以方便地按城市预先排序,从而自动按城市进行分组,但是如果商店列表没有预先排序,您需要先按城市对列表进行排序,以避免分组不完整,您可以直接使用 dictsort 过滤器(例如,{ % regroup stores | dict sort:' city ' by city as city _ list % })。{% regroup %}标记的另一种可能性是使用嵌套属性,如果分组对象有嵌套属性(例如,如果 city 有一个 state 属性{ % regroup stores by city . state as state _ list % })。

Python 和过滤器操作

  • {% filter %}。-{% filter %}标签用于将 Django 过滤器应用于模板部分。如果你声明了{% filter lower %},那么lower过滤器将应用于这个标签和{% endfilter %}标签之间的所有变量——注意过滤器lower将所有内容转换成小写。也可以使用相同的管道技术将多个过滤器应用到同一个部分,以将过滤器链接到变量(例如{% filter lower|center:"50" %}...variables to convert to lower case and center...{% endfilter %})。
  • {% with %}。-{% with %}标签允许您在 Django 模板的上下文中定义变量。当您需要为 Django view 方法没有公开的值创建变量时,或者当一个变量与一个重量级操作相关联时,这很有用。也可以在同一个{% with %}标签中定义多个变量(例如{% with drinkwithtax=drink.cost*1.07 drinkpromo=drink.cost*0.85 %})。在到达{% endwith %}标签之前,{% with %}标签中定义的每个变量都可供模板使用。

Python Logic Only Allowed Behind the Scenes in Custom Django Tags or Filters

Django 模板不允许包含内联 Python 逻辑。事实上,Django 模板允许内联 Python 逻辑的最接近的方式是通过{% with %}标记,这并不复杂。

让自定义 Python 逻辑在 Django 模板中工作的唯一方法是将代码嵌入到自定义 Django 标签或过滤器中。这样,您可以在模板上放置一个定制的 Django 标签或过滤器,Python 逻辑在后台运行。下一节将描述如何创建定制的 Django 过滤器。

间距和特殊字符

  • {% autoescape %}。-{% autoescape %}标签用于对模板部分的 HTML 字符进行转义。{% autoescape %}接受两个参数onoff中的一个。使用{% autoescape on %}时,该标签和{% endautoescape %}标签之间的所有模板内容都被 HTML 转义,而使用{% autoescape off %}时,该标签和{% endautoescape %}标签之间的所有模板内容都不被转义。

Tip

如果您想要全局启用或禁用自动转义(即,在所有模板上),更容易的方法是使用项目 settings.py 文件中模板配置的 OPTIONS 变量中的 autoescape 字段在项目级别禁用它,如本章第一节所述。

如果您想要启用或禁用单个变量的自动转义,您可以使用 safe 过滤器禁用单个 Django 模板变量的自动转义,或者使用 escape 过滤器对单个 Django 模板变量进行转义。

  • {% spaceless %}。-{% spaceless %}标签删除 HTML 标签之间的空白,包括制表符和换行符。因此,{% spaceless %}{% endspaceless %}中包含的所有 HTML 内容变得更加紧凑。注意{% spaceless %}标签只移除 HTML 标签之间的空间,它不移除文本和 HTML 标签之间的空间(例如<p> <span> my span </span> </p>,只移除<p> <span></span> </p>标签之间的空间,填充myspan字符串的<span>标签之间的空间保留)。
  • {% templatetag %}。-{% templatetag %}标签用于输出保留的 Django 模板字符。因此,如果您想在模板上逐字显示任何字符{%%}{{}}{}{##},您可以这样做。{% templatetag %}与八个参数中的一个结合使用来表示 Django 模板字符。{% templatetag openblock %}输出{%{% templatetag closeblock %}输出%}{% templatetag openvariable %}输出{{{% templatetag closevariable %}输出}}{% templatetag openbrace %}输出{{% templatetag closebrace %}输出}{% templatetag opencomment %}输出{#{% templatetag closecomment %}输出#}。一种更简单的方法是用{% verabtim %}标签包装保留的 Django 字符。
  • {% verbatim %}。-{% verbatim %}标签用于隔离正在处理的模板内容。Django 会绕过{% verbatim %}标签和{% endverbatim %}标签中的任何内容。这意味着像{{这样的特殊字符,像{{drink}}这样的变量语句,或者使用特殊 Django 字符的 JavaScript 逻辑将被忽略并逐字呈现。如果需要输出单个特殊字符,使用{% templatetag %}标签。
  • {% widthratio %}。-{% widthratio %}标签用于计算一个值与最大值的比值。{% widthratio %}标签有助于显示宽度固定但需要根据可用空间大小进行缩放的内容,例如图像和图表。例如,给定语句<img src="logo.gif" style="width:{% widthratio available_width image_width 100 %}%"/>,如果available_width是 75,而image_width是 150,则 0.50 乘以 100 得到 50。该图像的宽度比是根据可用空间和图像大小计算的,在这种情况下,该语句被呈现为:<img src="logo.gif" style="width:50%"/>
  • {% lorem %}。-{% lorem %}标签用于显示随机的拉丁文本,这对模板上的填充符很有用。{% lorem %}标签支持多达三个参数{% lorem [count] [method] [random] %}。其中[count]是要生成的段落数或字数的数字或变量,如果未提供,默认[count]为 1。其中[method]是单词的w、HTML 段落的p或纯文本段落块的b,如果没有提供,默认[method]b。并且其中单词random(如果给定的话)输出随机的拉丁单词,而不是公共模式(例如,Lorem ipsum dolor sit amet...).

模板结构

  • {% block %}。-{% block %}标签用于定义可以在不同 Django 模板上覆盖的页面部分。请参阅本章上一节如何创建可重用模板,以获取该标签的示例。
  • {% comment "Optional explanation" %}。-{% comment %}标签用于定义 Django 模板上的注释部分。Django 会绕过放置在{% comment %}{% endcomment %}标签之间的任何内容,这些内容不会出现在最终呈现的网页中。注意开始的{% comment %}标签中的字符串参数是可选的,但是有助于澄清注释的目的。
  • {# #}。-{# #}语法可以用于 Django 模板上的单行注释。Django 会绕过单行中位于{##}之间的任何内容,这些内容不会出现在最终呈现的网页中。注意,如果注释跨越多行,你应该使用{% comment %}标签。
  • {% extends %}。-{% extends %}标签用于重用另一个 Django 模板的布局。参见本章上一节关于创建可重用模板的例子。
  • {% include %}。-{% include %}标签用于将一个 Django 模板嵌入到另一个 Django 模板中。参见本章上一节关于创建可重用模板的例子。
  • {% load %}。-{% load %}标签用于加载自定义 Django 标签和过滤器。{% load %}标签需要一个或多个参数作为自定义 Django 标签或过滤器的名称。本章的下一节将描述如何创建自定义过滤器以及如何使用{% load %}标签。

Tip

如果您发现自己在许多模板上使用{% load %}标记,您可能会发现用模板中的 builtins 选项注册 Django 标记和过滤器更容易,这样它们就可以在所有模板上访问,就像它们是内置的一样。有关详细信息,请参见本章中关于模板配置的第一节。

开发和测试

  • {% debug %}。-{% debug %}标签输出包括模板变量和导入模块的调试信息。{% debug %}标签在开发和测试期间很有用,因为它输出 Django 模板使用的“幕后”信息。

资源定位符

  • {% url %}。-{% url %}标签用于从项目的urls.py文件中的预定义值构建 URL。{% url %}标签很有用,因为它避免了在模板上硬编码 URL 的需要,而是基于名称插入 URL。{% url %}标签接受一个 url 名称作为第一个参数,url 参数作为后续参数。

比如一个 url 指向/drinks/index/,命名为drinks_main,可以用{% url %}引用这个 url(如<a href="{% url drinks_main %}"> Go to drinks home page </a>);如果一个 url 指向/stores/1/并且被命名为stores_detail,你可以使用{% url %}和一个参数来引用这个 url(例如<a href="{% url stores_detail store.id %}"> Go to {{store.name}} page </a>)。

{% url %}标签还支持as关键字,将结果定义为一个变量。这允许结果被多次使用,或者在声明了{% url %}标签以外的地方使用(例如{% url drink_detail drink.name as drink_on_the_day%}...后来在模板<a href="{{drink_of_the_day}}> Drink of the day </a>。第二章详细描述了命名 Django url 的过程,以便于管理和反向匹配。

自定义过滤器

有时候,Django 内置的过滤器在逻辑或输出方面有所欠缺。在这些情况下,解决方案是编写一个自定义筛选器来实现您需要的结果。

Django 过滤器背后的逻辑完全是用 Python 编写的,因此使用 Python & Django 可以实现的任何功能(例如,执行数据库查询、使用第三方 REST 服务)都可以集成为定制过滤器生成的逻辑或输出的一部分。

结构

最简单的定制 Django 过滤器只需要你创建一个标准的 Python 方法并用@register.filter()修饰它,如清单 3-23 所示。

from django import template
register = template.Library()

@register.filter()
def boldcoffee(value):
    '''Returns input wrapped in HTML  tags'''
    return '<b>%s</b>' % value

Listing 3-23.Django custom filter with no arguments

清单 3-23 首先导入template包,创建一个register引用来修饰boldcoffee方法,并告诉 Django 从中创建一个自定义过滤器。

默认情况下,筛选器接收与修饰方法相同的名称。所以在这种情况下,boldcoffee方法创建了一个名为boldcoffee的过滤器。方法输入value代表过滤器调用者的输入。在这种情况下,该方法只是返回包装在 HTML <b>标记中的输入值,其中 return 语句中使用的语法是标准的 Python 字符串格式操作。

要在 Django 模板中应用这个定制过滤器,可以使用语法{{byline|boldcoffee}}byline变量作为value参数传递给过滤器方法,所以如果byline变量包含文本Open since 1965!,过滤器输出就是<b>Open since 1965!</b>

Django 定制过滤器也支持包含参数,如清单 3-24 所示。

@register.filter()
def coffee(value,arg="muted"):
    '''Returns input wrapped in HTML  tags with a CSS class'''
    '''Defaults to CSS class 'muted' from Bootstrap'''
    return '<span class="%s">%s</span>' % (arg,value)
Listing 3-24.Django custom filter with arguments

清单 3-24 中的过滤方法有两个输入参数。代表应用过滤器的变量的参数value和第二个参数arg="muted",其中"muted"代表默认值。如果您查看 return 语句,您会注意到它使用了arg变量来定义一个class属性,而value变量用于定义一个<span>标签内的内容。

如果使用与第一个定制过滤器相同的语法调用清单 3-24 中的定制过滤器(例如{{byline|coffee}}),输出默认使用"muted"作为arg变量,最终输出为<span class="muted">Open since 1965!</span>

然而,您也可以调用清单 3-24 中的过滤器,使用一个参数覆盖arg变量。过滤参数附加有:。例如,过滤器语句{ {byline|coffee:"lead muted"}}"lead muted"指定为arg变量的值,并产生输出<span class="lead muted">Open since 1965!</span>

参数为自定义过滤器提供了更大的灵活性,因为它们可以用不同于主输入的数据进一步影响最终输出。

Tip

如果筛选器需要两个或更多参数,您可以在筛选器定义中使用空格分隔或 CSV 类型的字符串参数(例如,byline|mymultifilter:"18,success,green,2em "),然后在 filter 方法中解析该字符串以访问每个参数。

选项:命名、HTML 和进出的内容

尽管前面的两个例子说明了定制过滤器的核心结构,但是它们缺少一系列使定制过滤器更加灵活和强大的选项。表 3-5 展示了一系列自定义过滤器选项,以及它们的语法和功能描述。

表 3-5。

Custom filter options.

| 选项语法 | 价值观念 | 描述 | | --- | --- | --- | | @register.filter(name= ) | 命名过滤器的一个刺 | 指定不同于筛选方法名称的筛选名称。 | | @register.filter(is_safe=False) | 对/错 | 定义如何处理过滤器的返回值(安全或带自动转义)。 | | @ register . filter(needs _ auto escape = False) | 对/错 | 定义访问调用者的自动转义状态的需要(即,是否在带有或不带有自动转义的模板中调用过滤器)。 | | @ register . filter(expects _ local time = False) | 对/错 | 如果筛选器应用于日期时间值,则在运行筛选器逻辑之前,它会将该值转换为项目时区。 | | @ register . filter()@ string filter | 不适用的 | 将输入转换为字符串的独立装饰器。 |

正如你在表 3-5 中看到的,除了一个选项,所有的自定义过滤器选项都由@register.filter()装饰器的参数提供,并且包括默认值。因此,即使您声明了一个空的@register.filter()装饰器,表 3-5 中五个选项中的四个都使用默认值。注意可以给@register.filter()装饰器添加多个选项,用逗号分隔(例如@register.filter(name='myfilter',is_safe=True))。

下面说说表 3-5 中的name选项。默认情况下,正如您在前面的例子中所了解到的,自定义过滤器的名称与它们修饰的方法相同(也就是说,如果自定义过滤器的后台方法被命名为coffee,那么该过滤器也被称为coffee)。name选项允许您给过滤器一个不同于支持方法名称的名称。注意,如果使用name选项并试图用方法名调用过滤器,你会得到一个错误,因为过滤器不再以方法名存在。

所有自定义过滤器都在由变量提供的输入上操作,这些变量可能是任何 Python 类型(字符串、整数、日期时间、列表、字典等)。).这产生了必须在定制过滤器的逻辑中处理的多种可能性;否则,错误必然是常见的(例如,使用整数变量调用过滤器,但内部过滤器逻辑是为字符串变量设计的)。为了缓解这些潜在的输入类型问题,自定义过滤器可以使用表 3-5 中给出的最后两个选项。

表 3-5 中的expects_localtime选项是为操作datetime变量的过滤器设计的。如果你期待一个datetime输入,你可以将expects_localtime设置为True,这使得datetime输入时区基于你的项目设置而被感知。

表 3-5 中的@stringfilter选项——是一个独立的装饰器,位于@register.filter装饰器的下方——设计用于将过滤器输入变量转换为字符串。这是有帮助的,因为它消除了执行输入类型检查的需要,并且不管过滤器被调用的变量类型是什么(例如,字符串、整数、列表或字典变量),过滤器逻辑都可以确保它将总是获得字符串。

由于表 3-5 中的is_safe选项默认为False,自定义过滤器的一个微妙但默认的行为是输出被认为是不安全的。

这个默认设置使得来自包含 HTML <b><span>标签的清单 3-23 和 3-24 的定制过滤器创建逐字输出(也就是说,您不会看到以粗体显示的文本,而是逐字显示的<b>Open since 1965!</b>)。有时这是想要的行为,但有时不是。

Tip

要使 Django 模板在使用默认设置应用自定义过滤器后呈现 HTML 字符,您可以使用内置的safe过滤器(例如{{byline|coffee|safe}})或使用内置的{% autoescape %}标签(例如{% autoescape off %} {{byline|coffee}} {% endautoescape %}标签)包围过滤器声明。然而,Django filters 也可以将 filter is_safe 选项设置为 True,以使该过程自动化,并避免使用额外的过滤器或标记。

您可以将自定义过滤器中的is_safe选项设置为True,以确保自定义过滤器输出“按原样”呈现(例如,<b>标签以粗体显示),并且 HTML 元素不会被转义。

这种过滤器设计方法做了一个很大的假设:自定义过滤器总是用包含安全内容的变量来调用。如果byline变量包含文本Open since 1965 & serving > 1000 coffees day!会发生什么?变量现在包含了不安全的字符&>,为什么它们是不安全的?因为它们在 HTML 中有特殊的含义,如果不转义,就有可能破坏页面布局(例如,>在这个上下文中可能意味着“不止”,但在 HTML 中它也意味着标签打开,浏览器可以将其解释为标记,从而破坏页面,因为它从未关闭)。

为了避免标记不安全的输入字符并在输出时将其标记为安全的潜在问题,您需要依靠调用模板来告诉过滤器输入是安全的还是不安全的,这将我们带到表 3-5 : needs_autoescape.中的最后一个自定义过滤器选项

needs_autoescape选项——默认为 False——用于通知过滤器调用模板中的底层自动转义设置。清单 3-25 显示了一个使用这个选项的过滤器。

from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter(needs_autoescape=True)
def smartcoffee(value, autoescape=True):
    '''Returns input wrapped in HTML tags'''
    '''and also detects surrounding autoescape on filter (if any) and escapes '''
    if autoescape:
        value = escape(value)
    result = '<b>%s</b>' % value
    return mark_safe(result)

Listing 3-25.Django custom filter that detects autoescape setting

filter 方法的needs_autoescape参数和autoescape关键字参数允许过滤器知道在调用过滤器时转义是否有效。如果自动转义开启,那么value通过escape方法来转义所有字符。无论 value 的内容是否转义,过滤器都通过mark_safe方法传递最终结果,因此 HTML <b>标签在模板中被解释为粗体。

这个过滤器比使用is_safe=True选项的过滤器更健壮——并且将所有东西都标记为“安全”——因为它可以处理不安全的输入,只要模板用户正确使用自动转义。

安装和进入

Django 自定义过滤器可以存储在以下两个位置之一:

  • 内部应用。-储存在。py 文件位于 Django apps 中一个名为templatetags的文件夹中。
  • 任何项目位置。-储存在。通过settings.py.TEMPLATES变量的OPTIONS中的libraries域配置 Django 项目中任意文件夹下的 py 文件

清单 3-26 展示了一个项目目录结构,举例说明了存储定制过滤器的这两个位置。

+-<PROJECT_DIR_project_name>
|
+-__init__.py
+-settings.py
+-urls.py
+-wsgi.py
|
+----common----+
|              |
|              +--coffeehouse_filters.py
|
+----<app_one>---+
|                |
|                +-__init__.py
|                +-models.py
|                +-tests.py
|                +-views.py
|                +-----------<templatetags>---+
|                                              |
|                                              +-__init__.py
|                                              +-store_format_tf.py
+----<app_two>---+
|                |
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-----------<templatetags>---+
                                               |
                                              +-__init__.py
                                               +-tax_operations.py
Listing 3-26.Django custom filter directory structure

清单 3-26 显示了两个应用,它们在两个不同的文件中包含 Django 定制过滤器- store_formay.tf.pytax_operations.py。请记住,您需要在 Django app 文件夹中手动创建templatetags文件夹,还需要创建一个__init__.py文件,以便 Python 能够从这个文件夹中导入模块。此外,记住需要在 Django 的settings.py内的INSTALLED_APPS变量中定义应用,以便加载自定义过滤器。

在清单 3-26 中还有一个。py 文件- coffeehouse_filters.py -也包含 Django 自定义过滤器。最后一个定制过滤器文件是不同的,因为它位于一个名为common的通用文件夹中。为了让 Django 在一个通用位置找到一个定制的过滤器文件,您必须将它声明为settings.pyTEMPLATES变量的OPTIONSlibraries字段的一部分。有关使用“库”字段的详细说明,请参阅本章的第一节。

尽管自定义滤镜通常根据其功能放入文件和应用中,但这并不限制自定义滤镜在特定模板中的使用。您可以在任何 Django 模板上使用定制过滤器,而不管定制过滤器存储在哪里。

要在 Django 模板中使用 Django 定制过滤器,你需要在 Django 模板中使用{% load %}标签,如清单 3-27 所示。

{% load store_format_tf %}
{% load store_format_t tax_operations %}
{% load undercoffee from store_format_tf %}
Listing 3-27.Configure Django template to load custom filters

如清单 3-27 所示,有多种方式可以使用{% load %}标签。您可以使自定义文件中的所有过滤器对模板可用。py 在{% load %}标记语法中——或者一次包含多个定制文件。此外,您还可以使用类似 Python 的语法load filter from custom_file有选择地加载某些过滤器。记住{% load %}标签应该在模板的顶部声明。

Tip

如果您发现自己经常使用{% load %}标记,那么您可以使用 builtins 字段使定制过滤器对所有模板都可用。builtins 字段是 settings.py 中 TEMPLATES 变量选项的一部分。有关使用 builtins 字段的详细说明,请参见本章第一节 Django 模板配置。

Footnotes 1

https://en.wikipedia.org/wiki/Escape_character

  2

https://www.python.org/dev/peps/pep-0263/

  3

https://docs.python.org/3/library/codecs.html#standard-encodings

  4

https://docs.python.org/3/library/stdtypes.html#old-string-formatting

  5

https://docs.python.org/3/reference/expressions.html#evaluation-order

四、Django 的 Jinja 模板

除了 Django 模板,Django 框架还支持 Jinja 模板。Jinja 是一个独立的模板引擎项目 1 与 Django 的内置模板系统非常相似。

然而,Django 项目中 Jinja 模板的采用和增长部分是由于 Django 模板的设计限制,自 Django 创建以来,这些模板几乎没有变化。

金贾的优势和劣势

为了让您对 Jinja 模板有一个高层次的了解,并了解它们是否适合您的 Django 项目,我将首先列举 Jinja 模板的一些主要优点和缺点。先说优点:

  • 速度和性能。- Jinja 在首次加载时将模板源代码编译成 Python 字节码,因此模板只需解析一次,从而获得更好的运行时性能。此外,Jinja 还支持提前编译选项,这也可以获得更好的性能。尽管速度和性能是 Jinja 模板最有争议的优势,但考虑到影响速度和性能基准的许多因素(例如,数据库查询/负载、服务器配置)。一般来说,在所有条件相同的情况下,Jinja 模板做的事情和 Django 模板完全一样,Jinja 版本将比 Django 版本快。

Note

公平地说,Django 模板也支持带缓存的定制加载器,以提高速度和性能——如前一章所述——但这需要在 Django 中做进一步的配置工作。

  • 灵活性。- Jinja 模板在内容方面非常灵活,支持宏和更多类似 Python 的结构。虽然 web 模板不鼓励这些做法,但是您会逐渐喜欢上 Django 模板中没有的或者受到严重限制的一些特性。
  • 类似于 Django 模板。- Jinja 实际上是受 Django 模板的启发,所以这两个系统之间有很多共同点。模板继承和块等强大的特性也以同样的方式工作,所以在 Django 项目中使用 Jinja 的学习曲线比您可能意识到的要小。此外,安全特性(例如,自动转义)也紧密集成到了 Jinja 中,就像它们在 Django 模板中一样。
  • 异步执行。-模板有时会加载大量数据或使用需要长时间运行的功能,导致模板延迟,模板必须“等待”后台任务完成(即它们是同步的)。Jinja 模板支持异步执行,这允许支持任务运行它们的进程——没有保留模板——并且稍后在完成时用模板重新集合。注意该特性需要使用异步生成器, 2 ,这仅在 Python 3.6 或更新版本中可用。

现在还有一些金贾模板的缺点:

  • 很少或没有第三方软件包支持。-因为 Django 对 Jinja 模板的官方支持相对较新-从 Django 1.8 开始,这是本书所基于的 Django 1.11 的早期长期支持(LTS)版本-几乎所有第三方软件包(例如 Django admin)仍然是用 Django 模板设计的。这使得很难有一个纯粹的 Jinja 模板 Django 项目,并要求 Jinja 模板与 Django 模板共存,这反过来会在需要模板定制时导致困难和混乱。
  • 新概念。-如果你习惯了 Django 模板,一些 Jinja 特性需要额外的练习才能理解和正确使用(例如,Jinja 宏、Jinja 过滤器)。虽然如果你是 Django 的新手,这应该不是问题,因为每个概念都是新的,需要一些实践。

从 Django 模板过渡到 Jinja 模板

如果你习惯于使用 Django 模板,这一节描述了你在使用 Jinja 模板时需要注意的细节,比如你可以在 Jinja 模板中利用哪些 Django 模板知识,与 Django 模板相比,Jinja 模板的工作方式有什么不同,以及你需要学习哪些新的东西,你会逐渐喜欢上 Jinja 模板。

如果您从未使用过 Django 模板,您可以跳到下一节关于 Django 中 Jinja 模板配置的内容,因为接下来的大部分内容是为有经验的 Django 模板用户准备的。

Jinja 和 Django 模板中的工作方式是一样的

仅仅因为 Jinja 是一个完全不同的模板引擎,并不意味着它与 Django 的内置模板引擎完全不同。对于变量和块、条件和循环、注释以及空格和特殊字符,您可以使用相同的方法。

变量和块

花括号{}在 Jinja 模板中被广泛使用,就像在 Django 模板中一样。要在 Jinja 中输出一个变量,可以使用相同的{{myvariable}}语法。类似地,您也可以用{% block footer %} {% endblock %}语法命名块来继承模板之间的代码片段。此外,Jinja 还使用相同的 Django {% extends "base.html" %}语法来创建模板之间的父/子关系。

条件句和循环

Jinja 使用相同的 Django 语法创建条件:{ % if variable %}{% elif othervariable %}{% else %}{% endif %}。此外,Jinja 还使用了与 Django 相同的 for 循环语法:{% for item in listofitems %}{{item}}{% endfor %}

评论

Jinja 也使用了和 Django 一样的评论标签:{# This is a template comment that isn't rendered #}。然而,note Jinja 对单行和多行注释都使用了{# #}标记。

间距和特殊字符

由于 Jinja 模板的灵感来自 Django 模板,Jinja 使用类似的方法来处理空格和特殊字符。例如,间距过滤器(例如,centerwordwrap)和特殊字符处理(例如,safeescape过滤器)在 Jinja 模板中的工作方式与在 Django 模板中的相同。

与 Django 模板相比,Jinja 模板有什么不同

然而,在 Jinja 模板中,并不是所有的东西都以同样的方式工作;这里有一些 Django 模板技术,你需要重新学习使用 Jinja 模板。

过滤

尽管 Jinja 使用相同的管道符号|将过滤器应用于变量,但 Jinja 过滤器在技术上分为过滤器和测试。在 Django 模板中,只有执行测试的过滤器(例如,divisibleby),但在 Jinja 中,这些类型构造被称为测试,并使用条件语法{% if variable is test %}而不是标准管道符号|

此外,Jinja 过滤器和测试由标准方法支持。这样做的好处是,向 Jinja 过滤器和测试传递参数就像方法调用一样简单(例如,{{variable|filesizeformat(true)}}),而 Django 过滤器的参数语法不直观,需要使用冒号,甚至需要在自定义的 Django 过滤器中解析参数(例如,{{variable|get_digit:"1"}})。

除了与 Django 内置过滤器相似的内置 Jinja 过滤器和测试之外,还可以创建定制的 Jinja 过滤器和测试。然而,与通过{% load %}标签加载到模板中的 Django 过滤器不同,Jinja 定制过滤器和测试是全局注册的,可以像 Django 上下文处理器一样被所有 Jinja 模板访问。

上下文处理器

上下文处理器允许 Django 模板访问项目中每个模板的变量集合,但是在 Jinja 中,这种功能被称为全局变量。这是您可能会错过 Django 模板功能的一个方面,它只是简单地声明上下文处理器和访问变量集。然而,创建 Jinja 全局变量变得可以在所有 Jinja 模板上访问并充当 Django 上下文处理器是相对容易的。

没有像{% now %}标记这样的日期元素,也没有像 time 和 timesince 这样的过滤器

Jinja 在开箱即用状态下不提供标签或过滤器来处理日期或时间。尽管 Jinja 确实提供了format过滤器,其工作方式就像 Python 的标准方法一样,可以用于日期格式化,但是您需要编写自己的定制过滤器和标签,以更高级的方式处理日期和时间元素。

不支持{% comment %}标记

Jinja 使用{# #}标签来定义单行或多行注释,所以不支持{% comment %},在 Django 模板中,?? 用于多行注释。

不支持{% load %}标记

在 Jinja 中,不支持导入定制标签和过滤器的{% load %}标签。在 Jinja 中,自定义标签和过滤器是全局注册的,并自动可供所有 Jinja 模板访问。

使用{{super()}}而不是{{block.super}}

在 Django 模板中,使用语法{{ block.super }}来访问父模板块的内容。在 Jinja 中,您必须使用{{super()}}语法来访问父模板块的内容。

不支持{% csrf_token %}标记,请使用 csrf_input 或 csrf_token 变量

在 Django 模板中,当您创建一个具有 HTTP POST 动作的表单时,您将{% csrf_token %}标记放在表单体中,以生成一个避免 XSS 的特殊标记(“跨站点脚本”)。要在 Jinja 中复制这种行为,您必须使用csrf_input变量(例如,{{csrf_input}}生成类似<input type="hidden" name="csrfmiddlewaretoken" value="4565465747487">的字符串)或使用包含原始 CSRF 令牌的csrf_token变量(例如,4565465747487)。

{% for %}循环变量

在 Django 模板中,{% for %}循环的上下文提供了对一系列变量的访问(例如,计数器、第一次和最后一次迭代)。Jinja 模板在{% for %}的上下文中提供了一个类似的变量,但它们并不相同。

循环中不支持{% empty %}标记,请使用{% else %}标记

Django 模板中的循环支持将{% empty %}子句作为最后一个参数,以便在迭代为空时生成逻辑或消息。在 Jinja {% for %}循环中,当迭代为空时,可以使用{% else %}子句作为最后一个参数来生成逻辑或消息。

不支持{% groupby %}标记,请使用 groupby 筛选器

Django 模板支持{% groupby %}标签来根据不同的属性重新排列字典或对象。在 Jinja 中你可以实现同样的功能,但是你必须通过 Jinja groupby过滤器中描述的groupby过滤器来实现。

不支持{% cycle %}标记,请在{% for %}循环中使用 cycler 函数或 loop.cycle 变量

Django 模板支持{% cycle %}标签来循环遍历一系列值。在 Jinja 中,这种功能有两种形式。如果需要循环之外的功能,可以使用cycler方法。或者您可以使用所有{% for %}循环中可用的loop.cycle功能。

不支持{% lorem %}标记,请使用 lipsum 函数

Django 模板支持{% lorem %}标签来生成随机的拉丁文本作为填充内容。在 Jinja 中,您可以使用lipsum函数实现相同的功能。

不支持其他杂项标记,如{% static %}、{% trans %}、{% blocktrans %}和{% url %}

{% static %}{% trans %}这样的一系列 Django 模板标签在 Jinja 中根本就没有。然而,有些第三方项目已经将这些和许多其他 Django 模板标签移植到了 Jinja 扩展中。本章后面关于 Jinja 扩展的部分将讨论这些选项。

Jinja 模板与 Django 模板中的新概念和新特性

现在您已经知道了可以利用哪些 Django 模板知识,以及需要重新学习哪些技术才能有效地使用 Jinja 模板,让我们来看看一些只适用于 Jinja 模板的概念。

更有用的内置过滤器、测试,与 Python 环境更相似

Jinja 模板提供了 Django 模板中所缺少的各种内置过滤器和测试。例如,像检查变量类型这样简单的事情(例如,字符串、数字、iterable 等。),Jinja 为此提供了一系列内置测试,而在 Django 中,这需要创建自定义过滤器。

与 Django 模板相比,Jinja 模板对复杂数据类型(例如对象和字典)的访问和操作也得到了极大的改进。例如,Jinja 提供了诸如rejectselectmap之类的过滤器来修剪、过滤或更改模板上的数据子集,这种技术虽然被纯粹主义者(即那些只在视图中操作数据的袖手旁观人)所不喜欢,但在实际和时间受限的项目中却是一种非常常见的需求。

Jinja 模板还支持更符合标准 Python 环境的语法。例如,在 Django 中,像通过变量访问字典键这样的事情需要定制过滤器,而在 Jinja 模板中,这与标准 Python 语法一起工作(例如,如果您有变量stores={"key1":"value1", "key2":"value2"}var="key1",Django 模板不能执行标准 Python 语法的stores.get(var),但是在 Jinja 中,这与 Python 环境的预期一样开箱即用)。

全局函数

Jinja 还支持一系列全局函数。例如,Jinja 提供了range函数,它的工作方式就像 Python 中在循环中有用的标准函数一样(例如{% for number in range(50 - coffeeshops|count) %})。此外,Jinja 还提供了全局函数lipsum来生成虚拟占位符内容、dict来生成字典、cycler来生成元素循环、joiner来连接节。

灵活的标签嵌套、条件和引用

Jinja 在嵌套标签方面非常灵活,尤其是与 Django 模板相比。例如,在 Jinja 中,你甚至可以有条件地应用{% extends %}标签(例如{% if user %}{% extends "base.html" %}{% else %}{% extends "signup_base.html" %}{% endif %})或者使用带有内嵌条件的变量引用名称(例如{% extends layout_template if layout_template is defined else 'master.html' %})——这在 Django 模板中是不可能的。

宏指令

在 Jinja 中,宏允许定义具有复杂布局的类似函数的片段,可以从任何具有不同实例值的模板中调用这些片段。宏对于限制复杂布局在模板间的传播特别有用。使用宏,您可以一次定义一个复杂的布局(即,作为一个宏),并使用不同的参数调用它,以每次输出定制的复杂布局,就像是一个函数一样。

范围限制较少的模板中的灵活变量赋值

在 Jinja 中,您可以使用{% set %}标签来定义变量,使其在模板结束之前都具有有效的作用域。尽管 Jinja 也支持{% with %}标签——就像 Django 模板版本一样——{% with %}标签对于多个变量定义来说可能会变得很麻烦,因为它需要每次都用{% endwith %}来结束作用域。对于全局模板变量来说,{% set %}是一个很好的选择,因为您只需要初始定义,作用域传播到模板的末尾,而不必担心关闭作用域。

行语句

Jinja 在其所谓的行语句中支持逻辑语句的定义。默认情况下,line 语句前面有一个#符号,可以作为标记语法的替代。例如,{% for %}标记语句{% for item in items %}可以使用等价的行语句# for item in items,正如标记语句{% endfor %}可以使用等价的行语句# endfor.行语句,更重要的是,与使用需要{% %}语法的标记语句相比,给模板一种 Python 的感觉,可以使复杂的逻辑更容易破译。

Django 中的 Jinja 模板配置

在 Django 中使用 Jinja 的第一步是用命令pip install Jinja2安装核心包。请注意,安装的是版本 2(即 Jinja2),这是最新的版本。虽然 Jinja 1 仍然可用,但是 Django 不提供对版本 1 的内置支持,所以要特别注意确保安装版本 2。

接下来,您需要在settings.py文件中的 Django 项目中配置 Jinja。清单 4-1 展示了 Django 的一个基本 Jinja 配置。

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND':'django.template.backends.jinja2.Jinja2',
        'DIRS': ['%s/jinjatemplates/'% (PROJECT_DIR),],
        'APP_DIRS': True,
        },
    {
        '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',
            ],
        },
    },
]

Listing 4-1.Jinja configuration in Django settings.py

正如您在清单 4-1 中看到的,在TEMPLATES中声明了两个配置,一个是 Jinja 模板配置的字典,另一个是默认 Django 模板配置的字典。由于 Django 模板仍然被诸如 Django admin 和许多第三方软件包使用,我强烈推荐你使用清单 4-1 中的基本配置,因为它可以防止你无法控制的其他东西被破坏。

清单 4-1 中的金贾配置是最基本的配置之一。在这种情况下,BACKEND变量使用django.template.backends.jinja2.Jinja2值来激活 Jinja 模板,紧接着是DIRSAPP_DIRS变量,它们告诉 Django 在哪里定位 Jinja 模板。

模板搜索路径

APP_DIRS变量允许在名为jinja2的特殊应用子目录中查找模板。如果您希望将 Jinja 模板包含到应用中,这是很有帮助的,但是要注意模板搜索路径并不知道应用的名称空间。例如,如果你有两个应用都依赖于一个名为index.html的模板——如清单 4-2 所示——并且两个应用都在views.py中有一个方法将控制权返回给index.html模板(例如render(request,'index.html')),那么两个应用都将使用INSTALLED_APPS中最顶层声明的应用中的index.html,因此一个应用不会使用预期的index.html

# Templates directly under jinja2 folder can cause loading conflicts
+---+-<PROJECT_DIR_project_name_conflict>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-jinja2-+
    |                     |
    |                     +-index.html
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-jinja2-+
                          |
                          +-index.html

# Templates classified with additional namespace avoid loading conflicts
+---+-<PROJECT_DIR_project_name_namespace>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-jinja2-+
    |                     |
    |                     +-about-+
    |                             |
    |                             +-index.html
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-jinja2-+
                          |
                          +-stores-+
                                   |
                                   +-index.html

Listing 4-2.Django apps with jinja2 dirs with potential conflict and namespace qualification

为了解决这个潜在的冲突,推荐的做法是在每个jinja2目录中添加一个额外的子文件夹作为名称空间,如清单 4-2 中的第二组文件夹所示。这样,您就可以使用这个额外的命名空间子文件夹将控件重定向到模板,以避免任何歧义。因此,要将控制权发送给about/index.html模板,您应该声明render(request,'about/index.html'),要将控制权发送给stores/index.html,您应该声明render(request,'about/index.html')

如果您希望禁止这种允许从这些内部应用子文件夹加载模板的行为,您可以通过将APP_DIRS设置为FALSE来实现。

Jinja 模板更常见的方法是用一个或多个文件夹——位于应用结构之外——来保存 Jinja 模板。Django 首先在第一个DIRS值中寻找匹配的 Jinja 模板,然后在应用的jinja2文件夹中寻找——如果APP_DIRSTRUE——直到找到匹配的模板或者抛出TemplateDoesNotExist错误。

对于清单 4-1 所示的情况,唯一的DIRS值依赖于一个名为jinjatemplates的目录,该目录相对于由PROJECT_DIR变量确定的路径。当在不同的机器上部署 Django 项目时,这种可变技术很有帮助,因为路径是相对于顶层 Django 项目目录的(即settings.py和主urls.py文件所在的位置),并且不管 Django 项目安装在哪里(例如/var/www//opt/websiteC://website/),都会动态调整。

与 Django 模板OPTIONS变量类似,Jinja 也通过OPTIONS变量支持一系列定制。在 Jinja 的例子中,OPTIONS变量是一个键值字典,对应于 Jinja 环境的初始化参数。 3

默认情况下,Django 在内部设置一系列 Jinja 环境初始化参数,以使 Jinja 的模板行为与 Django 模板的行为一致。然而,您可以很容易地用OPTIONS变量覆盖这些设置。接下来的部分描述了这些重要的设置。

自动转义行为

Django 默认启用 Jinja 模板自动转义,这种行为在 Jinja 引擎的开箱即用状态下实际上是禁用的。自动转义的症结在于,一方面,它在预防和安全性方面出错——限制了在 HTML 中破坏输出或引入 XSS(跨站点脚本)漏洞的可能性——但另一方面,它也在模板引擎中引入了额外的处理,这会导致性能问题。

默认情况下,Django 模板自动转义模板变量的所有输出——<被转换为&lt; , >被转换为&gt; , '(单引号)被转换为' , "(双引号)被转换为&quot;并且&被转换为&amp –,除非您明确禁用此行为。Jinja 在开箱即用状态下不会自动转义任何东西,当您想要自动转义某些东西时,您需要明确地告诉它。

因为 Django 的 Jinja 模板集成是由 Django 设计者完成的,所以为了安全起见,Jinja 自动转义被启用,就像 Django 模板一样。但是,您可以使用OPTIONS中的autoescape参数禁用 Jinja 自动转义,如清单 4-3 所示。

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    {
        'BACKEND':'django.template.backends.jinja2.Jinja2',
        'DIRS': ['%s/jinjatemplates/'% (PROJECT_DIR),],
        'APP_DIRS': True,
        'OPTIONS': {
            'autoescape': False
        },
    }
]

Listing 4-3.Jinja disable auto-escaping in Django

正如你在清单 4-3 中看到的,autoescape被赋值为False,随着这一变化,Jinja 模板的行为就像 Jinja 设计者想要的那样(也就是说,你需要明确地检查哪里需要自动转义,而 Django 模板检查哪里不需要自动转义)。

自动重新加载模板行为和缓存

在开箱即用状态下,Jinja 的模板加载器会在每次请求模板时进行检查,以查看源代码是否发生了变化;如果已经更改,Jinja 会重新加载模板。这在模板的源代码不断变化的开发中很有帮助,但在生产中也会影响性能,因为模板的源代码很少变化,检查会导致延迟。

默认情况下,Django 框架 Jinja 集成采用了一种明智的方法,并基于settings.py中的DEBUG变量启用 Jinja 模板自动重载。如果DEBUG=True -开发中的常用设置- Jinja 模板自动重装设置为True,如果DEBUG=False -生产中的常用设置- Jinja 模板自动重装设置为False。然而,您可以用OPTIONS中的auto_reload参数显式设置 Jinja 的自动加载行为。

默认情况下,Jinja 引擎还可以缓存多达 400 个模板。这意味着当加载模板 401 时,Jinja 清除最近最少使用的模板,如果以后需要,后者必须从其原始位置重新加载。Jinja 缓存限制可通过OPTIONS中的cache_size参数进行调整(如cache_size=1000,设置 1000 个模板缓存)。将cache_size设置为 0(零)禁用缓存,将cache_size设置为-1 启用无限制缓存。

Jinja 模板中另一个可用的缓存机制是字节码缓存。当您创建 Python 源文件(即那些带有.py扩展名的文件)时,Python 会生成带有包含字节码的.pyc扩展名的镜像文件。生成这些字节码文件需要时间,但它们是 Python 运行时过程的自然组成部分。基于 Python 的 Jinja 模板也需要转换成字节码,但这是一个你可以用OPTIONS中的bytecode_cache参数定制的过程。

可以为bytecode_cache参数分配一个定制的字节缓存 4 或者 Jinja 的一个内置字节码缓存,它包括对标准文件系统缓存的支持或者使用 memcached 的更专门的缓存。

无效的模板变量

当在 Jinja 模板中遇到无效变量时,您可以设置各种行为。Django 为 Jinja 设置了两个默认行为,一个用于 whenDEBUG=True——开发中的常见设置——另一个用于 whenDEBUG=False——生产中的常见设置。

如果在 Jinja 模板中设置了DEBUG=True和一个无效变量,Jinja 使用jinja2.DebugUndefined类来处理它。jinja2.DebugUndefined类逐字输出变量进行呈现(例如,如果模板有{{foo}}语句,而变量在上下文中不存在,Jinja 输出{{foo}},这样更容易发现无效变量)。

如果在 Jinja 模板中设置了DEBUG=False和一个无效变量,Jinja 使用jinja2.Undefined类来处理它。jinja2.Undefined类在变量的位置输出一个空格用于呈现(例如,如果模板有{{bar}}语句,而变量在上下文中不存在,Jinja 输出一个空格)。值得一提的是,最后一种行为符合 Django 模板中无效变量的默认行为。

除了jinja2.DebugUndefinedjinja2.Undefined类,Jinja 还支持jinja2.StrictUndefined类。jinja2.StrictUndefined类用于生成即时错误,而不是继续渲染,这有助于更快地诊断无效变量。然而,要注意最后一个类基于DEBUG变量改变了它的行为;它要么生成一个带有无效变量名的堆栈错误(即当DEBUG=True时),要么生成一个标准的 HTTP 500 错误页面(即当DEBUG=False时)。

清单 4-4 展示了如何通过settings.py中的OPTIONS参数配置一个 Jinja 类来处理无效变量。

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

import jinja2

TEMPLATES = [
   {
        'BACKEND':'django.template.backends.jinja2.Jinja2',
        'DIRS': ['%s/jinjatemplates/'% (PROJECT_DIR),],
        'APP_DIRS': True,
        'OPTIONS': {
            'undefined':jinja2.StrictUndefined
        },
    }
]

Listing 4-4.Generate error for invalid variables in Jinja with jinja2.StrictUndefined

正如你在清单 4-4 中看到的,我们首先声明import jinja2来访问settings.py中 Jinja 的类。接下来,我们在OPTIONS参数中声明了undefined键,并将其分配给 Jinja 类来处理无效变量。在这种情况下,当遇到无效模板变量时,我们使用jinja2.StrictUndefined类来获取错误,但是您同样可以使用其他两个 Jinja 类中的任何一个来处理无效变量(即jinja2.DebugUndefinedjinja2.Undefined)。

模板加载器

Jinja 模板加载器是 Python 类,它实现了搜索和加载模板所需的实际逻辑。在前面的“模板搜索路径”一节中,我描述了 Jinja 如何使用DIRSAPP_DIRS变量搜索模板,这是 Django 模板配置的一部分。然而,我有意忽略了与这个模板搜索过程相关的一个更深层的方面:每个搜索机制都由一个模板加载器支持。

在大多数情况下,你不需要处理 Jinja 模板加载器,因为 Jinja 加载器是在后台处理的,只需要依靠DIRSAPP_DIRS变量。但是如果您需要从这些位置之外的地方加载 Jinja 模板(例如,从内存结构或数据库),您可以在OPTIONS参数中用loader键指定模板加载器。

像 Django 模板加载器一样,除了使用类似于 Django 模板提供的内置 Jinja 模板加载器(例如,从 Python 字典加载模板),Jinja 还提供创建定制模板加载器、【5】的能力。

Tip

您可以在选项中为任何 Jinja 环境初始化参数 6 设置自定义值。前面的部分只是四个最常见的 Jinja 模板参数;后面的部分描述了其他可用的选项。

Note

OPTIONS 仅用于 Jinja 环境的初始化参数;其他 Jinja 环境设置需要配置单独的 Jinja 环境类(例如,Jinja 全局、Jinja 自定义过滤器和测试以及 Jinja 策略)。

创建可重用的 Jinja 模板

模板倾向于具有在多个实例中同等使用的公共部分。例如,无论一个项目有 5 个还是 100 个模板,所有模板的页眉和页脚部分很少改变。其他模板部分,如菜单和广告,也属于这种在多个模板中保持不变的内容类别。所有这些都会导致多个模板的重复,这可以通过创建可重用的模板来避免。

使用可重用的 Jinja 模板,您可以在单独的模板上定义公共部分,并在其他模板中重用它们。此过程使创建和管理项目模板变得容易,因为单个模板更新会影响所有模板。

可重用的 Jinja 模板还允许您定义页面块来逐页覆盖内容。此过程使项目的模板更加模块化,因为您定义了顶级块来建立整体布局并逐页定义内容。

让我们通过探索 Jinja 内置的{% block %}标签,向构建可重用的 Jinja 模板迈出第一步。清单 4-5 展示了一个名为base.html的带有几个{% block %}标签的模板的第一行。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>{% block title%}Default title{% endblock title %}</title>
    <meta name="description" content="{% block metadescription%}{% endblock metadescription %}">
    <meta name="keywords" content="{% block metakeywords%}{% endblock metakeywords %}">
Listing 4-5.Jinja template with {% block %} tags

注意清单 4-5 中的语法{% block <name>%}{% endblock <name>%}。每个{% block %}标签都有一个引用名。其他 Jinja 模板使用该引用名称来覆盖每个块的内容。例如,HTML <title>标签中的{% block title %}标签定义了一个网页标题。如果另一个模板重用清单 4-5 中的模板,它可以通过覆盖标题块来定义自己的网页标题。如果未在模板上覆盖块,则块将接收块中的默认内容。对于标题栏,默认内容是Default title,对于metadescriptionmetakeywords栏,默认内容是一个空字符串。

清单 4-5 中所示的相同机制可以用来定义任意数量的块(例如,内容、菜单、页眉、页脚)。值得一提的是{% endblock <name> %}<name>参数是可选的,仅使用{% endblock %}来结束一个 block 语句是有效的;但是,前一种技术使得 block 语句的结束位置更加清晰,这在一个模板有多个块时尤其有用。

尽管可以通过 Django 视图方法或 url 请求直接调用清单 4-5 中的模板,但这种模板的目的是将其用作其他模板的基础模板。要重用 Jinja 模板,您可以使用 Jinja 内置的{% extends %}标签。

{% extends %}标签使用语法{% extends <name> %}来重用另一个模板的布局。这意味着为了重用在文件base.html中定义的清单 4-5 中的布局,您使用语法{% extends "base.html" %},如清单 4-6 所示。

{% if user %}{% extends "base.html" %}{% else %}{% extends "signup_base.html" %}{% endif %}
{% block title %}Coffeehouse home page{% endblock %}
Listing 4-6.Jinja template with {% extends %} and {% block %} tag

看看清单 4-6 如何使用包裹在{% if user %}语句周围的{% extends "base.html" %}。如果定义了user变量,Jinja 扩展了base.html模板;否则它扩展了signup_base.html模板。这种条件语法在 Django 模板中是不可能的。

此外,注意清单 4-6 是如何用内容Coffeehouse home page定义{% block title %}标签的。清单 4-6 中的块覆盖了base.html模板中的title块。那么清单 4-6 中的 HTML <title>标签在哪里呢?没有,你也不需要。Jinja 自动重用来自base.htmlsignup_base.html模板的布局,并在必要的地方替换块的内容。

重用其他模板的 Jinja 模板倾向于使用有限的布局元素(例如 HTML 标签)和更多的 Jinja 块语句来覆盖内容。这是有益的,因为正如我前面所概述的,它允许您一次建立整体布局,并在逐页的基础上定义内容。

Jinja 模板的可重用性可以多次出现。例如,你可以有模板 A、B 和 C,其中 B 需要重用 A,但是 C 需要重用 B 的一部分,唯一的区别是模板 C 需要使用{% extends "B" %}标签而不是{% extends "A"%}标签。但是由于模板 B 重用了 A,模板 C 也可以访问模板 A 中的相同元素。

当重用 Jinja 模板时,也可以从父模板访问块内容。Jinja 通过super()方法公开父模板中的块内容。清单 4-7 展示了三个模板,展示了包含网页路径或“面包屑”的块的这种机制。

# base.html template
<p>{% block breadcrumb %}Home{% endblock %}</p>

# index.html template
{% extends "base.html" %}
{% block breadcrumb %}Main{% endblock %}

# detail.html template
{% extends "index.html" %}
{% block breadcrumb %} {{super()}} : Detail {% endblock %}

Listing 4-7.Jinja templates use of super() with three reusable templates

清单 4-7 中的base.html模板用默认值Home定义了breadcrumb块。接下来,index.html模板重用base.html模板并用值Main覆盖面包屑块。最后,detail.html模板重用index.html模板并覆盖breadcrumb块值。但是,请注意最终块覆盖中的{{super()}}语句。因为{{super()}}breadcrumb块中,{{super()}}告诉 Jinja 从父模板块中获取内容。

Jinja 模板支持的另一个可重用功能是将一个 Jinja 模板包含在另一个 Jinja 模板中。Jinja 通过{% include %}标签支持这一功能。

默认情况下,{% include %}标签需要一个模板的名称。例如,{% include "footer.html" %}在声明模板的位置插入footer.html模板的内容。{% include %}标签也让底层模板知道变量。这意味着footer.html模板可以有变量定义(例如{{year}},如果调用模板有这些变量定义,{% include %}标签会自动替换这些值。

此外,还可以提供一个模板列表作为后备机制。例如,{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}告诉 Jinja 首先尝试定位special_sidebar.html模板,如果没有找到就尝试定位sidebar.html模板;如果两个模板都没有找到,最后一个参数ignore missing告诉 Jinja 不要渲染任何东西。注意ignore missing参数也可以用在单独的语句中(例如{% include "footer.html" ignore missing %},以及列表)。此外,如果没有使用ignore missing语句,并且 Jinja 找不到在{% include %}中声明的匹配模板,Jinja 会引发一个异常。

标签允许跨模板定义可重用的内容片段。例如,如果您需要合并精细标记来显示具有共同特征的元素,您可以在一个{% macro %}语句中定义一次精细标记,然后重用这个{% macro %}来输出为每个元素实例定制的标记。

宏非常有用,因为如果您决定更改标记,只需在一个位置进行更改,更改就会传播到其他位置。清单 4-8 展示了{% macro %}语句的定义及其在模板中的用法。

# base.html template
{% macro coffeestore(name, id='', address='', city='San Diego', state='CA', email=None) -%}
    <a id="{{id}}"></a>
    <h4>{{name}}</h4>
    <p>{{address}} {{city}},{{state}}</p>
    {% if email %}<p><a href='mailto:{{email}}'>{{email}}</a></p>{% endif %}
{%- endmacro %}

# index.html template calls inherited macro directly
{% extends "base.html" %}
{{coffeestore('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}}
# detail.html template with no extends, uses {% import %} to access macro in base.html
{% import 'base.html' as base %}
{{base.coffeestore('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}}

# otherdetail.html template with no extends, uses {% from import %} to access macro in base.html
{% from 'base.html' import coffeestore as mycoffeestoremacro %}
{{mycoffeestoremacro('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}}

Listing 4-8.Jinja {% macro %} definition and use of {% import %}

清单 4-8 中做的第一件事是在base.html模板中声明的{% macro %}定义。注意,在{% macro片段之后,有一个名为coffeestore的常规方法,它对应于带有六个输入参数的宏的名称,其中五个有默认值。接下来,在{% macro %}{% endmacro %}语句中,您可以看到一些精心制作的 HTML 标记,它们利用标准的{{ }}语法来输出在给定宏实例上传递的任何变量值。

由于清单 4-8 中的{% macro %}是在base.html模板中定义的,任何其他使用base.html模板的模板都可以访问该宏,并调用带有实例的宏(例如{{coffeestore('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}} -为简单起见是硬编码的值)来呈现用实例值定制的 HTML 标记。

如果你想在其他模板中访问一个{% macro %},你有三个选择,也在清单 4-8 中给出。如果一个模板扩展了另一个模板(如{% extends "base.html" %}),那么默认情况下,它也将获得对父模板{% macro %}定义的访问权。也可以用{% import %}语句访问另一个模板的{% macro %}定义。例如,语句{% import 'base.html' as base %}base.html定义导入到另一个具有基本名称空间的模板中,在这种情况下,要调用名为coffeestore{% macro %},您将使用{{base.coffeestore(...}}语法。最后,也可以用{% from import %}语句有选择地导入{% macro %}定义。例如,语句{% from 'base.html' import coffeestore as mycoffeestoremacro %}base.html模板导入coffeestore定义,并将其放在mycoffeestoremacro名称下,在这种情况下,您将使用{{mycoffeehousemacro(...}}语法来调用{% macro %}

{% call %}标签是另一个选项,与{% macro %}标签一起使用,有利于宏本身的可重用性。{% call %}标签的第一个使用场景是调用一个{% macro %},它需要一个占位符,用于在调用宏之前定义的内容。清单 4-9 展示了{% call %}标签和{% macro %}标签的基本场景。

# macro definition
{% macro contentlist(adcolumn_width=3,contentcolumn_width=6) -%}
   <div class="col-md-{{adcolumn_width}}">
    Sidebar ads
   </div>
   <div class="col-md-{{contentcolumn_width}}">
      {{ caller() }}
   </div>
   <div class="col-md-{{adcolumn_width}}">
    Sidebar ads
   </div>
{%- endmacro %}

# macro call/invocation
{% call contentlist() %}
  <ul>
    <li>This is my list</li>
  </ul>
{% endcall %}

# rendering
<div class="col-md-3">
    Sidebar ads
</div>
<div class="col-md-6">
  <ul>
    <li>This is my list</li>
  </ul>
</div>
<div class="col-md-3">
    Sidebar ads
</div>

Listing 4-9.Jinja {% call %} and {% macro %}

use

在清单 4-9 中,我们首先定义一个与清单 4-8 结构相似的{% macro %};但是,请注意在{% macro %}中的{{ caller() }}语句。{% macro %}中的caller()方法作为占位符被调用实体替换。

接下来,在清单 4-9 中,您可以看到{% call %}语句是用宏调用声明的——在本例中是contentlist(){% call %}语句的主体包含一个 HTML 列表。当 Jinja 执行{% call %}语句时,{% call %}内容被放在{% macro %} {{caller()}}声明的位置。

带有{% macro %}{% call %}标签的一个更高级的场景是让caller()语句使用引用,这个过程对于本质上是递归的数据来说更自然(例如,宏在宏之上)。清单 4-10 展示了{% call %}标签和{% macro %}的递归场景。

# macro definition
{% macro contentlist(itemlist,adcolumn_width=3,contentcolumn_width=6) -%}
   <div class="col-md-{{adcolumn_width}}">
    Sidebar ads
   </div>
   <div class="col-md-{{contentcolumn_width}}">
     {% for item in itemlist %}
      {{ caller(item) }}
     {% endfor %}
   </div>
   <div class="col-md-{{adcolumn_width}}">
    Sidebar ads
   </div>
{%- endmacro %}

# variable definition
{% set coffeestores=[{'id':0,'name':'Corporate','address':'624 Broadway','city':'San Diego','state':'CA','email':'corporate@coffeehouse.com'},{'id':1,'name':'Downtown','address':'Horton Plaza','city':'San Diego','state':'CA','email':'downtown@coffeehouse.com'},{'id':2,'name':'Uptown','address':'1240 University Ave','city':'San Diego','state':'CA','email':'uptown@coffeehouse.com'},{'id':3,'name':'Midtown','address':'784 W Washington St','city':'San Diego','state':'CA','email':'midtown@coffeehouse.com'}] %}

# macro call/invocation
{% call(item) contentlist(coffeestores) %}
    <a id="{{item.id}}"></a>
    <h4>{{item.name}}</h4>
    <p>{{item.address}} {{item.city}},{{item.state}}</p>
    {% if item.email %}<p><a href='mailto:{{item.email}}'>{{item.email}}</a></p>{% endif %}
{% endcall %}

# rendering
<div class="col-md-3">
    Sidebar ads
</div>
<div class="col-md-6">
    <a id="0"></a>
    <h4>Corporate</h4>
    <p>624 Broadway San Diego,CA</p>
    <p><a href="mailto:corporate@coffeehouse.com">corporate@coffeehouse.com</a></p>

    <a id="1"></a>
    <h4>Downtown</h4>
    <p>Horton Plaza San Diego,CA</p>
    <p><a href="mailto:downtown@coffeehouse.com">downtown@coffeehouse.com</a></p>

    <a id="2"></a>
    <h4>Uptown</h4>
    <p>1240 University Ave San Diego,CA</p>
    <p><a href="mailto:uptown@coffeehouse.com">uptown@coffeehouse.com</a></p>

    <a id="3"></a>
    <h4>Midtown</h4>
    <p>784 W Washington St San Diego,CA</p>
    <p><a href="mailto:midtown@coffeehouse.com">midtown@coffeehouse.com</a></p>
</div>
<div class="col-md-3">
    Sidebar ads
</div>

Listing 4-10.Jinja {% call %} and {% macro %} recursive calls

正如您在清单 4-10 中看到的,{% macro %}定义现在有了一个名为itemlist的参数,它在这个参数上创建了一个迭代,并为每个条目调用了{{caller(item)}}。还要注意清单 4-10 中的{% call %}语句现在是{% call(item) contentlist(coffeestores) %},其中 item 表示从宏发送的回调项,而contentlist(coffeestores)是对名为contentlist的宏的实际调用及其输入coffeestores,这是一个字典列表。当 Jinja 执行{% call %}语句时,{% call %}内容在每一项上递归运行,结果输出显示在清单 4-10 的底部。

Tip

与使用变量的{% macro %}语句相比,Jinja 内置过滤器一节中描述的内置{% set %} statemen t 为静态内容块提供了更简单的重用功能。(如{ % set advertisement % }