Django-入门指南-全-

Django 入门指南(全)

原文:Beginning Django

协议:CC BY-NC-SA 4.0

一、Django 框架介绍

Django 框架始于 2003 年,是由美国堪萨斯州劳伦斯的《世界日报》的 Adrian Holovaty 和 Simon Willison 完成的一个项目。2005 年,Holovaty 和 Willison 发布了该框架的第一个公开版本,并以比利时-法国吉他手坦哥·雷恩哈特的名字命名。

快进到 2017 年——Django 框架现在在 Django 软件基金会(DSF)的指导下运行,框架核心有超过 1000 个贡献者,有超过 15 个发布版本,有超过 3000 个专门设计用于 Django 框架的包。 1

Django 框架仍然忠实于它的起源,即模型-视图-控制器(MVC)服务器端框架,设计用于操作关系数据库。尽管如此,Django 仍然通过第三方包紧跟大多数 web 开发趋势,与非关系数据库(NoSQL)、实时互联网通信和现代 JavaScript 实践等技术一起运行。所有这些都说明了一点,Django 框架现在是许多组织选择的 web 开发框架,包括照片共享网站 Instagram 2 和 Pinterest3;公共广播系统4;美国国家地理5;而借助这本书,你的组织!

在这一章中,你将学习 Django 框架设计原则,这是理解日常使用 Django 框架的关键。接下来,您将学习如何以各种方式安装 Django:作为 tar.gz 文件,使用 pip,使用 git,以及使用 virtualenv。

一旦安装了 Django 框架,您将学习如何启动一个 Django 项目,以及如何用关系数据库来设置它。接下来,您将了解 Django 框架中的核心构件——URL、模板和应用——以及它们如何相互协作来设置内容。最后,您将学习如何设置 Django 管理站点,这是一个基于 web 的界面,用于访问连接到 Django 项目的关系数据库。

Django 框架设计原则

如果你在 web 开发领域工作了足够长的时间,你最终会得出这样的结论:你可以用任何 web 框架和编程语言产生相同的结果。但是,虽然您实际上可以产生相同的结果,但是差异很大的是您创建解决方案所花费的时间:创建原型的时间、添加新功能的时间、进行测试的时间、进行调试的时间、部署到规模的时间等等。

从这个意义上来说,Django 框架使用了一套设计原则,与许多其他 web 框架相比,它产生了最有生产力的 web 开发过程之一。注意,我并不是说 Django 是银弹(例如,最好的原型,最具伸缩性);我是说,最终,Django 框架包含了一组设计原则和权衡,使其成为构建大多数大中型 web 应用所需特性的最有效的框架之一。

现在,虽然你可能认为我有偏见——毕竟我正在写一本关于这个主题的书——我将首先列出这些设计原则,这样你可以更好地理解是什么赋予了 Django 框架这种优势。

不重复自己(干)的原则

重复可能有助于强调一个观点,但是对于 web 开发来说,这只会导致额外的耗时工作。事实上,web 开发的本质是跨多个交互层(例如,HTML 模板、业务逻辑方法和数据库)进行操作,这本身就是重复的。

Django 框架确实试图强迫你不要重复自己,所以让我们看看 Django 是如何强制不要重复自己的,以及为什么这是一件好事。

假设您想要构建一个 coffeehouse 应用来发布关于商店的信息,并为客户提供一个联系表单。你需要做的第一件事是确定商店和联系表格需要什么样的信息。图 1-1 展示了每个实体的两个 Django 模型的实体模型。

A441241_1_En_1_Fig1_HTML.jpg

图 1-1。

Django models for store and contact entities

请注意图 1-1 中的 Django 模型每个都有不同的字段名称和数据类型来限制值。例如,语句name = models.CharField(max_length=30)告诉 Django 商店名称应该最多包含 30 个字符,而语句email = models.EmailField()告诉 Django 联系人实体应该包含有效的电子邮件值。如果咖啡馆像大多数 web 应用一样,您通常会为商店和联系人实体做以下事情:

  • 创建关系数据库表来保存实体信息。
  • 创建业务逻辑以确保实体符合需求。
  • 创建 HTML 表单以允许为实体提交数据。
  • 创建允许管理用户访问数据库中实体的界面。
  • 创建 REST 服务来公开移动应用版本的实体。

完成这最后一个任务列表的关键是,您可能会在数据库定义语言(DDL)、HTML 表单、业务验证逻辑和 URL 等方面重复许多类似的信息(例如名称、值限制),这一过程不仅耗时,而且容易出错。

基于像models.CharField(max_length=30)这样的语句,您可以生成一个 HTML 表单输入,一个 DDL 语句,并自动验证信息只包含 30 个字符,这不是更容易吗?这正是 Django 的干设计原则所做的。

图 1-2 展示了与图 1-1 相同的 Django 模型,以及您可以从相同的模型中生成的各种构造,而无需重复。

A441241_1_En_1_Fig2_HTML.jpg

图 1-2。

Django models create separate constructs based on DRY principle

正如您在图 1-2 中看到的,代表 Django 模型的实体能够生成向公众展示的 HTML 表单、管理实体的管理界面、实施实体值的验证逻辑,以及生成代表实体的数据库表的 DDL。

虽然现在讨论从 Django 模型生成这种结构的实际技术还为时过早,但不用说,这比在 HTML 表单、DDL、验证逻辑和其他位置跟踪同一事物(例如,姓名、电子邮件)的多个引用要简单得多。

从这个意义上说,Django 真正帮助你在一个地方定义事物,而不必在其他地方重复它们。注意,总是有可能重复自己来获得定制的行为,但是在默认情况下,Django 在几乎所有你用它做的事情中都执行 DRY 原则。

显性比隐性好

Django 使用的编程语言 Python 有一个类似咒语的声明,称为“Python 的禅”,被定义为该语言的 Python 增强建议(PEP)的一部分,特别是 PEP 20。PEP 20 中的一个声明是“显式比隐式更好”,并且 Django 是基于 Python 的,这个原则也被牢记在心。

显式导致 web 应用容易被更多的人理解和维护。对于最初没有编写 web 应用的人来说,添加新功能或理解 web 应用背后的逻辑可能已经够难了,但是如果您加入了具有隐式行为的 mix 构造,用户在试图弄清楚隐式地做了什么时只会面临更大的挫折。Explicit 确实需要更多的输入工作,但是当您将它与您可能面临的调试或解决问题的潜在努力相比较时,这是非常值得的。

让我们快速看一下 Django 在一个跨不同 MVC 框架使用的通用 web 开发结构中的显式性:一个视图方法。视图方法充当 MVC 框架中的 C(controller ),负责处理传入的请求,应用业务逻辑,然后用适当的响应路由请求。

为了更好地理解这种明确性,我将给出一个 Django 视图方法和一个等价的 Ruby on Rails 视图方法,这两个方法执行相同的逻辑,即通过给定的 id 获取存储并将响应路由到模板。以下代码片段是 Ruby on Rails 版本;请注意带有#的行,它们是注释,表示正在发生的事情。

class StoresController < ApplicationController
  def show
    # Automatic access to params, a ruby hash with request parameters and view parameters
    @store = Store.find(params[:id])
    # Instance variables like @store are automatically passed on to view template
    # Automatically uses template views/stores/show.html.erb
  end
end

尽管非常简洁,但请注意访问数据、将数据传递给模板和分配模板的过程中的所有隐式行为。下面的代码片段是一个等价的 Django 视图方法。

# Explicit request variable contains request parameters
# Other view parameters must be explicitly passed to views
def detail(request, store_id):
    store = Store.objects.get(id=store_id)
    # Instance variables must be explicitly passed on to a view template
    # Explicit template must be assigned
    return render(request, 'stores/detail.html', {'store': store})

请注意,在最后这段代码中,没有猜测输入参数来自哪里,它们被显式声明为 view 方法中的参数。此外,值被显式传递给模板,并且模板也被显式声明,因此逻辑对新来者来说更加友好。

Ruby on Rails 视图方法的隐含性通常被称为“魔力”,甚至被许多人认为是一种特性。之所以称之为‘魔法’,是因为某些行为是在幕后提供的。然而,除非你对框架和应用了如指掌,否则很难确定为什么会发生某些事情,这使得修复或更新变得更加困难。因此,尽管“魔术”可能在开始时为您节省几分钟或几个小时的开发时间,但它最终会花费您几个小时或几天的维护时间。

所以就像在 Python 中一样,Django 框架总是偏爱显式方法而不是隐式技术。

需要指出的是,显式不等于冗长或多余。虽然与隐式驱动的 web 框架(例如 Rails)相比,您最终肯定会在 Django 中键入更多的代码,但正如在前面的 DRY principle 部分中所描述的那样,Django 框架尽力避免在 web 应用中引入不必要的代码。

最后,显式也不意味着没有默认值。Django 框架尽可能使用合理的缺省值,只是在不明显的地方不使用缺省值。本质上,Django 框架使用默认值,但是避免使用会产生“神奇”结果的默认值。

松散耦合架构

Django 框架是一个 MVC 框架,跨多个层运行(例如,HTML 模板、业务逻辑方法和数据库)。然而,Django 非常注意维护跨这些层运行的所有组件的松散耦合架构。

松散耦合意味着组成 Django 应用的各个部分之间没有严格的依赖关系。例如,在 Django 中,直接从 HTML 模板提供内容是完全有效的,不需要使用业务逻辑或建立数据库。就像在 Django 中一样,放弃使用 HTML 模板并直接从业务逻辑方法返回原始数据也是完全有效的(例如,对于 REST 服务)。

本章后面的“设置内容:理解 URL、模板和应用”一节将更详细地举例说明 Django 的松散耦合架构是如何工作的。

安装 Django

安装 Django 框架有多种方法。你可以从 Django 的主站点 7 下载 Django,然后像安装普通的 Python 应用一样安装它。您也可以通过操作系统(OS)包管理工具下载并安装 Django,比如 Linux 发行版上提供的apt-get

另一个选择是通过 Python 包管理器pip下载安装 Django。还有一种方法是直接在 github 上安装 Django。8Django 安装选项列表及其优缺点如表 1-1 所示。

表 1-1。

Django installation options - Pros and Cons

| 方法 | 赞成的意见 | 骗局 | | --- | --- | --- | | 使用`pip` Python 包管理器下载/安装。(推荐选项) | 允许在虚拟 Python 环境中安装。依赖关系是自动处理的。 | 最新版本可能不可用。 | | 从主站点下载为`tar.gz`文件。 | 最容易获得最新的 Django 稳定版本。 | 需要手动下载和安装。需要额外管理 Django 依赖项(如果不使用 pip)。 | | 从 Git 下载。 | 访问最新的 Django 特性。 | 可能包含 bug。需要额外管理 Django 依赖项(如果不使用 pip)。 | | 从操作系统软件包管理器下载/安装(`apt-get`)。 | 易于安装。依赖关系是自动处理的。 | 最新版本可能不可用。安装在全球 Python 环境中。 |

正如表 1-1 中所强调的,安装 Django 的推荐选项是使用 Python pip包管理器,因为它提供了最大的灵活性。接下来,我将描述使用这种方法安装 Django 的每个步骤,更重要的是如何启动并运行pip

一旦我完成了这些步骤,我还将描述从一个tar.gz文件和从 git——使用pip——安装 Django 的步骤,如果您想尝试最新的 Django 特性,这可能会很有帮助。

安装 Python(先决条件)

由于 Django 是基于 Python 构建的,所以首先需要安装 Python 来运行 Django。最新的 Django 长期版本(LTS)是 1.11 版,这也是本书的重点。Django 1.11 要求您要么拥有 Python 2.7.x 版本,要么拥有 Python 3.4 或更高版本(3.5 或 3.6)。

如果这是你第一次使用 Python,注意 Python 2 和 Python 3 有很大的不同是很重要的。虽然 Python 3 确实是未来,但要知道,自 2008 年第一个 Python 3 版本问世以来,未来就一直在酝酿之中,Python 2 一直顽固不化,直到 2016 年 12 月 Python 2.7.13 才问世。

那么 Django 应该用 Python 2 还是 Python 3 呢?就 Django 的核心而言,它兼容两者,因此您可以轻松地在 Python 2 和 Python 3 之间切换。当涉及到第三方 Python 包和您计划自己编写的 Django Python 代码时,事情变得有点棘手。

虽然许多第三方 Python 包已经升级到可以在 Python 3 上运行,但这个过程一直很缓慢。正如我已经指出的,Python 3 的开发已经进行了将近 10 年,所以请注意,如果您选择 Python 3 路线,您可能会遇到无法与 Python 3 兼容的第三方 Python 包。

当涉及到您自己的 Django 应用代码时,理想的选择是让您的代码兼容 Python 2 和 Python 3——就像 Django 的核心一样——这并不难,我将在整本书中使用这种技术。侧栏包含更多关于编写 Python 2 和 Python 3 兼容代码的细节。

现在,如果你想坚持使用 Python 2,请注意 Django 1.11 将是最后一个支持 Python 2 的 Django 版本——计划支持到 2020 年 4 月左右——所以如果你最终升级到比 Django 1.11 更高的版本,你还需要将所有应用代码升级到 Python 3——这就是为什么我推荐 Python 2 和 Python 3 双重兼容技术。如果你想坚持使用 Python 3,那是未来的趋势,只是要注意,如前所述,一些第三方包可能无法与 Python 3 兼容。

Django Compatibility With Python 2 and Python 3

Django 使用 6 个 9 个 来运行 Python 2 和 Python 3 兼容的逻辑。Six 是一组实用程序,涵盖了 Python 2 和 Python 3 之间的差异,允许相同的逻辑在 Python 2 和 Python 3 中同等地运行。Django 框架的内部——很少需要检查或修改——已经使用了这种技术。

但是,如果您计划编写与 Python 2 和 Python 3 都兼容的 Django 应用代码,那么您需要对如何编写代码有更多的了解。Django 发布了自己的关于各种语法和技术的指南,您需要遵循这些指南来使 Django 应用代码与 Python 2 和 Python 3、 10 技术一起工作,这些技术也在整本书中使用。

如果您使用 Unix/Linux 操作系统,Python 很可能安装在您的系统上。如果您在 Unix/Linux 终端上键入which python,它会返回一个响应(例如/usr/bin/python),这表示 Python 可执行文件的位置,如果没有响应,则表示 Python 可执行文件在系统上不可用。

如果您的系统上没有 Python,并且您使用的是 Debian 或 Ubuntu Linux 发行版,那么您可以使用 OS package manager apt-get 通过键入:apt-get install python来安装 Python。如果您的 Unix/Linux 发行版不是 Debian 或 Ubuntu,并且您需要安装 Python,请查阅您的 Unix/Linux 文档以获得可用的 Python 包,或者从 http://python.org/download/ 下载 Python 源代码来进行安装。

如果你有一个运行在 Windows 操作系统或 macOS 上的系统,Python 安装程序可以从 http://python.org/download/ 下载。

无论您的系统是什么操作系统,一旦您完成 Python 安装,请确保 Python 安装正确,并且可以从系统的任何地方访问。打开一个终端并键入python,您应该会进入一个 Python 交互式会话,如清单 1-1 所示。

[user@∼]$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Listing 1-1.Python interactive session

如果您无法进入 Python 交互式会话,请查看 Python 安装过程,因为您将无法继续以下部分。

更新或安装 pip 软件包管理器(先决条件)

为了使 Python 包的安装和管理更容易,Python 使用了一个名为 pip 的包管理器。如果您使用的是 Python 2.7.9(或更高版本的 2.x 分支)或 Python 3.4(或更高版本的 3.x 分支),默认情况下会安装 pip。现在让我们在您的系统上升级 pip,如清单 1-2 所示,如果您的系统上没有 pip,我将简要说明如何获得它。

[user@∼]$ pip install --upgrade pip
Collecting pip
  Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)
Installing collected packages: pip
  Found existing installation: pip 8.1.1
Successfully installed pip-9.0.1
Listing 1-2.Update pip package manager

正如您在清单 1-2 中看到的,为了更新 pip,您调用带有参数install --upgrade pippip可执行文件。在执行时,pip 通过提供的名称搜索一个包——在本例中是 pip 本身——下载它,并在已经安装的情况下执行升级。如果您系统上的安装输出类似于清单 1-2 中的输出——没有任何错误——您已经成功更新了 pip。

如果您看到一个错误,如程序“pip”当前未安装或 pip 未找到,这意味着您的 Python 安装没有配备 pip。在这种情况下,您需要通过下载 https://bootstrap.pypa.io/get-pip.py 来安装 pip 可执行文件,然后使用命令python get-pip.py执行下载的文件。一旦安装了pip可执行文件,运行清单 1-2 中的 pip 更新程序。

有了系统上的 pip,您就可以进入下一步了。

安装 virtualenv(可选先决条件)

Virtualenv 对于开发 Django 应用并不重要,但是我强烈建议您使用它,因为它允许您在单个系统上创建虚拟 Python 环境。通过使用虚拟 Python 环境,应用可以在独立于其他 Python 应用的“沙箱”中运行。最初,virtualenv 似乎没有什么好处,但它对于将开发环境复制到生产环境以及避免不同应用之间可能出现的版本冲突等任务来说,可能会有巨大的帮助。

没有 virtualenv,您仍然可以使用 pip 继续安装 Django 和任何其他 Python 包,但问题是所有包都安装在全局 Python 安装下。最初这看起来很方便,因为在全局 Python 安装中只需要安装一次包。但是如果你思考下面的一些问题,就没那么方便了。

如果在你的第一个项目之后发布了一个新的 Django 版本,而你想开始第二个项目,会发生什么?您是升级第一个项目以便在新的 Django 版本上运行,还是启动第二个项目,就好像新的 Django 版本不存在一样?第一个选项需要额外的工作,而第二个选项需要您在过时的 Django 版本上进行开发。通过使用虚拟 Python 环境,可以避免这个问题,因为每个项目都可以独立运行自己的 Django 版本。

如果您考虑到任何 Python 包的潜在版本冲突,您就会明白为什么我推荐您使用 virtualenv。许多 Python 包都有特定的版本依赖关系(例如,包 A 依赖于包 B 版本 2.3 和包 C 版本 1.5)。如果您用特定的交叉依赖版本更新一个新的包,如果您使用的是全局 Python 安装,那么中断 Python 安装是非常容易的。使用 virtualenv,您可以拥有多个 Python 安装,而不会相互干扰。

既然我已经解释了 virtualenv 的好处,让我们用 pip 安装virtualenv可执行文件,如清单 1-3 所示。

[user@∼]$  pip install virtualenv
Downloading/unpacking virtualenv
  Downloading virtualenv-15.1.0.tar.gz (1.8Mb): 1.8Mb downloaded
  Running setup.py egg_info for package virtualenv
  Installing collected packages: virtualenv
  Running setup.py install for virtualenv
  Installing virtualenv script to /usr/local/bin
  Installing virtualenv-2.7 script to /usr/local/bin
Successfully installed virtualenv
Cleaning up...
Listing 1-3.Install virtualenv

with pip

如清单 1-3 所示,pip自动下载并安装所请求的包。类似于pip可执行文件,还安装了一个virtualenv可执行文件,可以从系统的任何地方访问。virtualenv可执行文件允许您创建虚拟 Python 环境。清单 1-4 展示了如何用virtualenv创建一个虚拟的 Python 环境。

[user@∼]$ virtualenv --python=python3 mydjangosandbox
Already using interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /mydjangosandbox/bin/python3
Also creating executable in /mydjangosandbox/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.
Listing 1-4.Create virtual Python environment

with virtualenv

virtualenv可执行文件接受几个参数。清单 1-4 中的任务利用了--python标志,它告诉 virtualenv 基于python3可执行文件创建一个虚拟 Python,从而创建一个 Python 3 虚拟环境。当一个操作系统上有多个 Python 版本(例如 Python 2 和 Python 3)并且需要指定创建 virtualenv 的 Python 版本时,这是一个常见的选项。可以省略--python标志;请注意,这样做 virtualenv 是用默认的操作系统python可执行文件创建的。

默认情况下,virtualenv 会创建一个原始的虚拟 Python 环境,就像您最初进行 Python 全局安装时的环境一样。在 virtualenv 参数之后,您只需要为虚拟 Python 环境的名称指定一个参数,在清单 1-4 的情况下是mydjangosandbox。在执行时,virtualenv 创建一个包含虚拟 Python 环境的目录,其内容如清单 1-5 所示。

+<virtual_environment_name>
|
|
+---+-<bin>
|   |
|   +-activate
|   +-easy_install
|   +-pip
|   +-python
    +-python-config
|   +-wheel
|
+---+-<include>
|
+---+-<lib>
|
+---+-<local>
†

|
+---+-<share>
Listing 1-5.Virtual Python environment directory structure

Tip

取决于用于创建 virtualenv 的 Python 版本,bin 目录可以包含同一命令的多个别名或版本(例如,除了pythonpython2.7python3;除了activateactivate.cshactivate_this.py

Note

本地文件夹仅包含在 Python 2 virtualenv 中,并链接到虚拟目录的顶级目录以模拟 Python 安装。

如清单 1-5 所示,虚拟 Python 环境的目录结构类似于全局 Python 安装。bin目录包含虚拟环境的可执行文件,include目录链接到全局 Python 安装头文件,lib目录是全局 Python 安装库的副本,也是安装虚拟环境的包的地方,share目录用于放置共享的 Python 包。

虚拟环境最重要的部分是bin目录下的可执行文件。如果您使用这些可执行文件中的任何一个,比如pipeasy_installpython,wheel,,它们将在虚拟 Python 环境的上下文中执行。例如,bin文件夹下的pip为虚拟环境安装软件包。类似地,在bin文件夹下的python可执行文件上运行的应用只能加载安装在虚拟 Python 环境中的包。这就是我之前提到的“沙盒”行为。

尽管访问不同的虚拟 Python 环境和可执行文件是一个强大的特性,但是对于多个虚拟 Python 环境和全局 Python 安装本身的可执行文件使用不同的pippython可执行文件,会由于长的访问路径和相对路径而变得混乱。

出于这个原因,virtualenv 有一个加载虚拟环境的机制,这样,如果您从系统上的任何地方执行pippython,或任何其他可执行文件,就会使用来自所选虚拟环境的可执行文件(而不是默认的全局 Python 安装可执行文件)。这是通过bin目录中的activate可执行文件实现的,清单 1-6 展示了这个过程。

[user@∼]$ source ./bin/activate
[(mydjangosandbox)user@∼] $
# NOTE: source is a Unix/Linux specific command, for other OS just execute activate
Listing 1-6.
Activate

virtual Python environment

请注意清单 1-6 中调用activate可执行文件后,命令提示符如何在括号中添加虚拟环境名称。这意味着虚拟 Python 环境mydjangosandboxbin目录下的可执行文件将优先于全局 Python 安装中的文件使用。要退出一个虚拟 Python 环境,只需键入deactivate就可以回到使用全局 Python 安装可执行文件。

正如您现在所了解的,virtualenv 透明地工作,允许您维护不同的 python 安装,每个安装都有自己的一组可执行文件,如主 Python 解释器和 pip 包管理器。您只需要在虚拟环境之间切换,以便在适当的虚拟环境中安装和运行 Python 应用。

Note

在以后的章节中,我不会提到 virtualenv(例如mydjangosandbox),因为它与 Django 没有直接关系。虽然我建议您使用 virtualenv,但是如果您希望继续使用全局 Python 安装pythonpip可执行文件来处理任何事情,或者如果您希望让虚拟 Python 环境拥有自己的可执行文件以使 Python 应用管理更容易,我会让您自己决定。因此,当你在书中看到 Python 可执行文件时,假设它们是全局的或者来自 virtualenv,无论你使用哪个。

安装 Django

一旦您的系统上运行了所有以前的工具,实际的 Django 安装就非常简单了。清单 1-7 展示了如何使用 pip 安装 Django。

[user@∼]$ pip install Django==1.11
Downloading/unpacking Django==1.11
Collecting Django==1.11
  Downloading Django-1.11-py2.py3-none-any.whl (6.9MB)
    100% |███████████████████| 6.9MB 95kB/s
Collecting pytz (from Django==1.11)
  Downloading pytz-2017.2-py2.py3-none-any.whl (484kB)
    100% |███████████████████| 491kB 735kB/s
Installing collected packages: pytz, Django
Successfully installed Django-1.11 pytz-2017.2
Listing 1-7.Install Django with pip

清单 1-7 中的pip install任务使用Django==1.11语法告诉 pip 下载并安装 Django 1.11 版本。使用同样的语法,您可以安装任何特定的 Django 版本。如果您不指定软件包版本,pip 会下载并安装指定软件包的最新可用版本。

有时 Django 版本可能需要几天才能通过 pip 获得,在这种情况下,您会收到一个错误。在这种情况下,您可以直接从 Django 主网站 https://www.djangoproject.com/download/ 下载该版本。一旦下载了 tar.gz 格式的发布文件,就可以使用 pip 进行安装,如清单 1-8 所示。

[user@∼]$ pip install /home/Downloads/Django-1.11.tar.gz
Processing /home/Downloads/Django-1.11.tar.gz
Collecting pytz (from Django==1.11)
  Using cached pytz-2017.2-py2.py3-none-any.whl
Building wheels for collected packages: Django
  Running setup.py bdist_wheel for Django ... done
  Stored in directory: /home/ubuntu/.cache/pip/wheels/56/bf/24/f44162e115f4fe0cfeb4b0ae99b570fb55a741a8d090c9894d
Successfully built Django
Installing collected packages: pytz, Django
Successfully installed Django-1.11 pytz-2017.2
Listing 1-8.Install Django from local tar.gz file with pip

请注意清单 1-8 中 pip 如何能够直接从本地文件系统上的压缩文件安装 Python 包。

从 Git 安装 Django

如果您想使用 Django 中最新的功能,那么您需要从它的 Git 存储库中安装 Django。Git 存储库包含对 Django 的最新更改。尽管 Django Git 版本可能会不稳定,但这是使用最新的 Django 特性进行开发或获取尚未在公开发行版中提供的问题的 bug 修复的唯一方法。

Note

您需要安装 Git 来执行以下任务。你可以在 http://git-scm.com/ 下载几个操作系统的 Git

就像前面的 pip 安装示例一样,pip 非常灵活,可以从 Git 安装 Django。在 Git 中使用 pip 有两种选择。您可以提供远程 Django Git 存储库,在这种情况下,pip 在本地克隆存储库,并在安装后丢弃它,如清单 1-9 所示。或者您可以在本地克隆 Django Git 存储库——稍后您可以在这里进行修改——然后运行 pip 进行安装,如清单 1-10 所示。

[user@∼]$ pip install git+https://github.com/django/django.git
Collecting git+https://github.com/django/django.git
  Cloning https://github.com/django/django.git to ./pip-31j_bcqa-build

Requirement already satisfied: pytz in /python/mydjangosandbox/lib/python3.5/site-packages (from Django==2.0.dev20170408112615)
Installing collected packages: Django
      Successfully uninstalled Django-1.11
  Running setup.py install for Django ... done
Successfully installed Django-2.0.dev20170408112615

Listing 1-9.Install Django from remote Git with pip

[user@∼]$ git clone https://github.com/django/django.git
Cloning into django...
remote: Counting objects: 388550, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 388550 (delta 5), reused 0 (delta 0), pack-reused 388531
Receiving objects: 100% (388550/388550), 158.63 MiB | 968.00 KiB/s, done.
Resolving deltas: 100% (281856/281856), done.
Checking connectivity... done.

# Assuming Django Git download made to /home/Downloads/django/
[user@∼]$ pip install /home/Downloads/django/
Processing /home/Downloads/django
Collecting pytz (from Django==2.0.dev20170408112615)
  Using cached pytz-2017.2-py2.py3-none-any.whl
Installing collected packages: pytz, Django
  Running setup.py install for Django ... done
Successfully installed Django-2.0.dev20170408112615 pytz-2017.2

Listing 1-10.Download Django from Git and install locally with pip

注意在清单 1-9 中,下载远程 Git 存储库的语法是git+后跟远程 Git 位置。在本例中, https://github.com/django/django.git 表示 Django Git 存储库。在清单 1-10 中,Django Git 存储库首先被本地克隆,然后用本地 Git 存储库目录的参数执行pip

启动 Django 项目

要启动 Django 项目,您必须使用 Django 附带的django-admin可执行文件或django-admin.py脚本。在你安装 Django 之后,这个可执行文件和脚本应该可以从你系统上的任何目录中访问(例如,安装在一个 virtualenv 的/usr/bin//usr/local/bin//bin/目录下)。请注意,可执行文件和脚本提供了相同的功能;因此,今后我将交替使用django-admin这一术语。

django-admin提供了各种子命令,您将在 Django 项目的日常工作中广泛使用这些命令。但是您将首先使用的是startproject子命令,因为它创建了 Django 项目的初始结构。startproject子命令接收一个参数来表示项目的名称,如下面的代码片段所示。

#Create a project called coffeehouse
django-admin startproject coffeehouse
#Create a project called sportstats
django-admin startproject sportstats

Django 项目名称可以由数字、字母或下划线组成。项目名称不能以数字开头,只能以字母或下划线开头。此外,项目名称中不允许出现特殊字符和空格,这主要是因为 Django 项目名称是目录和 Python 包的命名约定。

在执行django-admin startproject <project_name>时,会创建一个名为<project_name>的目录,其中包含默认的 Django 项目结构。清单 1-11 显示了 Django 项目的默认结构。

+<BASE_DIR_project_name>
|
+----manage.py
|
+---+-<PROJECT_DIR_project_name>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
Listing 1-11.Django project structure

如果您检查目录布局,您会注意到有两个目录带有<project_name>值。我将顶层 Django 项目目录称为BASE_DIR,它包括manage.py文件和基于项目名称的其他子目录。我将把第二级子目录——包括__init__.pysettings.pyurls.pywsgi.py文件——称为PROJECT_DIR。接下来,我将描述清单 1-11 中每个文件的用途。

  • manage.py。-运行项目特定任务。正如django-admin用于执行系统范围的 Django 任务一样,manage.py用于执行项目特定的任务。
  • __init__.py。- Python 文件,允许从 Python 包所在的目录中导入 Python 包。注意__init__.py不是 Django 特有的,它是一个在几乎所有 Python 应用中使用的通用文件。
  • settings.py。-包含 Django 项目的配置设置。
  • urls.py。-包含 Django 项目的 URL 模式。
  • wsgi.py。-包含 Django 项目的 WSGI 配置属性。WSGI 是在生产环境中部署 Django 应用的推荐方法(例如,面向公众)。开发 Django 应用不需要设置 WSGI。

Tip

给项目的BASE_DIR重新命名。在一个 Django 项目中有两个同名的嵌套目录会导致混淆,尤其是在处理 Python 包导入问题时。为了避免麻烦,我建议您将BASE_DIR重命名为不同于项目名称的名称(例如,重命名、大写或缩短名称,使其不同于PROJECT_DIR)。

Caution

不要重命名PROJECT_DIRPROJECT_DIR名称被硬编码到一些项目文件中(例如settings.pywsgi.py,所以不要更改它的名称。如果你需要重命名PROJECT_DIR,用一个新名字创建另一个项目更简单。

现在您已经熟悉了默认的 Django 项目结构,让我们在浏览器中查看默认的 Django 项目。所有 Django 项目都有一个内置的 web 服务器,当项目文件发生变化时,它可以在浏览器中观察应用。放在 Django 项目的BASE_DIR中——这里是manage.py文件——运行命令python manage.py runserver,如清单 1-12 所示。

[user@coffeehouse ∼]$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

May 23, 2017 - 22:41:20
Django version 1.11, using settings 'coffeehouse.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Listing 1-12.Start Django development web server

provided by manage.py

如清单 1-12 所示,命令python manage.py runserverhttp://127.0.0.1:8000/上启动一个开发 web 服务器——这是您系统上的本地地址。暂时不要担心'unapplied migration(s)'的消息,我将在接下来关于为 Django 项目建立数据库的章节中解决这个问题。接下来,如果你打开一个浏览器并指向地址http://127.0.0.1:8000/,你应该会看到 Django 项目的默认主页,如图 1-3 所示。

A441241_1_En_1_Fig3_HTML.jpg

图 1-3。

Default home page for a Django project

有时更改 Django 开发 web 服务器的默认地址和端口很方便。这可能是因为默认端口正被另一个服务占用,或者需要将 web 服务器绑定到非本地地址,以便远程计算机上的某个人可以查看开发服务器。这很容易通过将端口或完整地址:端口字符串附加到python manage.py runserver命令来实现,如列表 1-13 中的各种示例所示。

# Run the development server on the local address and port 4345 (http://127.0.0.1:4345/)
python manage.py runserver 4345
# Run the dev server on the 96.126.104.88 address and port 80 (http://96.126.104.88/)
python manage.py runserver 96.126.104.88:80
# Run the dev server on the 192.168.0.2 address and port 8888 (http://192.168.0.2:8888/)
python manage.py runserver 192.168.0.2:8888
Listing 1-13.Start Django development web server on different address and port

为 Django 项目建立数据库

“开箱即用”状态下的 Django 被设置为与 SQLite 通信——一个包含在 Python 发行版中的轻量级关系数据库。所以默认情况下,Django 会自动为您的项目创建一个 SQLite 数据库。

除了 SQLite,Django 还支持其他流行的数据库,包括 PostgreSQL、MySQL 和 Oracle。连接到数据库的 Django 配置是在 Django 项目的settting.py文件中的DATABASES变量中完成的。

如果您打开 Django 项目的settings.py文件,您会注意到DATABASES变量有一个默认的 Python 字典,其值如清单 1-14 所示。

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Listing 1-14.Default Django DATABASES dictionary

Tip

如果您想要最快的数据库设置,请使用 SQLite。

数据库设置本身可能很耗时。如果您想要最快的设置来为 Django 启用一个数据库,请保持之前的配置不变。SQLite 不需要额外的凭证或 Python 包来建立 Django 数据库连接。请注意,SQLite 数据库是一个平面文件,Django 基于NAME变量值创建 SQLite 数据库。在清单 1-14 的情况下,在 Django 项目的BASE_DIR下,作为一个名为db.sqlite3的平面文件。

Note

如果使用 SQLite,可以跳到本节的最后一步“测试 Django 数据库连接并构建 Django 基表”

Django DATABASES变量定义了键值对。每个键代表一个数据库引用名,值是一个带有数据库连接参数的 Python 字典。在清单 1-14 中,您可以观察到default数据库引用。default引用名用于表示在 Django 项目中声明的任何数据库相关操作都将针对该连接执行。这意味着,除非另有说明,所有数据库 CRUD(创建-读取-更新-删除)操作都是针对用default键定义的数据库进行的。

在这种情况下,默认数据库的数据库连接参数是键ENGINENAME,它们分别代表数据库引擎(即品牌)和数据库实例的名称。

Django 数据库连接最重要的参数是ENGINE值。与数据库相关联的 Django 应用逻辑是平台中立的,这意味着无论选择什么样的数据库,您总是以相同的方式编写数据库 CRUD 操作。然而,对不同数据库进行的 CRUD 操作之间存在微小的差异,这一点需要加以考虑。

Django 通过支持不同的后端或引擎来解决这个问题。因此,根据您计划用于 Django 应用的数据库品牌,ENGINE值必须是表 1-2 中所示的值之一。

表 1-2。

Django ENGINE value for different databases

| 数据库ˌ资料库 | Django `ENGINE`值 | | --- | --- | | 关系型数据库 | `django.db.backends.mysql` | | 神谕 | `django.db.backends.oracle` | | 一种数据库系统 | `django.db.backends.postgresql_psycopg2` | | 数据库 | `django.db.backends.sqlite3` |

Django 数据库连接参数NAME用于标识一个数据库实例,其值的约定可能因数据库品牌而异。例如,对于 SQLite 来说,NAME表示平面文件的位置,而对于 MySQL 来说,它表示实例的逻辑名称。

Django 数据库连接参数的完整设置在表 1-3 中描述。

表 1-3。

Django database connection parameters based on database brand

| Django 连接参数 | 缺省值 | 笔记 | | --- | --- | --- | | `ATOMIC_REQUESTS` | `False` | 对每个查看请求强制执行(或不执行)一个事务。默认情况下设置为 False,因为为每个视图打开一个事务会有额外的开销。对性能的影响取决于应用的查询模式以及数据库处理锁定的能力。 | | `AUTOCOMMIT` | `True` | 默认情况下设置为 True,因为否则将需要显式事务来执行提交。 | | `CONN_MAX_AGE` | `0` | 数据库连接的生存期(秒)。默认为 0,在每个请求结束时关闭数据库连接。对于无限制的持久连接,请使用 None。 | | `ENGINE` | `''`(空字符串) | 要使用的数据库后端。数值选项见表 1-2 。 | | `HOST` | `''`(空字符串) | 定义数据库主机,其中空字符串表示本地主机。对于 MySQL:如果该值以正斜杠('/')开头,MySQL 将通过 Unix 套接字连接到指定的套接字(例如," HOST": '/var/run/mysql ')。如果该值不以正斜杠开头,则该值被假定为主机。对于 PostgreSQL:默认情况下('),通过 UNIX 域套接字(pg_hba.conf 中的' local '行)连接到数据库。如果 UNIX 域套接字不在标准位置,请使用 postgresql.conf 中 unix_socket_directory 的相同值。如果要通过 TCP 套接字进行连接,请将 host 设置为“localhost”或“127 . 0 . 0 . 1”(pg _ HBA . conf 中的“HOST”行)。在 Windows 上,您应该始终定义主机,因为 UNIX 域套接字不可用。 | | `NAME` | `''`(空字符串) | 要使用的数据库的名称。对于 SQLite,它是数据库文件的完整路径。指定路径时,请始终使用正斜杠,即使在 Windows 上也是如此(例如,C:/www/STORE/db.sqlite3)。 | | `OPTIONS` | `{}`(空字典) | 连接到数据库时使用的额外参数。可用参数因数据库后端而异,请查阅后端模块自己的文档。有关后端模块的列表,请参见表 1-2 。 | | `PASSWORD` | `''`(空字符串) | 连接到数据库时使用的密码。不用于 SQLite。 | | `PORT` | `''`(空字符串) | 连接到数据库时使用的端口。空字符串表示默认端口。不用于 SQLite。 | | `USER` | `''`(空字符串) | 连接到数据库时使用的用户名。不用于 SQLite。 |

安装 Python 数据库包

除了配置 Django 连接到数据库,您还需要安装必要的 Python 包来与您的数据库品牌通信——唯一的例外是 SQLite,它包含在 Python 发行版中。

每个数据库依赖于不同的软件包,但是使用 pip 软件包管理器,安装过程非常简单。如果您的系统上没有 pip 可执行文件,请参阅本章前面的“安装 Django”一节中的“安装 pip”小节。

表 1-4 中列举了 Django 支持的每个数据库的 Python 包。此外,表 1-4 还包括安装每个包的 pip 命令。

表 1-4。

Python packages for different databases

| 数据库ˌ资料库 | Python 包 | pip 安装语法 | | --- | --- | --- | | 一种数据库系统 | `psycopg2` | `pip install psycopg2` | | 关系型数据库 | `mysql-python` | `pip install mysql-python` | | 神谕 | `cx_Oracle` | `pip install cx_Oracle` | | 数据库 | 包含在 Python 2.5+中 | 不适用的 |

Database Development Libraries

如果您在尝试安装表 1-4 中的一个 Python 数据库包时收到错误,请确保您的系统上安装了数据库开发库。数据库开发库是构建连接到数据库的软件所必需的。

数据库开发库与 Python 或 Django 无关,因此您需要咨询数据库供应商或操作系统文档(例如,在 Debian Linux 或 Ubuntu Linux 系统上,您可以使用以下 apt-get 任务安装 MySQL 开发库:apt-get install libmysql client-dev)。

测试 Django 数据库连接并构建 Django 基表

一旦用数据库凭证更新了 Django settings.py文件,就可以测试它,看看 Django 应用是否可以与数据库通信。在整个 Django 项目中,有几项任务需要与数据库进行通信,但是现在测试数据库连接最常见的任务之一是将项目的数据结构迁移到数据库中。

Django 数据库迁移过程确保与数据库相关的所有 Django 项目逻辑都反映在数据库本身中(例如,数据库具有 Django 项目所需的必要表格)。当您开始一个 Django 项目时,Django 需要进行一系列的迁移,这些迁移需要创建表来跟踪管理员和会话。这总是 Django 项目对数据库运行的第一个迁移过程。因此,为了测试 Django 数据库连接,让我们在数据库上运行第一次迁移,以创建这组基表。

要针对数据库运行 Django 项目的迁移,在项目的BASE_DIR中使用带有migrate参数的manage.py脚本(例如,python manage.py migrate)。第一次执行这个命令时,输出应该类似于清单 1-15 。

[user@coffeehouse ∼]$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
Listing 1-15.Run first Django migrate operation to create base database tables

如清单 1-15 所示,如果数据库连接成功,Django 会应用一系列迁移来创建数据库表,以管理项目的用户、组、权限和会话。目前,不要太担心这些 Django 迁移是如何工作的或者它们位于哪里——我将在后面提供细节——只需要知道 Django 需要这些迁移来提供一些基本的功能。

Tip

直接连接到数据库。如果您在尝试连接到数据库或迁移 Django 项目以创建初始数据库表集时收到错误,请尝试使用相同的 Django 参数直接连接到数据库。

在许多情况下,Django 变量 NAME、USER、PASSWORD、HOST 或 PORT 中的输入错误会导致进程失败,或者凭据甚至无法直接连接到数据库。

设置内容:了解 URL、模板和应用

Django 项目中的内容使用三个主要的构件:URL、模板和应用。您分别创建和配置 Django 的 URL、模板和应用,尽管您将它们相互连接以实现内容交付,这是 Django 松散耦合架构设计原则的一部分。

URL 定义了访问内容的入口点或位置。模板定义了赋予最终内容形式的端点。应用充当 URL 和模板之间的中间件,改变或添加来自数据库或用户交互的内容。要运行静态内容,您只需要创建和配置 Django urls 和模板。要运行动态内容——从数据库或用户交互中构建——除了 URL 和模板之外,还需要创建和配置 Django 应用。

但在描述如何创建和配置 URL、模板和应用之前,了解这些部分如何相互配合非常重要。图 1-4 显示了 Django 处理用户请求的工作流程,以及他们如何处理 Django urls、模板和应用。

A441241_1_En_1_Fig4_HTML.jpg

图 1-4。

Django workflow for urls, templates, and apps

正如您在图 1-4 中看到的,有两条独立的管道来传递静态或动态内容。更重要的是,注意每个不同的 Django 层是如何松散耦合的(例如,如果不需要应用层,您可以放弃它,而 URL 层和模板层仍然能够相互通信)。

创建和配置 Django Urls

Django urls 的主要入口点是启动项目时创建的urls.py文件——如果您不熟悉 Django 项目结构,请参见本章前面的清单 1-11 。如果你打开urls.py文件,你会注意到它只有一个到/admin/的活动 url,那就是 Django admin——我将在本章的下一节也是最后一节讨论 Django admin。

现在您已经熟悉了urls.py文件的语法,让我们激活一个 url 来查看 Django 项目主页上的定制内容。

Django urls 使用正则表达式来匹配传入的请求。匹配主页的正则表达式模式是^$——下一章包括一个关于在 Django urls 中使用正则表达式的专门章节。除了正则表达式模式之外,还需要在拦截到匹配模式的请求时做什么的动作(例如,发送来自特定模板的内容)。

打开urls.py文件,添加第 3 行——在django.contrib import admin下面的一行——和第 9 行——在url(r'^admin/', admin.site.urls),下面的一行——如清单 1-16 所示。

from django.conf.urls import url
from django.contrib import admin

from django.views.generic import TemplateView

...
...

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),

]

Listing 1-16.Django url for home page to template

如清单 1-16 所示,urlpatterns是一个url()语句的 Python 列表。url方法来自django.conf.urls包。您刚刚添加的url方法定义了主页的模式——正则表达式^$——后跟动作TemplateView.as_view(template_name='homepage.html')。最后一个动作是一个帮助器方法,将请求方指向一个采用参数template_name='homepage.html'的模板。

总之,您在清单 1-16 中添加的url方法告诉 Django 对主页的请求应该返回模板homepage.html中的内容。url方法是非常通用的,可以接受多种变化,我将在下一章简要而详细地描述。

现在让我们测试一下主页。通过在 Django 项目的BASE_DIR上执行python manage.py runserver来启动开发 web 服务器。打开浏览器上的默认地址http://127.0.0.1:8000/。你看到了什么?带有Exception Type: TemplateDoesNotExist homepage.html的错误页面。这个错误是因为 Django 找不到为 url 定义的homepage.html模板。在下一节中,我将向您展示如何配置和创建模板。

Caution

如果您收到错误OperationalError - no such table: django_session而不是错误TemplateDoesNotExist homepage.html,这意味着 Django 项目的数据库仍然没有正确设置。您需要在项目的BASE_DIR中运行python manage.py migrate,这样 Django 就会创建必要的表来跟踪会话。有关更多详细信息,请参见上一节关于设置数据库的内容。

创建和配置 Django 模板

默认情况下,Django 模板被解释为 HTML。这意味着 Django 模板应该有一个标准的 HTML 文档结构和 HTML 标签(例如,<html><body>)。您可以使用常规的文本编辑器来创建 Django 模板,并使用.html扩展名保存文件。

让我们为过去部分的 url 创建一个模板。在文本编辑器中,创建一个名为homepage.html的文件,并将清单 1-17 的内容放入其中。将文件保存在您的系统上,在 Django 项目的PROJECT_DIR.的子目录templates

<html>
 <body>
  <h4>Home page for Django</h4>
 </body>
</html>
Listing 1-17.Template homepage.html

一旦有了包含 Django 模板的目录,就需要配置一个 Django 项目,这样它就可以在这个目录中找到模板。在 Django 项目的settings.py文件中,需要在TEMPLATES变量的DIRS属性中定义模板目录。DIRS属性是一个列表,因此您可以定义几个目录来定位模板,尽管我建议您只使用一个带有各种子目录的目录来进行分类。

正如我之前推荐的,你应该把 Django 模板放在子目录中——在 Django 项目的PROJECT_DIR中使用一个像templates这样明显的名字。例如,如果 Django 项目PROJECT_DIR的绝对路径是/www/STORE/coffeehouse/,那么DIRS值的推荐位置就是/www/STORE/coffeehouse/templates/。清单 1-18 展示了在settings.py中使用在settings.py顶部动态设置的PROJECT_DIR引用变量的示例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),],
        '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 1-18.TEMPLATES and DIRS definition in settings.py

清单 1-18 的一个重要特点是它没有使用硬编码的目录路径;相反,它使用动态确定的PROJECT_DIR变量。这在目前看起来可能是微不足道的,但是一旦 Django 项目的位置有改变的趋势(例如,组开发,部署到生产),这就是一个好的实践。

最后,再次启动 Django 开发 web 服务器,并在默认地址http://127.0.0.1:8000/上打开一个浏览器。您现在应该在主页上看到模板homepage.html的内容,而不是您在上一节中看到的错误页面。

创建和配置 Django 应用

Django 应用用于对应用功能进行分组。如果你想处理来自数据库或用户交互的内容,你必须创建和配置 Django 应用。一个项目可以包含任意数量的应用。例如,如果您有一个咖啡馆项目,您可以创建一个商店应用、另一个菜单项应用、另一个关于信息的应用,并根据需要创建其他应用。一个项目中的应用数量没有硬性规定。无论是简化代码管理还是将应用工作委托给团队,Django apps 的目的都是将应用功能分组以使工作更容易。

Django 应用通常包含在项目的子目录中。这种方法使得使用 Python 引用和命名约定更加容易。如果项目名为 coffeehouse,名为 stores 的应用的功能很容易通过 Python 包引用为coffeehouse.stores

因为应用提供了一种组合应用功能的模块化方法,所以其他人或团体分发具有流行功能的 Django 应用是很常见的。例如,如果一个 Django 项目需要论坛功能,而不是从头开始编写一个论坛应用,您可以利用几个 Django 论坛应用中的一个。你寻找的功能越通用,你就越有可能找到第三方开发的 Django 应用。

You Already Worked With Django Apps!

您可能没有意识到这一点,但是在上一节中,当您为 Django 项目设置数据库时,您已经在调用 migrate 操作时使用了 Django apps。

默认情况下,所有 Django 项目都启用了框架提供的六个应用。这些应用分别是django.contrib.admindjango.contrib.authdjango.contrib.contenttypesdjango.contrib.sessionsdjango.contrib.messagesdjango.contrib.staticfiles。当您触发迁移操作时,Django 为这些预安装的应用创建了数据库模型。

接下来,让我们创建一个小的 Django 应用。转到PROJECT_DIR——urls.pysettings.py文件所在的位置——执行命令django-admin startapp about创建一个名为 about 的应用。一个名为about的子目录被创建,其中包含该应用。默认情况下,创建应用时,其子目录包括以下内容:

  • __init__.py。- Python 文件,允许从其他目录导入应用包。注意__init__.py不是一个 Django 特定的文件,它是一个在几乎所有 Python 应用中使用的通用文件。
  • migrations。-包含应用于应用数据库定义(即模型类)的迁移的目录。
  • admin.py。-包含应用管理定义的文件-从 Django admin 访问模型类实例需要这些定义。
  • apps.py。-包含应用配置参数的文件。
  • models.py。-包含应用数据库定义(即模型类)的文件。
  • tests.py。-应用的测试定义文件。
  • views.py。-包含应用视图定义(即控制器方法)的文件。

接下来,打开views.py文件并添加清单 1-19 中的内容。

from django.shortcuts import render

def contact(request):
    # Content from request or database extracted here
    # and passed to the template for display
    return render(request,'about/contact.html')

Listing 1-19.
Handler view method
in views.py

清单 1-19 中的contact方法——像views.py文件中的所有其他方法一样——是一个访问用户 web 请求的控制器方法。注意,联系方法的输入名为request。在这种类型的方法中,您可以使用request引用访问来自 web 请求的内容(例如,IP 地址、会话),或者访问来自数据库的信息,以便最终将这些信息传递给模板。如果您查看 contact 方法的最后一行,它以 Django helper 方法render的返回语句结束。在这种情况下,render 方法将控制权返回给about/contact.html模板。

因为清单 1-19 中的contact方法将控制权返回给模板about/contact.html,所以您还需要在您的templates目录中创建一个名为about的子目录,其中包含一个名为contact.html的模板(即在TEMPLATES变量的DIRS属性中定义的模板)。

contact方法本身什么也不做,它需要被 url 调用。清单 1-20 展示了如何向链接到清单 1-19 中contact方法的urls.py文件添加一个 url。

from django.conf.urls import url
from django.contrib import admin
from django.views.generic import TemplateView

from coffeehouse.about import views as about_views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/', about_views.contact),
]

Listing 1-20.Django url for view method

清单 1-20 中声明的第一件事是一个 import 语句,用于访问清单 1-19 中的contact方法。在这种情况下,因为应用被命名为about,并且它位于coffeehouse项目文件夹下,所以它显示为from coffeehouse.about,后跟import views,这使我们可以访问应用的views.py文件,其中有contact方法。

import 语句以as about_views结束,以分配一个唯一的限定符,如果您计划使用多个应用,这很重要。例如,没有as关键字的导入语句,如from coffeehouse.about import viewsfrom coffeehouse.items import viewsfrom coffeehouse.stores import views可以导入冲突的视图方法引用(例如,三个名为 index 的方法),因此as限定符是一种保护措施,以确保您不会无意中使用另一个应用中同名的方法。

清单 1-20 中的新 url 定义使用正则表达式来匹配about url 目录(例如http://127.0.0.1:8000/about/)上的请求,而不是将请求定向到模板,而是将控制权交给about_views.contact方法——其中about_views指的是上一段中描述的导入引用。

接下来,启动 Django 开发 web 服务器,并在地址http://127.0.0.1:8000/about/上打开一个浏览器。注意about url 目录上的请求如何显示在views.pycontact方法中定义的底层about/contact.html模板。

最后,虽然您现在可以访问应用的views.py方法,但是您还需要在项目的settings.py文件中配置应用。这最后一步很重要,这样 Django 就可以找到您稍后创建的其他应用构造(例如,数据库模型定义、静态资源、定制模板标签)。

打开 Django 项目的settings.py文件并寻找INSTALLED_APPS变量。你会看到一系列已经在INSTALLED_APPS上定义的应用。注意安装的应用是如何属于django.contrib包的,这意味着它们是由 Django 框架本身提供的。将coffeehouse.about应用添加到列表中,如清单 1-21 的第 8 行所示。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'coffeehouse.about',

]
Listing 1-21.Add app to INSTALLED_APPS in Django settings.py

如清单 1-21 的第 8 行所示,要将应用添加到项目中,您需要将应用包作为字符串添加到INSTALLED_APPS变量中。虽然coffeehouse.about应用实际上仍然是空的,但是将应用添加到INSTALLED_APPS变量中是未来操作的重要配置步骤,例如数据库操作和与应用相关的静态资源,等等。

设置 Django 管理站点

Django 管理站点提供了一个基于 web 的界面来访问连接到 Django 项目的数据库。即使对于有经验的技术管理员来说,直接在数据库上进行数据库 CRUD(创建-读取-更新-删除)操作也是困难和耗时的,因为需要发出原始 SQL 命令和导航数据库结构。对于非技术用户来说,直接在数据库上进行数据库 CRUD 操作可能会令人望而生畏,如果不是不可能的话。Django 管理站点解决了这个问题。

Django 管理站点可以公开链接到数据库的所有 Django 项目相关的数据结构,因此专家和新手都可以轻松地执行数据库 CRUD 操作。随着 Django 项目的增长,Django 管理站点可以成为管理与 Django 项目相关的数据库中不断增长的大量信息的重要工具。

Django 管理站点是作为 Django 应用构建的;这意味着设置 Django 管理站点唯一需要做的事情就是像其他 Django 应用一样配置和安装应用。如果您不熟悉 Django app 这个术语,请阅读上一节“设置内容:理解 URL、模板和应用”

Django 管理站点要求您预先配置一个数据库并安装 Django 基表。因此,如果您还没有这样做,请参阅上一节“为 Django 项目设置数据库”

配置并安装 Django 管理站点应用

默认情况下,所有 Django 项目都启用了 Django 管理。如果你打开一个 Django 项目的urls.py文件,在urlpatterns变量中你会看到行url(r'^admin/', admin.site.urls)。最后一个正则表达式模式告诉 Django 在/admin url 目录(例如http://127.0.0.1:8000/admin/)上启用管理站点应用。

接下来,如果你打开项目的settings.py文件,转到INSTALLED_APPS变量,在这个变量的顶部附近,你会看到一行django.contrib.admin,表示 Django 管理站点应用已启用。

通过在 Django 的BASE_DIR上执行python manage.py runserver来启动开发 web 服务器。在 Django 管理网站http://127.0.0.1:8000/admin/上打开浏览器。你会看到如图 1-5 所示的登录屏幕。

A441241_1_En_1_Fig5_HTML.jpg

图 1-5。

Django admin site login

接下来,让我们创建一个 Django 超级用户或管理员,通过图 1-5 中的界面访问 Django admin。要创建 Django 超级用户,你可以使用清单 1-22 中的manage.py命令。

[user@coffeehouse ∼]$ python manage.py createsuperuser
Username (leave blank to use 'admin'):
Email address: admin@coffeehouse.com
Password:
Password (again):
The password is too similar to the email address.
This password is too short. It must contain at least 8 characters.
This password is too common.
Password:
Password (again):
Superuser created successfully.
Listing 1-22.Create Django superuser for admin interface

Caution

如果您收到错误OperationalError - no such table: auth_user,这意味着 Django 项目的数据库仍然没有正确设置。您需要在项目的 BASE_DIR 中运行python manage.py migrate,这样 Django 就会创建必要的表来跟踪用户。更多细节请参见上一节“为 Django 项目设置数据库”。

Tip

默认情况下,Django 强制用户密码符合最低安全级别。例如,在清单 1-22 中,您可以看到在尝试使用密码 coffee 之后,Django 拒绝了这个任务,并给出了一系列错误消息,强制进行新的尝试。您可以在setttings.pyAUTH_PASSWORD_VALIDATORS变量中修改这些密码验证规则。

最后这个过程创建了一个超级用户,其信息存储在连接到 Django 项目的数据库中,具体地说是存储在auth_user表中。现在您可能会问自己,如何更新这个用户的名称、密码或电子邮件?虽然您可以直接进入数据库表并执行更新,但这是一条曲折的路线;更好的方法是依赖 Django admin,它可以让您非常友好地查看 Django 项目中的数据库表。

接下来,将刚刚创建的超级用户用户名和密码引入图 1-5 的界面。一旦你在管理站点上提供了超级用户的用户名和密码,你将访问如图 1-6 所示的管理站点的主页。

A441241_1_En_1_Fig6_HTML.jpg

图 1-6。

Django admin site home page

在 Django 管理网站的主页上,如图 1-6 所示,点击“用户”链接。您将看到有权访问 Django 项目的用户列表。目前,您只能看到您在上一步中创建的超级用户。您可以更改该用户的凭证(如密码、电子邮件、用户名)或直接从 Django 管理站点屏幕添加新用户。

这种修改或添加存储在与 Django 项目相关的数据库中的记录的灵活性使得 Django 管理站点如此强大。例如,如果您开发了一个咖啡馆项目,并添加了商店、饮料或客户等应用,Django admin 授权用户可以对这些对象进行 CRUD 操作(例如,创建商店、更新饮料、删除客户)。从内容管理的角度来看,这是非常强大的,特别是对于非技术用户。最重要的是,在项目的应用上启用 Django 管理站点几乎不需要额外的开发工作。

这里介绍的 Django 管理站点任务只是功能上的“冰山一角”;下一章将更详细地介绍 Django 管理站点的功能。

配置并安装 Django 管理站点文档应用

Django 管理站点也有自己的文档应用。Django 管理站点文档应用不仅提供关于管理站点本身操作的信息,还包括关于 Django 模板的 Django 过滤器的其他通用文档。更重要的是,Django admin site documentation 应用会对所有已安装的项目应用的源代码进行自省,以呈现关于控制器方法和模型对象的文档(即嵌入在应用models.pyviews.py文件的源代码中的文档)。

要安装 Django 管理站点文档应用,您首先需要安装 docutils Python 包,使用 pip 包管理器执行以下命令:pip install docutils。一旦安装了 docutils 包,就可以像安装其他 Django 应用一样安装 Django 管理站点文档应用。

添加 url 以访问 Django 管理站点文档应用。如果打开项目的urls.py文件,在urlpatterns变量中添加下面一行:

 url(r'^admin/doc/', include('django.contrib.admindocs.urls'))

请确保在url(r'^admin/'…行之前添加此内容,以便在底部保留更多通用匹配表达式,在顶部保留同一 url 路径上的更多粒度表达式(例如/admin)。最后一个正则表达式模式告诉 Django 启用/admin/doc/ url 目录下的管理站点文档应用(例如http://127.0.0.1:8000/admin/doc/)。

接下来,打开项目的settings.py文件,转到INSTALLED_APPS变量。在这个变量的最终值附近添加一行django.contrib.admindocs来启用 Django 管理站点文档应用。

随着开发 web 服务器的运行,在地址http://127.0.0.1:8000/admin/doc/上打开一个浏览器,您应该会看到如图 1-7 所示的页面。

A441241_1_En_1_Fig7_HTML.jpg

图 1-7。

Django admin site doc home page

如果您注销了 Django 管理站点,您将需要再次登录来访问文档,因为它也需要用户认证。登录后,您将能够看到 Django 管理站点的文档主页——如图 1-7 所示——以及关于项目的控制器方法和模型对象的文档。

Footnotes 1

https://djangopackages.org/

2

https://engineering.instagram.com/what-powers-instagram-hundreds-of-instances-dozens-of-technologies-adf2e22da2ad#.pui97g5jk

3

https://www.quora.com/Pinterest/What-is-the-technology-stack-behind-Pinterest-1

4

http://open.pbs.org/

5

https://github.com/natgeo

6

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

7

https://www.djangoproject.com/download/

8

https://github.com/django/django/

9

https://pythonhosted.org/six/

10

https://docs.djangoproject.com/en/1.11/topics/python3/

二、Django Url 和视图

在第一章中,你学习了 Django 的核心构建模块,包括什么是视图、模型和 URL。在这一章中,您将了解更多关于 Django urls 的内容,它是 Django 应用工作流的入口点。您将学习如何创建复杂的 url 正则表达式,如何在视图方法和模板中使用 url 值,如何构造和管理 URL,以及如何命名 URL。

在 URL 之后,Django 视图代表了几乎所有 Django 工作流的下一步,视图负责检查请求、执行业务逻辑、查询数据库和验证数据,以及生成响应。在本章中,您将学习如何创建带有可选参数的 Django 视图,视图请求和响应的结构,如何使用视图中间件,以及如何创建基于类的视图。

Url 正则表达式

正则表达式在所有编程语言中都提供了一种强大的方法来确定模式。然而,强大的同时也带来了复杂性,甚至有整本书都在讨论正则表达式。 1

尽管大多数 Django URL 的复杂性不会超过许多正则表达式书中所描述的一小部分,但是理解 Django URL 中正则表达式的一些底层行为和最常见的模式是很重要的。

优先规则:粒度 URL 优先,广义 URL 最后

Django urls 需要遵循一定的顺序和语法才能正常工作。广义 url 正则表达式应该最后声明,并且只能在更细粒度的 url 正则表达式之后声明。

这是因为 Django url 正则表达式匹配不使用短路行为,就像嵌套条件语句(例如,if/elif/elif/elif/else)一样,只要满足一个条件,其余选项就会被忽略。在 Django urls 中,如果一个传入的 url 请求有不止一个匹配的正则表达式,那么将会触发最顶层的一个操作。匹配 url 正则表达式的优先级从顶部(即第一个声明的)到底部(即最后一个声明的)给出。

你不应该低估引入两个匹配相同模式的 url 正则表达式有多容易,特别是如果你从来没有使用过正则表达式,因为语法可能很神秘。清单 2-1 展示了声明 Django urls 的正确方式,更细粒度的正则表达式在顶部,更宽泛的正则表达式在底部。

from django.views.generic import TemplateVieww

urlpatterns = [
    url(r'^about/index/',TemplateView.as_view(template_name='index.html')),
    url(r'^about/',TemplateView.as_view(template_name='about.html')),
]

Listing 2-1.Correct precedence for Django url regular expressions

基于清单 2-1 ,让我们看看如果 Django 收到对 url /about/index/的请求会发生什么。最初,Django 匹配最后一个正则表达式,即“匹配^about/”。接下来,Django 继续向上检查正则表达式,并到达与请求 url /about/index/完全匹配的‘match^about/index/',因此触发这个动作将控制发送到index.html模板。

现在让我们浏览一个对 url /about/的请求。最初,Django 匹配最后一个正则表达式“匹配^about/”。接下来,Django 继续向上检查正则表达式,寻找潜在的匹配。因为没有找到匹配——因为‘match^about/index/'是一个更细粒度的正则表达式——Django 触发第一个动作,将控制发送给about.html模板,这是唯一的正则表达式匹配。

正如您所看到的,清单 2-1 产生了可以说是预期的行为。但是现在让我们颠倒 url 正则表达式的顺序,如清单 2-2 所示,并分解为什么向底部声明更细粒度的正则表达式是声明 Django url 正则表达式的错误方式。

from django.views.generic import TemplateVieww

urlpatterns = [
    url(r'^about/',TemplateView.as_view(template_name='about.html')),
    url(r'^about/index/',TemplateView.as_view(template_name='index.html')),
]

Listing 2-2.Wrong precedence for Django url regular expressions

当请求 url /about/index/时,清单 2-2 中的问题出现了。最初,Django 匹配最后一个正则表达式,即“匹配^about/index/”。然而,Django 继续检查正则表达式,并到达‘match^about/',这是对请求 url /about/index/的更广泛的匹配,但仍然是匹配!因此 Django 触发了这个动作,并将控制权发送给了about.html模板,而不是第一次匹配时预期的index.html模板。

精确 Url 模式:放弃广泛匹配

在上一节中,我有意使用了允许广泛 url 匹配的正则表达式。根据我的经验,随着 Django 项目的增长,你最终会面临使用这种类型的 url 正则表达式的需求——但是稍后会详细解释为什么会这样。

事实证明,使用精确的 url 正则表达式是可能的。精确的 url 正则表达式消除了由于 Django url 正则表达式的声明顺序而引入的任何歧义。

让我们修改清单 2-2 中的 url 正则表达式,使它们成为精确的正则表达式,这样它们的顺序就无关紧要了。清单 2-3 在清单 2-2 的基础上展示了精确的正则表达式。

from django.views.generic import TemplateVieww

urlpatterns = [
    url(r'^about/$',TemplateView.as_view(template_name='about.html')),
    url(r'^about/index/$',TemplateView.as_view(template_name='index.html')),
]

Listing 2-3.Exact regular expressions, where url order doesn’t matter

注意清单 2-3 中的正则表达式以$字符结束。这是表示行尾的正则表达式符号,这意味着正则表达式 URL 只匹配一个精确的模式。

例如,如果 Django 接收到对 url /about/index/的请求,它将只匹配清单 2-3 中的最后一个正则表达式,即“匹配^about/index/$”。然而,它不会匹配更高级的^/about/$正则表达式,因为这个正则表达式说与about/完全匹配,因为$表示模式的结束。

然而,尽管$字符对于创建更严格的 url 正则表达式很有用,但是分析它的行为也很重要。如果您计划使用 url 搜索引擎优化(SEO)、A/B 测试技术,或者只是希望允许多个 URL 运行相同的操作,那么使用更严格的正则表达式$最终需要做更多的工作。

例如,如果您开始使用像/about/index//about/email//about/address/这样的 URL,并且它们都使用相同的模板或视图进行处理,精确正则表达式只会使您声明的 URL 数量更大。类似地,如果您使用 A/B 测试或 SEO,其中相同 url 的较长变体以相同的方式处理(例如,/about/landing/a//about/landing/b//about/the+coffeehouse+in+san+diego/),宽泛的 url 匹配比声明精确的 url 模式要简单得多。

最后,无论您是否选择使用以$结尾的精确 url 正则表达式,我仍然建议您保持将更细粒度的 url 正则表达式放在顶部而将更广泛的 url 正则表达式放在底部的做法,因为这可以避免当不止一个正则表达式匹配一个 URL 请求时出现清单 2-2 中描述的意外行为。

常见 Url 模式

尽管 url 正则表达式可以有无限多种变化——几乎不可能描述每种可能性——但我将提供一些您更可能使用的最常见 url 模式的示例。表 2-1 显示了 Django urls 的单个正则表达式字符,表 2-2 显示了 url 模式的一系列更具体的例子。

表 2-2。

Common Django url patterns and their regular expressions, with samples

| Url 正则表达式 | 描述 | 示例 URL | | --- | --- | --- | | URL(r ' ^ $ ') | 空字符串(主页) | 匹配项:http://127.0.0.1/ | | url(r'^stores/',.....) | 有尾随字符吗 | 火柴: [`http://127.0.0.1/stores/`](http://127.0.0.1/stores/) [`http://127.0.0.1/stores/long+string+with+anything+12345`](http://127.0.0.1/stores/long+string+with+anything+12345) | | url(r'^about/contact/$',.....) | 精确,无尾随字符 | 匹配: [`http://127.0.0.1/about/contact/`](http://127.0.0.1/about/contact/) 不匹配: [`http://127.0.0.1/about/`](http://127.0.0.1/about/) | | url(r'^stores/\d+/',..…) | 数字 | 匹配: [`http://127.0.0.1/stores/2/`](http://127.0.0.1/stores/2/) [`http://127.0.0.1/stores/34/`](http://127.0.0.1/stores/34/) 不匹配: [`http://127.0.0.1/stores/downtown/`](http://127.0.0.1/stores/downtown/) | | URL(r ' ^ drinks/\ d+/', | 非数字 | 匹配: [`http://127.0.0.1/drinks/mocha/`](http://127.0.0.1/drinks/mocha/) 不匹配: [`http://127.0.0.1/drinks/324/`](http://127.0.0.1/drinks/324/) | | url(r'^drinks/mocha|espresso/',.....) | 单词选项,任何尾随字符 | 匹配:[`http://127.0.0.1/drinks/mocha/`](http://127.0.0.1/drinks/mocha/)[`http://127.0.0.1/drinks/mochaccino/`](http://127.0.0.1/drinks/mochaccino/)[`http://127.0.0.1/drinks/espresso/`](http://127.0.0.1/drinks/espresso/)不匹配: [`http://127.0.0.1/drinks/soda/`](http://127.0.0.1/drinks/soda/) | | url(r'^drinks/mocha$|espresso/$',.....) | 单词选项精确,无尾随字符 | 匹配: [`http://127.0.0.1/drinks/mocha/`](http://127.0.0.1/drinks/mocha/) 不匹配: [`http://127.0.0.1/drinks/mochaccino/`](http://127.0.0.1/drinks/mochaccino/) 匹配: [`http://127.0.0.1/drinks/espresso/`](http://127.0.0.1/drinks/espresso/) 不匹配: [`http://127.0.0.1/drinks/espressomacchiato/`](http://127.0.0.1/drinks/espressomacchiato/) | | url(r'^stores/\w+/',.....) | 单词字符(任何小写或大写字母、数字或下划线) | 匹配:[`http://127.0.0.1/stores/sandiego/`](http://127.0.0.1/stores/sandiego/)[`http://127.0.0.1/stores/LA/`](http://127.0.0.1/stores/LA/)[`http://127.0.0.1/stores/1/`](http://127.0.0.1/stores/1/)不匹配: [`http://127.0.0.1/san-diego/`](http://127.0.0.1/san-diego/) | | url(r'^stores/[-\w]+/',.....) | 单词字符或破折号 | 火柴: [`http://127.0.0.1/san-diego/`](http://127.0.0.1/san-diego/) | | url(r'^state/[A-Z]{2}/',.....) | 两个大写字母 | 匹配: [`http://127.0.0.1/CA/`](http://127.0.0.1/CA/) 不匹配: [`http://127.0.0.1/Ca/`](http://127.0.0.1/Ca/) |

表 2-1。

Regular expression syntax for Django urls: Symbol (Meaning)

| url 的开头) | url 的结尾) | \(对解释的值进行转义) | |(或) | | + (1 次或多次出现) | ?(出现 0 次或 1 次) | {n}(出现 n 次) | {n,m}(出现次数介于 n 和 m 之间) | | [](字符分组) | (?P ___)(捕获与 regexp ___ 匹配的匹配项,并将其分配给 name | 。(任何字符) | \d+(一个或多个数字)。注意 escape,没有 escape 按字面意思匹配' d+'。 | | \D+(一个或多个非数字)。注意转义,没有转义匹配' D+' | [a-zA-Z0-9_]+(一个或多个单词字符、小写或大写字母、数字或下划线) | \w+(一个或多个单词字符,相当于[a-zA-Z0-9_])。注意 escape,没有 escape 按字面意思匹配‘w+’。 | [-@\w]+(一个或多个单词字符,破折号。或者在符号处)。请注意,\w 没有转义,因为它被括在括号中(即分组)。 |

Django Urls Don’T Inspect Url Query Strings

在某些 URL 上——那些由 HTTP GET 请求生成的 URL,常见于 HTML 表单或 REST 服务中——参数被添加到 URL 中,其中?后跟由&分隔的parameter_name=parameter_value(例如,/drinks/mocha/?type=cold&size=large)。这些值集被称为查询字符串,Django 出于 url 模式匹配的目的忽略了它们。

如果您需要使用这些值作为 url 参数——这是下一节探讨的主题——您可以通过请求引用在 Django 视图方法中访问这些值。另一种方法是改变 url 结构以适应正则表达式(例如,用/drinks/mocha/cold/large/代替/drinks/mocha/?type=cold&size=large)。

Url 参数、额外选项和查询字符串

您刚刚学习了如何使用各种各样的正则表达式为您的 Django 应用创建 URL。然而,如果你回头看看清单 2-1 、 2-2 和 2-3 ,你会注意到 URL 上提供的信息被丢弃了。

有时将 url 信息作为参数传递给处理结构是有帮助的,甚至是必要的。例如,如果你有几个类似于/drinks/mocha//drinks/espresso//drinks/latte/的 url,URL 的最后一部分代表一个饮料名称。因此,将此 url 信息传递给处理模板以在视图中显示它或以其他方式使用它(例如,查询数据库)可能是有帮助的或必要的。为了传递这些信息,url 需要将这些信息作为一个参数。

为了处理 url 参数,Django 对命名组使用 Python 的标准正则表达式语法。 2 清单 2-4 显示了一个创建名为drink_name的参数的 url。

urlpatterns = [
    url(r'^drinks/(?P<drink_name>\D+)/',TemplateView.as_view(template_name='drinks/index.html')),
]
Listing 2-4.Django url parameter definition for access in templates

注意清单 2-4 中的(?P<drink_name>\D+)语法。?P<>语法告诉 Django 将正则表达式的这一部分视为命名组,并将值赋给在<>之间声明的名为drink_name的参数。最后一块\D+是确定匹配值的正则表达式;在这种情况下,匹配值是一个或多个非数字字符,如表 2-1 所述。

非常重要的一点是,您必须理解,只有当提供的值与指定的正则表达式匹配时,参数才会被捕获(例如,\D+表示非数字)。例如,对于 url 请求/drinks/mocha/,值mocha被分配给drink_name参数,但是对于像/drinks/123/这样的 url,正则表达式模式不匹配——因为123是数字——所以不采取任何行动。

如果清单 2-4 中出现 url 匹配,请求将被直接发送到模板drinks/index.html。Django 通过同名的 Django 模板上下文变量提供对以这种方式定义的所有参数的访问。因此,要访问参数,您可以直接在模板中使用参数名drink_type。例如,要输出参数drink_name的值,您可以使用标准的{{}} Django 模板语法(例如,{{drink_name}})。

除了将 url 的一部分视为参数,还可以在 url 定义中定义额外的选项,以便在 Django 模板中将它们作为上下文变量来访问。这些额外的选项在一个字典中定义,该字典被声明为 url 定义的最后一部分。

例如,请看清单 2-4 中修改后的 url Django 定义:

url(r'^drinks/(?P<drink_name>\D+)', TemplateView.as_view(template_name='drinks/index.html'), {'onsale':True}),

注意一个带有键值的字典是如何被添加到 url 定义的末尾的。以这种方式,onsale键成为一个 url 额外选项,它作为一个上下文变量被传递给底层模板。Url 额外选项可以像 url 参数一样作为模板上下文变量来访问。因此,为了输出额外的选项onsale,你可以使用{{onsale}}语法。

接下来,让我们看看清单 2-5 中展示的 url 参数的另一种变体,它将控制发送给 Django 视图方法。

# Project main urls.py
from coffeehouse.stores import views as stores_views

urlpatterns = patterns[
    url(r'^stores/(?P<store_id>\d+)/',stores_views.detail),
]

Listing 2-5.Django url parameter definition for access in view methods in main urls.py file

注意清单 2-5 中的(?P<store_id>\d+)语法与清单 2-4 中的非常相似。发生变化的是参数现在被命名为store_id,正则表达式是\d+来匹配数字。因此,举例来说,如果向 url /stores/1/发出请求,则值 1 被赋给store_id参数,如果向类似/stores/downtown/的 url 发出请求,则正则表达式模式不匹配——因为downtown是字母而不是数字——所以不采取任何行动。

如果清单 2-5 出现 url 匹配,请求将被直接发送到 Django 视图方法coffeehouse.stores.views.detail。其中coffeehouse.stores是包名,views.py是 stores 应用中的文件,detail是视图方法的名称。清单 2-6 展示了访问store_id参数的detail视图方法。

from django.shortcuts import render

def detail(request,store_id):
    # Access store_id with 'store_id' variable
    return render(request,'stores/detail.html')

Listing 2-6.Django view method

in views.py to access url parameter

注意清单 2-6 中的detail方法有两个参数。第一个参数是一个request对象,它对于所有 Django 视图方法都是相同的。第二个参数是 url 传递的参数。需要注意的是,url 参数的名称必须与方法参数的名称相匹配。在这种情况下,注意清单 2-5 中的参数名是store_id,而清单 2-6 中的方法参数也被命名为store_id

通过 view method 参数访问 url 参数,该方法可以使用该参数执行逻辑(例如,查询数据库),然后可以将该参数传递给 Django 模板以供呈现。

Caution

无论正则表达式如何,Django url 参数总是被视为字符串。例如,\d+捕捉数字,但值 1 被视为“1”(字符串),而不是 1(整数)。如果您计划在视图方法中使用 url 参数,并执行需要字符串以外的内容的操作,这一点尤其重要。

由视图方法处理的 url 参数的另一个可用选项是使它们成为可选的,这反过来允许您对多个 URL 使用相同的视图方法。通过为视图方法参数分配默认值,可以使参数成为可选的。清单 2-7 显示了一个新的 url,它调用了同一个视图方法(coffeehouse.stores.views.detail),但是没有定义参数。

from coffeehouse.stores import views as stores_views

urlpatterns = patterns[
    url(r'^stores/',stores_views.detail),
    url(r'^stores/(?P<store_id>\d+)/',stores_views.detail),
]

Listing 2-7.Django urls with optional parameters leveraging the same view method

如果您调用 url /stores/而没有修改清单 2-6 中的detail方法,您会得到一个错误。出现错误是因为detail视图方法需要一个store_id参数,而第一个 url 没有提供这个参数。要解决这个问题,您可以在视图方法中为store_id定义一个默认值,如清单 2-8 所示。

from django.shortcuts import render

def detail(request,store_id='1'):
    # Access store_id with 'store_id' variable
    return render(request,'stores/detail.html')

Listing 2-8.Django view method

in views.py with default value

注意清单 2-8 中的store_id参数如何赋值='1'。这意味着如果调用视图方法时没有使用store_id,参数将会有一个默认值'1'。这种方法允许您利用同一个视图方法来处理带有可选参数的多个 URL。

除了在视图方法中访问 url 参数之外,还可以从 url 定义中访问额外的选项。这些额外的选项在一个字典中定义,该字典被声明为 url 定义中的最后一个参数。在视图方法声明之后,添加一个字典,其中包含您希望在视图方法中访问的键-值对。下面的代码片段展示了清单 2-7 中 url 语句的修改版本。

url(r'^stores/',stores_views.detail,{'location':'headquarters'})

在这种情况下,location键成为一个 url 额外选项,作为参数传递给 view 方法。访问 Url 额外选项就像访问 url 参数一样,因此要访问 view 方法中的 url 额外选项,您需要修改方法签名以接受与 url 额外选项同名的参数。在这种情况下,方法签名:

def detail(request,store_id='1'):

需要更改为:

def detail(request,store_id='1',location=None):

请注意,location参数通过赋予默认值None而成为可选参数。

最后,还可以在 Django 视图方法中访问由?&分隔的 url 参数——技术上称为查询字符串。使用request对象可以在视图方法中访问这些类型的参数。

以 url /stores/1/?hours=sunday&map=flash为例,清单 2-9 展示了如何使用request.GET从这个由?&分隔的 url 中提取参数。

from django.shortcuts import render

def detail(request,store_id='1',location=None):
    # Access store_id param with 'store_id' variable and location param with 'location' variable
    # Extract 'hours' or 'map' value appended to url as
    # ?hours=sunday&map=flash
    hours = request.GET.get('hours', '')
    map = request.GET.get('map', '')
    # 'hours' has value 'sunday' or '' if hours not in url
    # 'map' has value 'flash' or '' if map not in url
    return render(request,'stores/detail.html')

Listing 2-9.Django view method extracting url parameters with request.GET

清单 2-9 使用了语法request.GET.get(<parameter>, '')。如果参数出现在request.GET中,它提取值并将其赋给一个变量以备将来使用;如果参数不存在,那么参数变量被赋予一个默认的空值''——你同样可以使用None或任何其他默认值——因为这是 Python 的标准字典get()方法语法的一部分,以获得默认值。

最后一个过程被设计成从 HTTP GET 请求中提取参数;然而,Django 还支持从 HTTP POST 请求中提取参数的语法request.POST.get,这将在 Django 表单一章和本章后面的 Django 视图方法请求一节中详细描述。

Url 整合和模块化

默认情况下,Django 会在项目主目录下的urls.py文件中查找 url 定义——值得一提的是,这是因为settings.py中的ROOT_URLCONF变量。然而,一旦一个项目超过了几个 URL,在这个文件中管理它们就变得困难了。例如,看看清单 2-10 中所示的urls.py文件。

from django.conf.urls import url
from django.views.generic import TemplateView
from coffeehouse.about import views as about_views
from coffeehouse.stores import views as stores_views

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/',about_views.index),
    url(r'^about/contact/',about_views.contact),
    url(r'^stores/',stores_views.index),
    url(r'^stores/(?P<store_id>\d+)/',stores_views.detail,{'location':'headquarters'}),
   ]

Listing 2-10.Django urls.py with no url consolidation

正如您在清单 2-10 中看到的,有几个 URL 有多余的根- about/stores/。将这些 URL 分开分组会很有帮助,因为这样可以将常见的 URL 保存在各自的文件中,避免了对一个大的urls.py文件进行修改的困难。

清单 2-11 显示了urls.py文件的更新版本,其中about/stores/根放在不同的文件中。

from django.conf.urls import include, url
from django.views.generic import TemplateView

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/',include('coffeehouse.about.urls')),
    url(r'^stores/',include('coffeehouse.stores.urls'),{'location':'headquarters'}),
   ]

Listing 2-11.Django urls.py with include to consolidate urls

清单 2-11 利用include参数从完全独立的文件中加载 URL。在这种情况下,include('coffeehouse.about.urls')告诉 Django 从 Python 模块coffeehouse.about.urls加载 url 定义,这与 Django 基目录的分离对应于文件路径/coffeehouse/about/urls.py。在这种情况下,我继续使用urls.py文件名,并把它放在相应的 Django about app 目录下,因为它处理的是about/URL。但是,您可以使用任何您喜欢的文件名或路径来定义 url(例如,coffeehouse.allmyurl.resturls从文件路径/coffeehouse/allmyurls/resturls.py加载 URL)。

清单 2-11 中的第二个 include 语句与第一个一样,其中include('coffeehouse.stores.urls')告诉 Django 从 Python 模块coffeehouse.stores.urls加载 url 定义。但是,请注意,第二条语句附加了一个额外的字典作为 url 额外选项,这意味着 include 语句中的所有 URL 也将收到这个额外选项。

清单 2-12 展示了通过include('coffeehouse.about.urls')链接的文件/coffeehouse/about/urls.py的内容。

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$',views.index),
    url(r'^contact/$',views.contact),
]

Listing 2-12.Django /coffeehouse/about/urls.py loaded via include

快速浏览清单 2-12 ,您可以看到其结构与主urls.py文件非常相似;然而,还是有一些细微的区别。虽然 url 正则表达式r'^$'看起来像是匹配主页,但事实并非如此。因为清单 2-12 中的文件通过主urls.py文件中的include链接,Django 将 url 正则表达式与父 url 正则表达式连接起来。所以清单 2-12 中的第一个 url 实际上匹配/about/,清单 2-12 中的第二个 url 实际上匹配/about/contact/。还因为清单 2-12 中的urls.py文件放在应用的views.py文件旁边,所以导入语句使用相对路径from . import views语法。

除了使用include选项引用带有 url 定义的单独文件之外,include选项还可以接受作为 Python 列表的 url 定义。本质上,这允许你在主urls.py文件中保留所有的 url 定义,但是给它更多的模块性。清单 2-13 中说明了这种方法。

from django.conf.urls import include, url
from django.views.generic import TemplateView

from coffeehouse.about import views as about_views
from coffeehouse.stores import views as stores_views

store_patterns = [
    url(r'^$',stores_views.index),
    url(r'^(?P<store_id>\d+)/$',stores_views.detail),
]

about_patterns = [
    url(r'^$',about_views.index),
    url(r'^contact/$',about_views.contact),
]

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/',include(about_patterns)),
    url(r'^stores/',include(store_patterns),{'location':'headquarters'}),
   ]

Listing 2-13.Django urls.py with inline include statements

清单 2-13 中 url 模式的结果与清单 2-11 和 2-12 相同。区别在于清单 2-13 使用主urls.py文件声明多个 url 列表,而清单 2-11 和 2-12 依赖于在不同文件中声明的 url 列表。

Url 命名和名称空间

项目的内部链接或 url 引用(例如<a href='/'>Home Page</a>)往往是硬编码的,无论是在视图方法中将用户重定向到特定位置,还是在模板中提供足够的用户导航。随着项目的增长,硬编码链接会带来严重的维护问题,因为它会导致难以检测和修复的链接。Django 提供了一种命名 URL 的方法,因此很容易在视图方法和模板中引用它们。

命名 Django urls 最基本的技术是将name属性添加到urls.py中的url定义中。清单 2-14 展示了如何命名一个项目的主页,以及如何从一个视图方法或模板中引用这个 url。

# Definition in urls.py
url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage")

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('homepage'))

# Definition in template
<a href="{% url 'homepage' %}">Back to home page</a>

Listing 2-14.Django url using name

清单 2-14 中的 url 定义使用了正则表达式r'^$',它被翻译成/或主页,也称为根目录。注意带有homepage值的name属性。通过给 url 分配一个name,您可以在视图方法和模板中使用这个值作为参考,这意味着将来对 url 正则表达式的任何更改,都会自动更新视图方法和模板中的所有 url 定义。

接下来在清单 2-14 中,您可以看到一个视图方法示例,它将控制重定向到reverse('homepage')。Django reverse方法试图通过给定的名称查找 url 定义——在本例中是homepage——并相应地替换它。类似地,清单 2-14 中的链接示例<a href="{% url 'homepage' %}">Back to home page</a>利用了 Django {% url %}标签,该标签试图通过其第一个参数来查找 url 在本例中是homepage——并相应地替换它。

同样的命名和替换过程也适用于更复杂的 url 定义,比如那些带有参数的定义。清单 2-15 显示了带有参数的 url 的过程。

# Definition in urls.py
url(r'^drinks/(?P<drink_name>\D+)/',TemplateView.as_view(template_name='drinks/index.html'),name="drink"),

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('drink', args=(drink.name,)))

# Definition in template
<a href="{% url 'drink' drink.name %}">Drink on sale</a>

<a href="{% url 'drink' 'latte' %}">Drink on sale</a>

Listing 2-15.Django url with arguments using name

清单 2-15 中的 url 定义使用了一个更复杂的正则表达式,它带有一个参数,可以转换成形式为/drinks/latte//drinks/espresso/的 URL。在这种情况下,url 被赋予参数名drink_name

因为 url 使用一个参数,reverse方法和{% url %}标记的语法略有不同。reverse方法要求 url 参数作为元组提供给args变量,而{% url %}标签要求 url 参数作为值列表提供。注意,在清单 2-15 中,参数同样可以是变量或硬编码值,只要它匹配 url 参数正则表达式类型——在本例中是非数字。

对于有多个参数的 url 定义,使用reverse{% url %}的方法是相同的。对于reverse方法,您传递给它一个带有所有必要参数的元组,对于{% url %}标记,您传递给它一个值列表。

Caution

小心使用反向和{% url %}的无效 url 定义。Django 总是在启动时检查所有反向和{% url %}定义是否有效。这意味着如果您在反向方法或{% url %}标记定义中出错——比如 url 名称中的输入错误或参数类型与正则表达式不匹配——应用将不会启动并抛出 HTTP 500 内部错误。

这种情况的误差是NoReverseMatch at....Reverse for 'urlname' with arguments '()' and keyword arguments '{}' not found. X pattern(s) tried。如果您查看错误堆栈,您将能够指出这是在哪里发生的,并纠正它。请注意,这是一个致命的错误,如果它没有被隔离到发生它的视图或页面,它将在启动时停止整个应用。

有时使用属性本身不足以对 URL 进行分类。如果您有两个或三个索引页面,会发生什么情况?或者,如果您有两个符合详细信息条件的 URL,但一个是商店的,另一个是饮料的?

一种简单的方法是使用复合名称(例如,drink_details、store_details)。然而,在这种形式中使用复合名称会导致难以记忆的命名约定和松散的层次结构。Django 支持的一种更简洁的方法是通过namespace属性。

属性允许用一个唯一的限定符来标识一组 URL。因为namespace属性与一组 URL 相关联,所以它与前面描述的include方法一起使用来合并 URL。

清单 2-16 展示了一系列 url 定义,它们利用了包含的名称空间属性。

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^about/',include('coffeehouse.about.urls',namespace="about")),
    url(r'^stores/',include('coffeehouse.stores.urls',namespace="stores")),
]

# About urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^contact/$',views.contact,name="contact"),
]

# Stores urls.py
from . import views

urlpatterns = 
    url(r'^$',views.index,name="index"),
    url(r'^(?P<store_id>\d+)/$',views.detail,name="detail"),
)

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('about:index'))

# Definition in template
<a href="{% url 'stores:index' %}">Back to stores index</a>

Listing 2-16.Django urls.py

with namespace attribute

清单 [2-16 以一组典型的 Django urls.py主文件的include定义开始。注意这两个定义都使用了namespace属性。接下来,您可以看到在主urls.py文件中引用的urls.py文件,这些文件利用了前面示例中描述的name属性。注意 about 和 stores urls.py文件都有一个带name='index'的 url。

要用名称空间限定 url 名称,可以使用语法<namespace>:<name>。正如您在清单 2-16 的底部看到的,要引用 about urls.py中的索引,您使用about:index,要引用 stores urls.py文件中的索引,您使用stores:index.

namespace 属性也可以嵌套使用语法<namespace1>:<namespace2>:<namespace3>:<name>来引用 URL。清单 2-17 展示了一个嵌套名称空间属性的例子。

# Main urls.py
from django.conf.urls import include, url
from django.views.generic import TemplateView

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^stores/',include('coffeehouse.stores.urls',namespace="stores")),
]

# Stores urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^(?P<store_id>\d+)/$',views.detail,name="detail"),
    url(r'^(?P<store_id>\d+)/about/',include('coffeehouse.about.urls',namespace="about")),
]

# About urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^contact/$',views.contact,name="contact"),
]

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('stores:about:index', args=(store.id,)))

# Definition in template
<a href="{% url 'stores:about:index' store.id %}">See about for {{store.name}}</a>

Listing 2-17.Django urls.py with nested namespace attribute

清单 2-17 中的 url 结构与清单 2-16 的不同之处在于,它为每个商店(例如/stores/1/about/)创建了 about url,而不是拥有一个通用的 about URL(例如/about/)。在清单 2-17 的顶部,我们使用namespace="stores"来限定 stores urls.py文件中的所有 URL。

接下来,在 stores urls.py文件中,注意还有另一个带有namespace="about"include元素来限定 about urls.py中的所有 URL。最后,在 about urls.py文件中,有一些 URL 只使用了name属性。在清单 2-17 的最后一部分,您可以看到嵌套的名称空间是如何与reverse方法和{% url %}标签一起使用的,它们使用一个:来分隔名称空间。

在 99%的 Django urls 中,你可以像描述的那样使用namenamespace参数。然而,当您在同一个项目中部署同一个 Django 应用的多个实例时,namespace参数具有特殊的意义。

因为 Django 应用是具有 url 定义的自包含单元,所以即使 Django 应用使用 url 名称空间,也会出现边缘情况。如果 Django 应用使用名称空间 X,但是您想在同一个项目中部署该应用两次或三次,会发生什么情况?假设每个应用都使用名称空间 X,那么如何引用它们的 URL 呢?这就是术语实例名称空间和app_name属性的由来。

让我们看一个场景,这个场景使用同一个 Django 应用的多个实例来说明这个与 url 名称空间相关的边缘情况。假设您开发了一个名为 banners 的 Django 应用来显示广告。横幅应用的构建方式是,它必须在不同的 URL 上运行(例如,/coffeebanners//teabanners//foodbanners/),以简化横幅的选择。本质上,您需要在同一个项目中运行横幅应用的多个实例,每个实例位于不同的 URL 上。

那么多个 app 实例和 url 命名有什么问题呢?它与使用需要根据当前应用实例动态改变的命名 URL 有关。这个问题通过一个例子最容易理解,所以让我们跳到清单 2-18 中的例子。

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^coffeebanners/',include('coffeehouse.banners.urls',namespace="coffee-banners")),
    url(r'^teabanners/',include('coffeehouse.banners.urls',namespace="tea-banners")),
    url(r'^foodbanners/',include('coffeehouse.banners.urls',namespace="food-banners")),
]

# Banners urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
]

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('coffee-banners:index'))
    return HttpResponsePermanentRedirect(reverse('tea-banners:index'))
    return HttpResponsePermanentRedirect(reverse('food-banners:index'))

# Definition in template
<a href="{% url 'coffee-banners:index' %}">Coffee banners</a>
<a href="{% url 'tea-banners:index' %}">Tea banners</a>
<a href="{% url 'food-banners:index' %}">Food banners</a>

Listing 2-18.Django urls.py with multiple instances

of the same app

在清单 2-18 中,你可以看到我们有三个指向同一个coffeehouse.banners.urls文件的 URL,每个都有自己独特的名称空间。接下来,让我们看看清单 2-18 中的各种reverse方法和{% url %}标记示例。

清单 2-18 中的reverse方法和{% url %}标记示例都使用<namespace>:<name>语法解析为三个不同的 url 名称。所以你可以使用namespacename有效地部署同一个 Django 应用的多个实例。

然而,仅仅依靠namespacename,解析的 url 名称无法动态适应不同的应用实例,这是与内部应用逻辑相关的边缘情况,必须包含内部应用逻辑以支持 Django 应用的多个实例。现在让我们来看看一个视图和模板场景,它展示了这个场景以及app_name属性如何解决这个问题。

假设在横幅应用中,您想要将控制重定向到应用的主索引 url(例如,由于异常)。现在戴上应用设计师的帽子,你会如何解决这个问题?作为一名应用设计师,你甚至不知道咖啡横幅、茶横幅或食物横幅名称空间,因为这些是部署名称空间。您如何在应用中内部集成重定向,以适应正在部署的应用的多个实例?这就是app_name参数的用途。

清单 2-19 展示了如何利用app_name属性来动态确定重定向到哪里。

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^coffeebanners/',include('coffeehouse.banners.urls',namespace="coffee-banners")),
    url(r'^teabanners/',include('coffeehouse.banners.urls',namespace="tea-banners")),
    url(r'^foodbanners/',include('coffeehouse.banners.urls',namespace="food-banners")),
]

# Banners urls.py
from django.conf.urls import url
from . import views

app_name = 'banners_adverts'
urlpatterns = [
    url(r'^$',views.index,name="index"),
]

# Logic inside Banners app
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    try:
       ...
    except:
       return HttpResponsePermanentRedirect(reverse('banners_adverts:index'))

Listing 2-19.Django redirect that leverages app_name to determine url

注意横幅应用的清单 2-19 中的urls.py文件在声明urlpatterns值之前设置了app_name属性。接下来,注意清单 2-19 中的reverse方法使用了banners_adverts:index值,其中banners_adverts代表app_name。这是一个重要的约定,因为 Django 依赖相同的语法来搜索app_namenamespace匹配。

那么你认为banners_adverts:index会解析到什么 url 呢?这完全取决于导航发生在哪里,它是动态的!如果用户在咖啡横幅应用实例(即 url coffeebanners)中导航,那么 Django 将banners_adverts:index解析为咖啡横幅实例索引,如果用户在茶横幅应用实例(即 url teabanners)中导航,那么 Django 将banners_adverts:index解析为茶横幅实例索引,以此类推任何其他数量的实例。如果用户在 banners 应用实例之外导航(即没有应用实例),Django 默认将banners_adverts:index解析为urls.py中最后定义的实例,这将是 food-banners。

以这种方式并基于用户来自的请求路径实例(例如,如果用户在带有/coffeebanners//teabanners/的路径上),反向方法将banners_adverts:index动态解析为三个 url 应用实例之一,而不是硬编码特定的 url 名称空间,如清单 2-18 所示。

现在让我们假设 banners 应用有一个内部模板,里面有一个到应用主index url 的链接。同样,考虑到多个应用实例的可能性,您将如何在模板中生成这个链接?依靠相同的app_name参数解决了清单 2-20 中所示的模板链接的问题。

# template banners/index.html
<a href="{% url 'banners_adverts:index' %}">{% url 'banners_adverts:index' %}</a>
Listing 2-20.Django template link

that leverages app_name to determine url

注意清单 2-20 中的{% url %}标签指向banners_adverts:indexbanners_adverts:index的解析过程与前面使用reverse方法的方法示例中概述的过程相同。

如果用户在咖啡横幅应用实例(即 url coffeebanners)中导航,那么 Django 将banners_adverts:index解析为咖啡横幅实例索引,如果用户在茶横幅应用实例(即 url teabanners)中导航,那么 Django 将banners_adverts:index解析为茶横幅实例索引,以此类推任何其他数量的实例。如果用户在横幅应用实例之外导航(即没有应用实例),Django 默认将banners_adverts:index解析为urls.py中最后定义的实例,即food-banners

正如您所看到的,app_name属性的目的是为 Django 应用设计者提供一种内部机制,通过这种机制,可以为动态适应同一应用的多个实例的命名 URL 集成逻辑。由于这个原因,它没有被广泛用于 url 命名,并且在大多数情况下可以被放弃,只使用namespacename属性。

查看方法请求

到目前为止,您已经使用了 Django 视图方法及其输入——request对象和参数——以及它们的输出,包括生成直接响应或依赖模板生成响应。然而,现在是时候更深入地了解视图方法请求中的可用内容以及生成视图方法响应的各种替代方法了。

到目前为止,您毫无疑问地放在视图方法中的request引用是django.http.request.HttpRequest类的一个实例。 3 这个request对象包含由视图方法之前存在的实体设置的信息:用户的 web 浏览器、运行应用的 web 服务器或应用上配置的 Django 中间件类。

下面的列表显示了一些在request参考中最常见的属性和方法:

  • request.method。-包含用于请求的 HTTP 方法(例如,GETPOST)。
  • request.GETrequest.POST。-包含分别作为 GET 或 POST 请求的一部分添加的参数。参数被括为一个django.http.request.QueryDict 4 个 实例。
    • request.POST.get('name',default=None)。-获取 POST 请求中的name参数的值,如果该参数不存在,则获取None。注意default可以用自定义值覆盖。
    • request.GET.getlist('drink',default=None)。-获取 GET 请求中的drink参数的值列表,或者如果参数不存在,则获取空列表None。注意default可以用自定义值覆盖。
  • request.META。-包含由浏览器或 web 服务器作为请求的一部分添加的 HTTP 标头。参数包含在一个标准的 Python 字典中,其中键是 HTTP 头名称——大写和下划线(例如,Content-Length作为键CONTENT_LENGTH)。
    • request.META['REMOTE_ADDR']。-获取用户的远程 IP 地址。
  • request.user。-包含链接到请求的 Django 用户的信息(如用户名、电子邮件)。注意user指的是django.contrib.auth包中的用户,通过 Django 中间件设置,这将在本章后面描述。

正如您可以从这个简短的列表中证明的那样,request引用包含许多可操作的信息来满足业务逻辑(例如,您可以基于来自用户'的 IP 地址的地理位置信息来响应某些内容)。在django.http.request.HttpRequestdjango.http.request.QueryDict属性和方法之间有超过 50 个request选项可用,所有这些都在书中相关的部分进行了解释——但是你可以在上一页的脚注链接中查看request选项的完整范围。

一旦您完成了从request引用中提取信息并对其进行相关的业务逻辑处理(例如,查询数据库,从第三方 REST 服务中获取数据),您就需要在一个视图方法中设置数据,以将其作为响应的一部分发送出去。

要在 Django 视图方法中设置数据,首先需要在方法体中声明或提取数据。您可以声明字符串、数字、列表、元组、字典或任何其他 Python 数据结构。

一旦在视图方法中声明或提取了数据,就可以创建一个字典来使数据在 Django 模板上可访问。字典键代表模板的引用名,而值是数据结构本身。清单 2-21 展示了一个视图方法,它声明了多个数据结构并将它们传递给 Django 模板。

from django.shortcuts import render

def detail(request,store_id='1',location=None):
    # Create fixed data structures to pass to template
    # data could equally come from database queries
    # web services or social APIs
    STORE_NAME = 'Downtown'
    store_address = {'street':'Main #385','city':'San Diego','state':'CA'}
    store_amenities = ['WiFi','A/C']
    store_menu = ((0,''),(1,'Drinks'),(2,'Food'))
    values_for_template = {'store_name':STORE_NAME, 'store_address':store_address, 'store_amenities':store_amenities, 'store_menu':store_menu}
    return render(request,'stores/detail.html', values_for_template)

Listing 2-21.Set up dictionary in Django view method for access in template

注意清单 2-21 中的render方法是如何包含values_for_template字典的。在前面的例子中,render方法只包含了request对象和一个处理请求的模板。在清单 2-21 中,字典作为最后一个render参数被传递。通过将一个字典指定为最后一个参数,该字典对模板可用——在本例中是stores/detail.html

Tip

如果您计划在多个模板上访问相同的数据,而不是在多个视图上声明它,您可以使用上下文处理器来声明它一次,并使它在所有项目模板上都可以访问。关于 Django 模板的下一章将讨论这个主题。

清单 2-21 中的字典包含键和值,它们是在方法体中声明的数据结构。字典键成为访问 Django 模板中的值的引用。

Output View Method Dictionary in Django Templates

虽然下一章将深入讨论 Django 模板,但是下面的代码片段显示了如何使用{{}}语法输出清单 2-21 中的字典值。

<h4>{{store_name}} store</h4>
<p>{{store_address.street}}</p>
<p>{{store_address.city}},{{store_address.state}}</p>
<hr/>
<p>We offer: {{store_amenities.0}} and {{store_amenities.1}}</p>
<p>Menu includes : {{store_menu.1.1}} and {{store_menu.2.1}}</p>

第一个声明{{store_name}}使用独立键显示Downtown值。其他访问声明使用点(.)符号,因为值本身是复合数据结构。

store_address键包含一个字典,因此要访问内部字典值,可以使用由点(.)分隔的内部字典键。store_address.street显示街道值,store_address.city显示城市值,store_address.state显示州值。

store _ 市容键包含一个使用类似点(.)符号来访问内部值。然而,因为 Python 列表没有键,所以使用列表索引号。store _ facilities . 0 显示列表 store _ facilities 和 store _ facilities 中的第一项。1 显示列表 store _ facilities 中的第二项。

store_menu key包含一个元组,由于缺少键,它也需要一个数字。{{store_menu.1.1}}显示store_menu的第二元组值的第二元组值,{{store_menu.2.1}}显示store_menu的第三元组的第二元组值。

查看方法响应

到目前为止,生成视图方法响应的render()方法实际上是一种快捷方式。你可以在清单 2-21 的顶部看到,render()方法是django.shortcuts包的一部分。

这意味着除了生成视图响应的render()方法之外,还有其他方法,尽管render()方法是最常用的技术。对于初学者来说,有三种类似的方法来生成带有模板支持的数据的视图方法响应,如清单 2-22 所示。

# Option 1)
from django.shortcuts import render

def detail(request,store_id='1',location=None):
    ...
    return render(request,'stores/detail.html', values_for_template)

# Option 2)
from django.template.response import TemplateResponse

def detail(request,store_id='1',location=None):
    ...
    return TemplateResponse(request, 'stores/detail.html', values_for_template)

# Option 3)
from django.http import HttpResponse
from django.template import loader, Context

def detail(request,store_id='1',location=None):
     ...
     response = HttpResponse()
     t = loader.get_template('stores/detail.html')
     c = Context(values_for_template)
     return response.write(t.render(c))

Listing 2-22.Django view method response alternatives

清单 2-22 中的第一个选项是django.shortcuts.render()方法,它显示了生成响应的三个参数:(必需的)request引用、(必需的)模板路由和(可选的)字典——也称为上下文——以及要传递给模板的数据。

清单 2-22 : content_type中没有显示render()方法的另外三个(可选)参数,这些参数为响应设置 HTTP Content-Type头,默认为settings.py中的DEFAULT_CONTENT_TYPE参数,后者本身默认为text/htmlstatus为默认为200的响应设置 HTTP Status代码;和using来指定模板引擎——或者是jinja2或者是django——来生成响应。下一节关于render()方法的 HTTP 处理描述了如何使用content_type & status,而第 3 和 4 章讨论了 Django 和 Jinja 模板引擎。

清单 2-22 中的第二个选项是django.template.response.TemplateResponse()类,它在输入方面与render()方法几乎相同。这两种变体的区别在于,一旦一个视图方法完成后,TemplateResponse()可以改变响应(例如,通过中间件),而render()方法被认为是视图方法完成后生命周期中的最后一步。当您预见到需要在多个视图方法完成工作后修改它们的视图方法响应时,您应该使用TemplateResponse(),这种技术将在本章关于视图方法中间件的后续章节中讨论。

清单 2-22 : content_type中没有显示的TemplateResponse()类还有四个(可选)参数,默认为text/htmlstatus默认为200charset设置来自 HTTP Content-Type头或settings.py中的DEFAULT_CHARSET的响应编码,其本身默认为utf-8;和using来指示模板引擎——或者jinja2或者django——来生成响应。

清单 2-22 中的第三个选项代表了最长的,但也是最灵活的响应创建过程。这个过程首先创建一个原始的HTTPResponse实例,然后用django.template.loader.get_template()方法加载一个模板,创建一个Context()类将值加载到模板中,最后将一个呈现的模板及其上下文写到HTTPResponse实例中。虽然这是三个选项中最长的一个,但是当视图方法响应需要高级选项时,这是首选。即将到来的关于内联和流式内容的内置响应快捷方式的部分,有更多关于HTTPResponse响应类型的细节。

HTTP 状态和内容类型标头的响应选项

浏览器在请求中设置 HTTP 头,告诉应用在处理时考虑某些特征。类似地,应用在响应中设置 HTTP 头,告诉浏览器考虑发送内容的某些特征。Django 等应用设置的最重要的 HTTP 头是StatusContent-Type

HTTP Status头是一个三位数的代码,表示给定请求的响应状态。Status值的例子有200,它是成功的 HTTP 请求的标准响应,以及404,它用于指示无法找到请求的资源。HTTP Content-Type报头是一个 MIME(多用途互联网邮件扩展)类型的字符串,用于指示响应中的内容类型。Content-Type值的例子有text/html,它是 HTML 内容响应的标准,以及image/gif,它用于指示响应是 GIF 图像。

默认情况下,除非有错误,所有用django.shortcuts.render()TemplateResponse()类或HttpResponse()类创建响应的 Django 视图方法——如清单 2-22 所示——创建一个响应,将 HTTP Status值设置为200,将 HTTP Content-Type设置为text/html。虽然这些默认值是最常见的,但是如果您想要发送不同类型的响应(例如,错误或非 HTML 内容),就有必要修改这些值。

覆盖清单 2-22 中三个选项中任意一个的 HTTP StatusContent-Type头值就像提供额外的参数status和/或content_type一样简单。清单 2-23 展示了这个过程的各种例子。

from django.shortcuts import render

# No method body(s) and only render() example provided for simplicity

# Returns content type text/plain, with default HTTP 200
return render(request,'stores/menu.csv', values_for_template, content_type='text/plain')

# Returns HTTP 404, wtih default text/html
# NOTE: Django has a built-in shortcut & template 404 response, described in the next section
return render(request,'custom/notfound.html',status=404)

# Returns HTTP 500, wtih default text/html
# NOTE: Django has a built-in shortcut & template 500 response, described in the next section
return render(request,'custom/internalerror.html',status=500)

# Returns content type application/json, with default HTTP 200
# NOTE: Django has a built-in shortcut JSON response, described in the next section
return render(request,'stores/menu.json', values_for_template, content_type='application/json')

Listing 2-23.HTTP Content-type and HTTP Status for Django view method responses

清单 2-23 中的第一个例子旨在返回一个包含纯文本内容的响应。注意render方法的content_type参数。清单 2-23 中的第二个和第三个例子将 HTTP Status代码设置为404500。因为 HTTP Status 404代码用于未找到的资源,所以render方法为此使用了一个特殊的模板。类似地,因为 HTTP Status 500代码用于指示错误,render方法也为此使用了一个特殊的模板。

Tip

Django 有内置的快捷方式和模板来处理 HTTP Status代码404500,以及 JSON 快捷方式响应,所有这些都将在下一节中描述,您可以使用它们来代替清单 2-23 中的示例。

清单 2-23 中的第四个也是最后一个例子旨在返回一个带有 JavaScript 对象符号(JSON)内容的响应。HTTP Content-Type application/json是通过异步 JavaScript (AJAX)消费 JavaScript 数据的浏览器发出的请求的常见要求。

常见 HTTP 状态的内置响应快捷方式和模板:404(未找到)、500(内部服务器错误)、400(错误请求)和 403(禁止)

虽然 Django 在找不到页面时会自动触发 HTTP 404 Status (Not Found)响应,并且在视图中出现未处理的异常时也会触发 HTTP 500 Status(内部服务器错误)响应,但是它有内置的快捷方式和模板,当您知道最终用户应该获得它们时,这些快捷方式和模板就应该在 Django 视图中明确使用。表 2-3 说明了触发特定 HTTP 状态响应的不同快捷方式。

表 2-3。

Django shortcut exceptions to trigger HTTP statuses

| HTTP 状态代码 | Python 代码示例 | | --- | --- | | 404(未找到) | 从 django.http 导入 Http404 引发 Http404 | | 500(内部服务器错误) | 引发异常 | | 400(错误请求) | 从 django.core.exceptions 导入可疑操作提出可疑操作 | | 403(禁止) | 从 django.core.exceptions 导入权限拒绝提升权限拒绝 |

*Django automatically handles not found pages raising HTTP 404 and unhandled exceptions raising HTTP 500

正如你在表 2-3 的例子中看到的,快捷方式的语法很简单。例如,您可以在 Django 视图中进行评估,如if article_id < 100:if unpayed_subscription:,并基于结果从表 2-3 中抛出异常,以便最终用户获得正确的 HTTP 状态响应。

那么当表 2-3 中的异常被触发时,除了 HTTP 状态之外,响应中发送的实际内容是什么呢?HTTP 400(错误请求)和 HTTP 403(禁止请求)的默认设置是一个单行 HTML 页面,分别显示“Bad Request (400)和“403 Forbidden”。对于 HTTP 404(未找到)和 HTTP 500(内部服务器错误),取决于settings.py中的DEBUG值。

如果 Django 项目在settings.py中有DEBUG=True,HTTP 404(未找到)生成一个带有可用 URL 的页面——如图 2-1 所示——HTTP500(内部服务器错误)生成一个带有详细错误的页面——如图 2-2 所示。如果 Django 项目在settings.py中有DEBUG=False,HTTP 404(未找到)会生成一个单行 HTML 页面,显示为“Not Found. The requested URL <url_location> was not found on this server.”,HTTP 500(内部服务器错误)会生成一个单行 HTML 页面,显示为“A server error occurred. Please contact the administrator"

A441241_1_En_2_Fig2_HTML.jpg

图 2-2。

HTTP 500 for Django project when DEBUG=True

A441241_1_En_2_Fig1_HTML.jpg

图 2-1。

HTTP 404 for Django project when DEBUG=True

还可以用定制模板覆盖所有以前的 HTTP 代码的默认响应页面。要使用定制的响应页面,您需要用所需的 HTTP 代码和.html扩展名创建一个模板。例如,对于 HTTP 403,您将创建403.html模板,对于 HTTP 500,您将创建500.html模板。所有这些定制的 HTTP 响应模板都需要放在TEMPLATES变量的DIRS列表中定义的文件夹中,以便 Django 在使用默认的 HTTP 响应模板之前找到它们。

Caution

自定义 404.html 和 500.html 页面仅在 DEBUG=False 时有效。

如果DEBUG=True,无论在正确的位置是否有404.html500.html模板,Django 都使用默认的响应行为,分别如图 2-1 和图 2-2 所示。您需要设置DEBUG=False以使自定义404.html500.html模板工作。

在某些情况下,使用定制的 HTTP 响应模板可能还不够。例如,如果您想将上下文数据添加到处理 HTTP 响应的自定义模板中,您需要自定义内置的 Django HTTP view 方法本身,因为没有其他方法可以将数据传递到这种类型的模板中。要定制内置的 Django HTTP 视图方法,您需要在项目的urls.py文件中声明特殊的处理程序。清单 2-24 展示了带有 Django 内置 HTTP Status视图方法的定制处理程序的urls.py文件。

# Overrides the default 400 handler django.views.defaults.bad_request
handler400 = 'coffeehouse.utils.views.bad_request'
# Overrides the default 403 handler django.views.defaults.permission_denied
handler403 = 'coffeehouse.utils.views.permission_denied'
# Overrides the default 404 handler django.views.defaults.page_not_found
handler404 = 'coffeehouse.utils.views.page_not_found'
# Overrides the default 500 handler django.views.defaults.server_error
handler500 = 'coffeehouse.utils.views.server_error'

urlpatterns = [....
]

Listing 2-24.Override built-in Django HTTP Status

view methods in urls.py

Caution

如果 DEBUG=True,handler404 和 handler500 处理程序将无法工作,Django 将继续使用内置的 Django HTTP 视图方法。要使 handler404 和 handler500 处理程序工作,需要设置 DEBUG=False。

正如您在清单 2-24 中看到的,在标准urlpatterns变量的正上方urls.py中有一系列变量。清单 2-24 中的每个变量代表一个 HTTP Status处理程序,其值对应于一个定制的 Django 视图来处理请求。例如,handler400表示所有 HTTP 400请求都应该由 Django 视图方法coffeehouse.utils.views.bad_request处理,而不是默认的django.views.defaults.bad_request。对于使用handler403的 HTTP 403请求、使用handler404的 HTTP 404请求和使用handler500的 HTTP 500 请求,采取相同的方法。

就定制 Django 视图方法的实际结构而言,它们与任何其他 Django 视图方法都是相同的。清单 2-26 显示了清单 2-25 中使用的定制视图方法的结构。

from django.shortcuts import render

def page_not_found(request):
    # Dict to pass to template, data could come from DB query
    values_for_template = {}
    return render(request,'404.html',values_for_template,status=404)

def server_error(request):
    # Dict to pass to template, data could come from DB query
    values_for_template = {}
    return render(request,'500.html',values_for_template,status=500)

def bad_request(request):
    # Dict to pass to template, data could come from DB query
    values_for_template = {}
    return render(request,'400.html',values_for_template,status=400)

def permission_denied(request):
    # Dict to pass to template, data could come from DB query
    values_for_template = {}
    return render(request,'403.html',values_for_template,status=403)

Listing 2-25.Custom views to override built-in Django HTTP view methods

正如您在清单 2-26 中看到的,定制的 HTTP 视图方法使用了与前面的视图方法示例相同的来自django.shortcut s 的render方法。这些方法指向一个由 HTTP Status代码命名的模板,使用一个可以在模板上访问的定制数据字典,并使用status参数来指示 HTTP 状态代码。

内联和流式内容的内置响应快捷方式

所有先前的视图响应示例都是基于通过模板结构化的内容来工作的。然而,有时使用模板输出响应是不必要的(例如,一行响应说“这里没有什么可看的”)。

其他时候,响应使用模板是没有意义的,例如 HTTP 301(永久重定向)或 HTTP 302(重定向),其中响应只需要一个重定向 url。表 2-4 说明了触发 HTTP 重定向的不同快捷方式。

表 2-4。

Django shortcuts for HTTP redirects

| HTTP 状态代码 | Python 代码示例 | | --- | --- | | 301(永久重定向) | 从 django.http 导入 HttpResponsePermanentRedirect 返回 HttpResponsePermanentRedirect("/") | | 302(重定向) | 从 django.http 导入 httpresponserdirect 返回 httpresponserdirect("/") |

表 2-4 中的两个示例都重定向到应用的主页(即"/")。但是,您也可以将重定向设置为任何应用 url,甚至是不同域上的完整 url(例如, http://maps.google.com/ )。

除了响应重定向快捷方式,Django 还提供了一系列响应快捷方式,您可以在其中添加内联响应。表 2-5 说明了具有内嵌内容响应的 HTTP 状态代码的各种其他快捷方式。

表 2-5。

Django shortcuts for inline and streaming content responses

| 目的或 HTTP 状态代码 | Python 代码示例 | | --- | --- | | 304(未修改) | 从 django.http 导入 HttpResponseNotModified 返回 HttpResponseNotModified()* | | 400(错误请求) | 从 django.http 导入 HttpResponseBadRequest 返回 HttpResponseBadRequest("

请求看起来不对劲

") |
| 404(未找到) | 从 django.http 导入 HttpResponseNotFound 返回 HttpResponseNotFound("

Ups,我们找不到那个页面

") |
| 403(禁止) | 从 django.http 导入 HttpResponseForbidden 返回 HttpResponseForbidden("这里什么都看不到",content_type="text/plain ") |
| 405(不允许的方法) | 从 django.http 导入 HttpResponseNotAllowed 返回 HttpResponseNotAllowed("

方法不允许使用

") |
| 410(走了) | 从 django.http 导入 HttpResponseGone 返回 HttpResponseGone("不再在这里",content_type="text/plain ") |
| 500(内部服务器错误) | 从 django.http 导入 httpresponseserverror 返回 httpresponseserverror("

Ups,这是我们的失误,抱歉!

)) |
| 将数据序列化为 JSON 的内联响应(默认为 HTTP 200 和内容类型 application/json) | 从 django.http 导入 JSON response data _ dict = { ' name ':' Downtown ',' address':'Main #385 ',' city':'San Diego ',' state':'CA'}返回 JsonResponse(data_dict) |
| 流数据内联响应(默认为 HTTP 200 和流内容,它是字符串的迭代器) | 从 django.http 导入 StreamingHttpResponse 返回 streaming httpresponse(large _ data _ structure) |
| 流二进制文件的内联响应(默认为 HTTP 200 和流内容) | 从 django.http 导入文件响应返回文件响应(open('Report.pdf ',' rb ')) |
| 带有任何 HTTP 状态代码的内联响应(默认为 HTTP 200) | 从 django.http 导入 HttpResponse 返回 HttpResponse("

Django 内联响应

") |

  • The HTTP 304 status code indicates a “Not Modified” response, so you can't send content in the response, it should always be empty.

正如您在表 2-5 的示例中所看到的,有多种快捷方式可以生成带有内嵌内容的不同 HTTP 状态响应,并且完全不需要使用模板。另外,你可以看到表 2-5 中的快捷键也可以接受content_type参数,如果内容是 HTML 以外的东西(即content_type=text/html)。

由于非 HTML 响应在 web 应用中变得非常普遍,您可以看到表 2-5 还显示了三个 Django 内置的响应快捷方式来输出非 HTML 内容。JsonResponse类用于将内联响应转换成 JavaScript 对象符号(JSON)。因为这个响应将有效负载转换为 JSON 数据结构,所以它会自动将内容类型设置为application/jsonStreamingHttpResponse类的设计目的是在不需要将整个有效负载放在内存中的情况下传输响应,这种情况有助于大型有效负载响应。FileResponse类是StreamingHttpResponse的子类,旨在传输二进制数据(如 PDF 或图像文件)。

这将我们带到表 2-5 中的最后一个条目,即HttpResponse类。事实证明,表 2-5 中的所有快捷方式都是HttpResponse类的定制子类,我最初在清单 2-22 中将其描述为创建视图响应的最灵活的技术之一。

HttpResponse方法有助于为没有直接快捷方法的 HTTP 状态代码创建响应(例如,HTTP408[请求超时],HTTP429[太多请求]),或者包含性地利用模板来生成内联响应,如清单 2-26 所示。

from django.http import HttpResponse
from django.utils import timezone
from django.template import loader, Context

response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=Users_%s.csv' % str(timezone.now().today())
t = loader.get_template('dashboard/users_csvexport.html')
c = Context({'users': sorted_users,})
response.write(t.render(c))
return response

Listing 2-26.HttpResponse with template and custom CSV file download

清单 2-26 中的HTTPResponse对象是用text/csv内容类型生成的,用来通知请求方(例如浏览器)它即将接收 CSV 内容。接下来,Content-Disposition报头还告诉请求方(例如浏览器)尝试下载名为Users_%s.csv的内容,其中用当前服务器日期替换了%s

接下来,使用loader模块,我们使用get_template方法加载模板users_csvexport.html,该模板将具有类似 CSV 的结构,带有数据占位符。然后我们创建一个Context对象来保存将填充模板的数据,在本例中,它只是一个名为 u sers的变量。接下来,我们用context对象调用模板的render方法,以便用数据填充模板的数据占位符。最后,通过write方法将渲染后的模板写入response对象,并返回response对象。

HttpResponse类在属性和方法之间提供了 20 多个选项, 5 以及content_typestatus参数。

视图方法中间件

在大多数情况下,在视图方法中,请求和响应中的数据是以逐段的方式添加、删除或更新的。然而,有时将这些更改应用于所有请求和响应会很方便。

例如,如果您想在所有视图方法上访问某些数据,使用中间件类使这些数据可以跨所有请求访问会更容易。就像您想对所有响应进行安全检查一样,使用中间件类更容易在全局范围内完成。

因为中间件是一个相当抽象的概念,所以在我描述 Django 中间件类的结构之前,我将带您浏览各种内置的 Django 中间件类,这样您就可以更好地理解中间件在哪里是好的设计选择。

内置中间件类

Django 配备了一系列中间件类,其中一些在所有 Django 项目中默认启用。如果你打开 Django 项目的settings.py文件,你会注意到MIDDLEWARE变量,其默认内容如清单 2-27 所示。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Listing 2-27.Default Django middleware classes in MIDDLEWARE

正如您在清单 2-27 中看到的,Django 项目在开箱即用状态下启用了七个中间件类,因此所有请求和响应都被设置为通过这七个类运行。如果您计划利用 Django 的主要特性,我建议您不要删除这些默认的中间件类。但是,如果您愿意,可以将MIDDLEWARE变量留空;请注意这样做可能会破坏 Django 的某些功能。

为了让您更好地理解清单 2-27 中的 Django 中间件类的功能,并帮助您做出是否禁用它们的更明智的决定,表 2-6 描述了每个中间件类的功能。

表 2-6。

Django default middleware classes and functionality

| 中间件类 | 功能 | | --- | --- | | django . middleware . security . security 中间件 | 提供安全性增强,例如:基于 SECURE_SSL_REDIRECT 和 SECURE_SSL_HOST 设置的 SSL 重定向。通过各种设置实现严格的运输安全。 | | django . contrib . sessions . middleware . session middleware | 启用会话支持。 | | django . middleware . common . common 中间件 | 提供一组通用的功能,例如:禁止访问 DISALLOWED_USER_AGENTS 设置中的用户代理,该设置可以是已编译的正则表达式对象的列表。根据 APPEND_SLASH 和 PREPEND_WWW 设置执行 url 重写,以便规范化 URL。为非流式响应设置 HTTP Content-Length 标头。 | | django . middleware . csrf . csrfview middleware | 通过在发布表单中添加隐藏的表单域并检查请求的正确值,来防止跨站点请求伪造。 | | django . contraib . auth . middleware . authenticationmiddleware | 将表示当前登录用户的用户属性添加到每个传入的 HttpRequest 对象中。注意:这个中间件类依赖于中间件 django . contrib . sessions . middleware . session middleware 的功能,必须出现在它的后面。 | | django . IB . messages . middleware . message middleware .讯息中介软体 | 启用基于 cookie 和基于会话的消息支持。注意:这个中间件类依赖于中间件 django . contrib . sessions . middleware . session middleware 的功能,必须出现在它的后面。 | | django . middleware . click packing . xframoptions middleware . django .中介软体 | 通过 X-Frame-Options 标题提供点击劫持保护。关于什么是点击劫持的更多细节请参见: [`http://en.wikipedia.org/wiki/Clickjacking`](http://en.wikipedia.org/wiki/Clickjacking) 。 |

正如您在表 2-6 中所看到的,尽管各种默认中间件类的目的差异很大,但它们的功能适用于需要在项目中所有请求或响应中应用的特性。

表 2-6 中中间件类的另一个重要因素是一些依赖于另一些。例如,AuthenticationMiddleware类的设计是基于这样的假设,即它可以访问由SessionMiddleware类提供的功能。这种依赖性很重要,因为它使得中间件类定义顺序相关(例如,在MIDDLEWARE中,某些中间件类需要在其他类之前定义),这个主题我将在下一节中详细阐述。

除了表 2-6 中给出的默认中间件类,Django 还提供了其他中间件类。表 2-7 展示了你可以在你的项目中利用的 Django 中间件类的剩余集合,这对你没有必要从头开始编写中间件类很有帮助。

表 2-7。

Other Django middleware classes and functionality

| 中间件类 | 功能 | | --- | --- | | django . middleware . cache .updatecache 中间件 | 响应阶段缓存中间件,如果响应可缓存,则更新缓存。注意:UpdateCacheMiddleware 必须是中间件中的第一部分,以便在响应阶段最后调用它。 | | django . middleware . cache . fetchfromcachemiddleware | 从缓存中获取页面的请求阶段缓存中间件。注意:FetchFromCacheMiddleware 必须是中间件中的最后一部分,这样它将在请求阶段最后被调用。 | | django . middleware . common . broken link mail middleware | 向经理发送断开链接通知电子邮件。 | | django,中间件,exceptionmiddleware | Django 使用这个中间件,不管您是否将它包含在中间件中;但是,如果您自己的中间件需要将它处理的异常转换成适当的响应,您可能希望创建子类。 | | django . middleware . gzip . gzip middleware | 为理解 GZip 压缩的浏览器压缩内容。注意:GZipMiddleware 应该放在任何其他需要读取或写入响应体的中间件之前,以便压缩发生在之后。只有当请求方在 HTTP Accept-Encoding 头上发送 gzip,并且内容大于 200 个字节,并且响应没有设置 HTTP Content-Encoding 头时,这个中间件才会进行压缩。 | | django.middleware.http | 处理条件 GET 操作。如果响应没有 HTTP ETag 头,则会添加一个。如果响应有一个 ETag 或 Last-Modified 头,而请求有 If-None-Match 或 If-Modified-Since,则响应被替换为 HttpNotModified。 | | Django,中间件,本地,本地 | 解析请求并决定在当前线程上下文中安装什么翻译对象。这允许页面被动态地翻译成用户想要的语言。 | | django . contrib . sites . middleware . currentsitemiddleware | 将表示当前站点的站点属性添加到每个传入的 HttpRequest 对象中。 | | django . contraib . auth . middleware . persistent treomeusermiddleware | 添加 REMOTE_USER -在请求中可用。META -通过外部来源(如 web 服务器)进行 Django 认证。 | | django . contraib . auth . middleware . remote user middleware | 允许 web 服务器提供的身份验证。如果 request.user 没有通过身份验证,这个中间件会尝试对 REMOTE_USER 请求头中传递的用户名进行身份验证。如果身份验证成功,用户将自动登录,以便将用户保留在会话中。 | | django . contraib . flatpages . middleware . flatpagefallback middleware . django . flatpages 回退中间件 | 每当 Django 应用引发 404 错误时,这个中间件都会检查 flatpages 数据库中所请求的 url,这是最后的手段。 | | django . contrib . redirects . middleware . redirectfallback middleware | 每次 Django 应用引发 404 错误时,这个中间件都会检查重定向数据库中请求的 url,这是最后一招。 |

现在您已经了解了 Django 的内置中间件类以及它们的用途,让我们来看看中间件类的结构及其执行过程。

中间件结构和执行过程

Django 中间件类有两个必需的方法和三个可选的方法,它们在视图请求/响应生命周期的不同点执行。清单 2-28 展示了一个示例中间件类及其各个部分。

class CoffeehouseMiddleware(object):

    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization on start-up

    def __call__(self, request):
        # Logic executed on a request before the view (and other middleware) is called.

        # get_response call triggers next phase
        response = self.get_response(request)

        # Logic executed on response after the view is called.

        # Return response to finish middleware sequence
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        # Logic executed before a call to view
        # Gives access to the view itself & arguments

    def process_exception(self,request, exception):
        # Logic executed if an exception/error occurs in the view

    def process_template_response(self,request, response):
        # Logic executed after the view is called,
        # ONLY IF view response is TemplateResponse, see listing 2-22

Listing 2-28.Django middleware class structure

为了让视图方法执行清单 2-28 中的 Django 中间件类,必须将中间件类添加到settings.py中的MIDDLEWARE变量中。例如,如果清单 2-28 中的CoffeehouseMiddleware类存储在coffeehouse/utils/项目文件夹下名为middleware.py的文件/模块中,您可以将coffeehouse.utils.middleware.CoffeeMiddleware语句添加到settings.py中的MIDDLEWARE值列表中。

接下来,我将描述清单 2-28 中显示的所有 Django 中间件类中所需的两个方法:

  • __init__。-在所有 Python 类中用于引导对象实例。Django 中间件类中的__init__方法只在支持 Django 应用的 web 服务器启动时被调用一次。Django 中间件中的__init__方法必须声明一个get_response输入,它表示对先前中间件类响应的引用。get_response输入被分配给一个实例变量——也称为get_response——稍后用于中间件类的主处理逻辑。当我扩展 Django 中间件的执行过程时,get_response引用的目的将会变得更加清晰。
  • __call__。-在所有 Python 类中使用,将对象实例作为函数调用。每个应用请求都会调用 Django 中间件类中的__call__方法。正如您在清单 2-28 中看到的,__call__方法声明了一个request输入,它表示视图方法使用的同一个HttpRequest对象。__call__方法分为三个阶段:
    • 在视图方法调用之前。-一旦触发了__call__方法,您就有机会在将request引用传递给视图方法之前对其进行修改。如果你想在request中添加或修改一些东西,在它被移交给一个视图方法之前,这就是要做的阶段。
    • 触发视图方法调用。-在您修改(或不修改)原始的request之后,您必须将控制权移交给 view 方法,以便它能够运行。当您将request传递给在__init__方法中设置的self.get_response参考时,该阶段被触发。这个阶段实际上是在说,“我已经完成了对request的修改,继续把它交给视图方法,这样它就可以运行了。”
    • Post 视图方法调用。-一旦查看方法完成,结果将被分配给__call__中的response引用。在这个阶段,您有机会在视图方法完成后执行逻辑。您可以通过简单地从视图方法返回response引用(即return response)来退出这个阶段。

这是这两个必需方法执行的每个 Django 中间件类背后的核心逻辑。现在让我们看看清单 2-28 中给出的三个可选中间件类方法:

  • process_view。-所需的中间件方法——__init____call__——缺乏任何关于他们正在使用的视图方法的知识。在视图方法被触发之前,process_view方法允许您访问视图方法及其参数。如果存在,process_view中间件方法在__call__之后和调用self.get_response(request)之前被调用,这触发了视图方法。
  • process_exception。-如果视图方法的逻辑中出现错误,就会调用process_exception中间件方法,让您有机会执行错误后清理逻辑。
  • process_template_response。-在调用 self.get_response(request)并且视图方法完成后,可能需要更改响应本身以对其执行附加逻辑(例如,修改上下文或模板)。如果存在的话,process_template_response中间件方法会在视图方法完成后被调用,让您有机会修改响应。

Warning

只有当视图方法返回 TemplateResponse 时,才会触发 process_template_response 中间件方法。如果视图方法使用 render()生成响应,则不会触发 process_template_response。更多细节请参见查看方法响应的清单 2-22 。

总之,单个中间件类的执行过程如下:

  1. __init__方法被触发(在服务器启动时)。
  2. __call__触发的方法(针对每个请求)。
  3. 如果声明,process_view()方法被触发。
  4. 查看方法从__call__中的self.get_response(request)语句开始。
  5. 如果声明,process_exception()方法在视图中发生异常时触发。
  6. 查看方法完成。
  7. 如果声明,当视图返回TemplateResponseprocess_template_response()被触发。

尽管理解单个中间件类的执行过程很重要,但更重要的方面是理解多个中间件类的执行过程。正如我在本节开始时提到的,Django 项目支持清单 2-27 中所示的七个中间件类,因此多个中间件类的执行更像是常态而不是例外。

Django 中间件类是背靠背执行的,但是 view 方法代表了它们执行顺序中的一个转折点。清单 2-27 中默认中间件类的执行顺序如下:

Server start-up

__init__ on django.middleware.security.SecurityMiddleware called
__init__ on django.contrib.sessions.middleware.SessionMiddleware called
__init__ on django.middleware.common.CommonMiddleware called
__init__ on django.middleware.csrf.CsrfViewMiddleware called
__init__ on django.contrib.auth.middleware.AuthenticationMiddleware called
__init__ on django.contrib.messages.middleware.MessageMiddleware called
__init__ on django.middleware.clickjacking.XframeOptionsMiddleware called

request for index() view method

__call__ on django.middleware.security.SecurityMiddleware called
process_view on django.middleware.security.SecurityMiddleware called (if declared)
__call__ on django.contrib.sessions.middleware.SessionMiddleware called
process_view on django.contrib.sessions.middleware.SessionMiddleware called (if declared)
__call__ on django.middleware.common.CommonMiddleware called
process_view on django.middleware.common.CommonMiddleware called (if declared)
__call__ on django.middleware.csrf.CsrfViewMiddleware called
process_view on django.middleware.csrf.CsrfViewMiddleware called (if declared)
__call__ on django.contrib.auth.middleware.AuthenticationMiddleware called
process_view on django.contrib.auth.middleware.AuthenticationMiddleware called (if declared)
__call__ on django.contrib.messages.middleware.MessageMiddleware called
process_view on django.contrib.messages.middleware.MessageMiddleware called (if declared)
__call__ on django.middleware.clickjacking.XframeOptionsMiddleware called
process_view on django.middleware.clickjacking.XframeOptionsMiddleware called (if declared)

start index() view method logic

if an exception occurs in index() view
process_exception on django.middleware.clickjacking.XframeOptionsMiddleware called (if declared)
process_exception on django.contrib.messages.middleware.MessageMiddleware called (if declared)
process_exception on django.contrib.auth.middleware.AuthenticationMiddleware called(if declared)
process_exception on django.middleware.csrf.CsrfViewMiddleware called (if declared)
process_exception on django.middleware.common.CommonMiddleware called (if declared)
process_exception on django.contrib.sessions.middleware.SessionMiddleware called (if declared)
process_exception on django.middleware.security.SecurityMiddleware called (if declared)

if index() view returns TemplateResponse
process_template_response on django.middleware.clickjacking.XframeOptionsMiddleware called (if declared)
process_template_response on django.contrib.messages.middleware.MessageMiddleware called (if declared)
process_template_response on django.contrib.auth.middleware.AuthenticationMiddleware called(if declared)
process_template_response on django.middleware.csrf.CsrfViewMiddleware called (if declared)
process_template_response on django.middleware.common.CommonMiddleware called (if declared)
process_template_response on django.contrib.sessions.middleware.SessionMiddleware called (if declared)
process_template_response on django.middleware.security.SecurityMiddleware called (if declared)

请注意,在进入视图方法的执行之前,中间件类的执行顺序遵循声明的顺序(即,先声明的先运行,最后声明的最后运行)。但是,一旦执行了视图方法,中间件的执行顺序就会颠倒(即,最后声明的先运行,首先声明的最后运行)。

这种行为类似于拔塞钻,要到达中心(视图方法),您需要向一个方向移动(1 到 7),要向外移动,您需要向相反的方向移动(7 到 1)。因此中间件方法process_exceptionprocess_template_response__init____call__process_view的相反顺序执行。

清单 2-27 中默认 Django 中间件类的执行过程如图 2-3 所示。

A441241_1_En_2_Fig3_HTML.jpg

图 2-3。

Django middleware execution process

查看方法中的中间件 Flash 消息

当用户执行一个操作(例如,提交一个表单)时,通常会使用快速消息,并且有必要告诉他们该操作是成功的还是有某种错误。其他时候,快速消息用作网页上的一次性通知,告诉用户某些事件(例如,网站维护或特殊折扣)。图 2-4 显示了一组示例简讯。

A441241_1_En_2_Fig4_HTML.jpg

图 2-4。

Web page flash messages Django Flash Messages Require a Django App, Middleware, and a Template Context Processor

默认情况下,所有 Django 项目都支持 flash 消息。但是,如果您调整了项目的 settings.py 文件,您可能会无意中禁用了 flash 消息。

为了使 Django flash 消息工作,您必须确保在 settings.py 中设置以下值:变量 INSTALLED_APPS 具有 django.contrib.messages 值,变量 MIDDLEWARE 具有 Django . contrib . messages . messages . message MIDDLEWARE 值,并且模板变量的选项中的 context_processors 列表具有 Django . contrib . messages . context _ processors . messages 值。

正如您在图 2-4 中看到的,可以有不同类型的 flash 消息,在技术上称为级别。Django 遵循标准的 Syslog 标准严重性级别,并支持表 2-8 中描述的五个内置消息级别。

表 2-8。

Django built-in flash messages

| 水平常数 | 标签 | 价值 | 目的 | | --- | --- | --- | --- | | 调试 | 调试 | Ten | 在生产部署中将被忽略(或删除)的开发相关消息。 | | 信息 | 信息 | Twenty | 用户的信息性消息。 | | 成功ˌ成就 | 成功 | Twenty-five | 操作成功,例如,“联系信息已成功发送” | | 警告 | 警告 | Thirty | 故障没有发生,但可能即将发生。 | | 错误 | 错误 | Forty | 操作不成功或发生了其他故障。 |

添加即时消息

Django flash 消息是基于每个请求进行管理的,并被添加到视图方法中,因为这是确定 flash 消息是否被授权的最佳位置。要添加消息,您可以使用django.contrib.messages包。

django.contrib.messages包添加 flash 消息有两种技术:一种是通用的add_message()方法,另一种是表 2-8 中描述的不同级别的快捷方式方法。清单 2-29 展示了不同的技术。

from django.contrib import messages

# Generic add_message method
messages.add_message(request, messages.DEBUG, 'The following SQL statements were executed: %s' % sqlqueries) # Debug messages ignored by default
messages.add_message(request, messages.INFO, 'All items on this page have free shipping.')
messages.add_message(request, messages.SUCCESS, 'Email sent successfully.')
messages.add_message(request, messages.WARNING, 'You will need to change your password in one week.')
messages.add_message(request, messages.ERROR, 'We could not process your request at this time.')

# Shortcut level methods
messages.debug(request, 'The following SQL statements were executed: %s' % sqlqueries) # Debug messages ignored by default
messages.info(request, 'All items on this page have free shipping.')
messages.success(request, 'Email sent successfully.')
messages.warning(request, 'You will need to change your password in one week.')
messages.error(request, 'We could not process your request at this time.')

Listing 2-29.Techniques to add Django flash messages

清单 2-29 中的第一组样本使用add_message()方法,而第二组样本使用快捷方式级别方法。清单 2-29 中的两组样本产生相同的结果。

如果你仔细观察清单 2-29 ,你会注意到两个DEBUG级消息都有行尾注释# Ignored by default。Django 消息框架默认处理所有高于INFO级别的消息,这意味着DEBUG消息——如表 2-8 所述,是一个较低级别的消息阈值——被忽略,即使它们可能被定义。

您可以更改默认的 Django 消息级别阈值,以包含所有消息级别,或者包含性地降低默认的INFO阈值。默认的消息级别阈值可以用两种方法之一来改变:用清单 2-30 中所示的MESSAGE_LEVEL变量在settings.py中全局地(即对于整个项目)改变,或者用清单 2-31 中所示的django.contrib.messages包的set_level方法在每个请求的基础上改变。

# Reduce threshold to DEBUG level in settings.py
from django.contrib.messages import constants as message_constants
MESSAGE_LEVEL = message_constants.DEBUG

# Increase threshold to WARNING level in setting.py
from django.contrib.messages import constants as message_constants
MESSAGE_LEVEL = message_constants.WARNING

Listing 2-30.Set default Django message level globally in settings.py

# Reduce threshold to DEBUG level per request
from django.contrib import messages
messages.set_level(request, messages.DEBUG)

# Increase threshold to WARNING level per request
from django.contrib import messages
messages.set_level(request, messages.WARNING)

Listing 2-31.Set default Django message level on a per request basis

清单 2-30 中的第一个MESSAGE_LEVEL定义将默认消息级别更改为DEBUG,这意味着所有消息级别定义都会得到处理,因为DEBUG是最低的阈值。清单 2-30 中的第二个MESSAGE_LEVEL定义将默认消息级别更改为WARNING,这意味着处理高于WARNING(含)的消息级别(即WARNINGERROR)。

清单 2-31 中的第一个set_level定义将默认的请求消息级别更改为DEBUG,这意味着所有的消息级别定义都会得到处理,因为DEBUG是最低的阈值。清单 2-31 中的第二个set_level定义将默认消息级别更改为WARNING,这意味着处理高于WARNING(含)的消息级别(即WARNINGERROR)。

如果您同时定义了两个默认的消息级别机制,默认的请求消息级别优先于默认的全局消息级别定义(例如,如果您定义了messages.set_level(request, messages.WARNING),则处理高于WARNING(包含)的消息级别,即使全局MESSAGE_LEVEL变量被设置为MESSAGE_LEVEL = message_constants.DEBUG以包含所有消息。

除了设置 flash 消息和了解忽略来自某个级别的消息的内置阈值机制之外,您还必须认识到清单 2-29 中的消息定义假设 Django 消息框架的先决条件在settings.py中声明——如本节开始的侧栏中所述。

因为您最终可能会将 Django 项目发布给第三方,并且无法控制最终的部署settings.py文件,所以 Django messages 框架提供了在必要的先决条件没有在settings.py.中声明的情况下静默忽略消息定义的能力。为了在没有声明先决条件的情况下静默忽略消息定义,您可以向任何一种添加消息的技术添加fail_silently=True属性,如清单 2-32 所示。

from django.contrib import messages

# Generic add_message method, with fail_silently=True
messages.add_message(request, messages.INFO, 'All items on this page have free shipping.',fail_silently=True)

# Shortcut level method, with fail_silently=True
messages.info(request, 'All items on this page have free shipping.',fail_silently=True)

Listing 2-32.Use of the fail_silently=True attribute to ignore errors in case Django messages framework not installed

现在您已经知道了如何添加消息以及添加消息时需要记住的重要方面,让我们来看看如何访问消息。

访问即时消息

您最常访问 Django flash 消息的地方是显示给最终用户的 Django 模板。作为一种快捷方式,感谢上下文处理器django.contrib.messages.context_processors.messages Django flash 消息通过messages变量在所有模板上可用。但是在我们开始实际的模板示例之前,让我们快速看一下 Django flash 消息的结构。

当您使用上一节描述的技术之一添加 Django flash 消息时,Django 会创建一个storage.base.Message类的实例。表 2-9 描述了storage.base.Message级的结构。

表 2-9。

Django storage.base.Message structure

| 属性 | 描述 | 例子 | | --- | --- | --- | | 消息 | 消息的实际文本。 | 此页面上的所有项目都有免费送货。 | | 水平 | 描述消息类型的整数(见表 2-8 中的值列)。 | Twenty | | 标签 | 由空格分隔的所有消息标记(extra_tags 和 level_tag)组合而成的字符串。 | 信息 | | 额外标签 | 包含此消息的自定义标记的字符串,用空格分隔。 | 默认情况下为空。 | | 级别标签 | 级别的字符串表示形式。 | 信息 |

正如您在表 2-9 中看到的,您可以利用几个属性在 Django 模板中显示。清单 2-33 显示了样板模板代码,您可以用它来显示请求中设置的所有 flash 消息。

{% if messages %}
<ul class="messages">
    {% for msg in messages %}
    <li>
        <div class="alert alert-{{msg.level_tag}}" role="alert">
        {{msg.message}}
        </div>
    </li>
    {% endfor %}
</ul>
{% endif %}
Listing 2-33.
Boilerplate code

to use in Django template to display Django flash messages

清单 2-33 从检查messages变量是否存在开始——它包含所有的 flash 消息——如果存在,那么一个 HTML 列表从<ul>开始。接下来,对messages中的所有元素进行循环,其中每个元素对应于一个storage.base.Message实例。对于这些元素中的每一个,都创建了一个列表和部分标签——<li><div>——将level_tag属性作为 CSS 类输出,将消息属性作为<div>内容输出。

您可以根据需要修改清单 2-33 中的样板代码,例如,添加条件并输出特定的消息级别,或者利用其他storage.base.Message属性,等等。

Note

清单 2-33 中的 HTML 代码使用 CSS class = " alert alert-{ { msg . level_tag } } ",该代码根据 level _ tag 属性呈现为 class="alert alert-info "或 class="alert alert-success "。这些 CSS 类是 CSS 引导框架的一部分。通过这种方式,你可以快速格式化 flash 消息,看起来如图 2-2 所示。

虽然您通常会在 Django 模板中访问 Django flash 消息,但这并不意味着您不能在其他地方访问它们,比如视图方法。您还可以通过django.contrib.messages包的get_messages()方法访问请求中的 Django flash 消息。清单 2-34 展示了使用get_messages()方法的代码片段。

from django.contrib import messages

the_req_messages = messages.get_messages(request)
for msg in the_req_messages:
    do_something_with_the_flash_message(msg)

Listing 2-34.Use of get_messages() method

to access Django flash messages

在清单 2-34 中,get_messages()方法接收request作为输入,并将结果赋给the_req_messages变量。接下来,对the_req_messages中的所有元素进行循环,其中每个元素都对应于一个storage.base.Message实例。对于这些元素中的每一个,都会调用方法do_something_with_the_flash_message来处理每个 flash 消息。

访问 Django flash 消息时需要理解的一个重要方面是消息本身的持续时间。Django flash 消息被标记为在主 messages 实例上发生迭代时被清除,并在处理响应时被清除。

对于 Django 模板中的访问,这意味着如果你不能在 Django 模板中像清单 2-33 中那样进行迭代,并且请求中有 flash 消息,这可能会导致陈旧或幻影消息出现在其他地方,直到进行迭代并处理响应。对于 Django 视图方法中的访问(即使用get_messages()),这没有影响,因为即使您可以对主消息实例进行迭代——因此,将消息标记为待清除——在 Django 视图方法中不会处理响应,因此消息永远不会被清除,只是被标记为待清除。

基于类的视图

在第一章和本章的开始——在清单 2-1——你看到了如何定义一个 Django url 并使它在不需要视图方法的情况下使用 Django 模板操作。这可能是由于django.views.generic.TemplateView类,它被称为基于类的视图。

与使用 Django HttpRequest输入参数并输出 Django HttpResponse的标准 Python 方法支持的 Django 视图方法不同,基于类的视图通过成熟的 Python 类提供其功能。这反过来又允许 Django 视图按照面向对象编程(OOP)原则(例如封装、多态和继承)进行操作,从而提高可重用性并缩短实现时间。

尽管基于 Django 类的视图代表了一种更强大的创建 Django 视图的方法,但它们只是到目前为止您所使用的视图方法的一种替代方法。如果您想在 Django 请求上快速执行业务逻辑,您可以继续使用视图方法,但是对于要求更高的视图需求(例如表单处理、样板模型查询),基于类的视图可以节省您大量的时间。

内置的基于类的视图

django.views.generic.TemplateView基于类的视图提供的功能确实节省了时间。虽然可以配置一个 url 来执行一个空视图方法,然后将控制发送给一个模板,但是TemplateView类允许这个过程在一行中完成。

除了TemplateView基于类的视图之外,Django 还提供了许多其他内置的基于类的视图,使用类似 OOP 的原则来缩短普通 Django 视图操作的创建过程。表 2-10 展示了 Django 的内置视图类。

表 2-10。

Built-in classes for views

| 班级 | 描述 | | --- | --- | | django.views.generic.View | 所有基于类的视图的父类,提供核心功能。 | | django . views . generic . template view | 允许 url 返回模板的内容,而不需要视图。 | | django . views . generic . redirect view | 允许 url 执行重定向,而不需要视图。 | | django . views . generic . archive index view django . views . generic . yeararchiveview django . views . generic . monthararchiveview django . views . generic . weekarchiveview django . views . generic . dayarchiveview django . views . generic . todayarchiveview django . views . generic . date detail view | 允许视图返回基于日期的对象结果,而无需显式执行 Django 模型查询。 | | django . views . generic . create view django . views . generic . detail view django . views . generic . update view django . views . generic . delete view django . views . generic . listview django . views . generic . formview | 允许视图执行创建-读取-更新-删除(CRUD)操作,而不需要显式执行 Django 模型查询。 |

在本章接下来也是最后一节,我将解释表 2-10 上半部分的类,这样你可以更好地理解 Django 基于类的视图的结构和执行过程。表 2-10 下半部分中涉及 Django 模型的基于类的视图在关于 Django 模型的单独章节中描述。

基于类的视图结构和执行

要创建基于类的视图,您需要创建一个从表 2-10 中的一个类继承而来的类。清单 2-35 显示了使用这种继承技术的基于类的视图,以及执行基于类的视图的相应 url 定义。

# views.py
from django.views.generic import TemplateView

class AboutIndex(TemplateView):
      template_name = 'index.html'

      def get_context_data(self, **kwargs):
         # **kwargs contains keyword context initialization values (if any)
         # Call base implementation to get a context
         context = super(AboutIndex, self).get_context_data(**kwargs)
         # Add context data to pass to template
         context['aboutdata'] = 'Custom data'
         return context

#urls.py
from coffeehouse.about.views import AboutIndex

urlpatterns = [
    url(r'^about/index/',AboutIndex.as_view(),{'onsale':True}),
]

Listing 2-35.Class-based view inherited from TemplateView

with url definition

我选择首先创建一个继承自TemplateView的视图,因为它很简单,而且您已经知道了这个类的用途。清单 2-35 中的例子和本章中清单 2-1 中的第一个例子产生了几乎相同的结果。

不同之处在于,清单 2-1 直接声明了一个TemplateView类实例作为 url 的一部分(例如TemplateView.as_view(template_name='index.html')),而清单 2-35 声明了一个名为AboutIndexTemplateView子类的实例。比较这两种方法,您可以对基于类的视图的 OOP 行为有一个初步的感觉。

清单 2-35 的第一部分声明了基于AboutIndex类的视图,它从TemplateView类继承了它的行为。注意这个类声明了template_name属性和get_context_data()方法。

AboutIndex类中的template_name值充当基于类的视图的默认模板。但是在 OOP 方式中,相同的值可以通过在实例创建时提供一个值来覆盖(例如,AboutIndex.as_view(template_name='other.html')使用other.html模板)。

AboutIndex类中的get_context_data方法允许您向类视图模板添加上下文数据。请注意,get_context_data方法的签名使用**kwargs来访问上下文初始化值(例如,在 url 或父类视图中声明的值),并根据标准 OOP Python 实践使用 Python super()方法调用父类的get_context_data方法。接下来,get_context_data方法使用aboutdata键添加额外的上下文数据,并返回修改后的context引用。

在清单 2-35 的第二部分中,您可以看到基于AboutIndex类的视图是如何首先导入到一个urls.py文件中,然后连接到一个 url 定义。注意基于类的视图是如何使用as_view()方法在url定义上声明的。此外,请注意 url 定义如何声明 url 额外选项{'onsale':True},该选项作为上下文数据传递给基于类的视图(即在get_context_data方法的**kwargs中)。

Tip

所有基于类的视图都使用 as_view()方法集成到 url 定义中。

现在您已经对 Django 基于类的视图有了基本的了解,清单 2-36 展示了另一个基于类的视图,它有不同的实现细节。

# views.py
from django.views.generic import View
from django.http import HttpResponse
from django.shortcuts import render

class ContactPage(View):
    mytemplate = 'contact.html'
    unsupported = 'Unsupported operation'

    def get(self, request):
        return render(request, self.mytemplate)

    def post(self, request):
        return HttpResponse(self.unsupported)

#urls.py
from coffeehouse.contact.views import ContactPage

urlpatterns = [
    url(r'^contact/$',ContactPage.as_view()),
]

Listing 2-36.Class-based view inherited from View with multiple HTTP handling

清单 2-36 中的第一个区别是基于类的视图继承了通用类django.views.generic.View的行为。如表 2-10 所示,View类为所有基于类的视图提供核心功能。所以事实上,清单 2-35 中使用的TemplateView类是View的子类,这意味着使用TemplateView的基于类的视图可以访问使用View的基于类的视图的相同功能。

您选择一个类而不是另一个类来实现基于类的视图的原因根植于 OOP 多态性原则。例如,在 OOP 中你可以有一个饮料→咖啡→拿铁的类层次结构,其中饮料类提供了可用于饮料、咖啡和拿铁实例的通用功能;Coffee 类提供了更多适用于 Coffee 和后续实例的特定功能;Latte 类提供了仅适用于 Latte 实例的最具体的功能。

因此,如果您事先知道您需要一个基于类的视图来放弃对模板的控制,而不应用复杂的业务逻辑或定制的请求和响应处理,那么与更通用的View类相比,TemplateView类提供了最快的解决方案。扩展同样的原则,一旦你开始使用 Django 模型和视图,你会发现表 2-10 中一些更专业的基于类的视图也比创建一个从通用View类继承的基于类的视图提供更快的解决方案。现在你知道了为什么你会选择一个基于View类的视图而不是一个更专业的类,让我们来分解清单 2-36 中的功能。

注意基于类的视图ContactPage声明了两个属性:mytemplateunsupported。这些是通用的类属性,我使用了mytemplate名称来说明与清单 2-35 和TemplateView基于类的视图中使用的template_name属性没有关系。从TemplateView派生的基于类的视图需要一个template_name值,并自动使用这个模板来生成响应。然而,从View类派生的基于类的视图并不期望特定的模板,而是期望您实现如何生成响应,这就是清单 2-36 中的getpost方法发挥作用的地方。

get方法用于处理视图上的 HTTP GET 请求,而post方法用于视图上的 HTTP POST 请求。这提供了一种更加模块化的方法来处理不同的 HTTP 操作,而标准视图方法需要显式地检查请求并创建条件来处理不同的 HTTP 操作。目前,不要担心 HTTP GET 和 HTTP POST 视图处理;Django 表格中对此进行了更详细的探讨,其中主题更具相关性。

接下来,注意到getpost方法都声明了一个request输入,它表示一个 Django HttpRequest实例,就像标准视图方法一样。在这两种情况下,方法都会立即返回响应,但是在生成响应之前,可以检查请求值或执行任何业务逻辑,就像在标准视图方法中一样。

get方法使用django.shortcuts.render方法生成响应,而post方法使用HttpResponse类生成响应,这两种方法都是在标准视图方法中用于生成响应的相同技术。清单 2-36 中唯一微小的区别是render方法和HttpResponse类都使用实例属性(例如self.mytemplateself.unsupported)来生成响应,但除此之外,您可以自由地返回一个 Django HttpResponse,其中包含本章已经解释过的任何变体(例如,清单 2-22 响应替代,表 2-5 快捷响应)。

最后,清单 2-36 中的最后一部分展示了如何将ContactPage基于类的视图导入到urls.py文件中,然后使用as_view()方法连接到一个 url。

为了结束对基于类的视图和本章的讨论,我们来看一下django.views.generic.RedirectView类。类似于TemplateView基于类的视图允许您快速生成响应而不需要视图方法,而RedirectView基于类的视图允许您快速生成 HTTP 重定向——就像表 2-4 中描述的那样——不需要视图方法。

RedirectView类支持以下列表中描述的四个属性:

  • permanent。-默认为False执行表 2-4 中描述的HttpResponseRedirect类支持的非永久重定向。如果设置为True,则使用表 2-4 中描述的HttpResponsePermanentRedirect类进行永久重定向。
  • url。-默认为None。定义执行重定向的 url 值。
  • pattern_name。-默认为None。定义一个 url 名称,通过reverse方法生成一个重定向 url。注意reverse方法在本章前面的 url 命名和名称空间部分已经解释过了。
  • query_string。-默认为False将查询字符串附加到重定向 url。如果提供,重定向 url 的query_string值。

至此,我们结束了对 Django 视图和 URL 的探索。在接下来的两章中,您将了解 Django 模板和 Jinja 模板。

Footnotes 1

http://www.apress.com/la/book/9781590594414

2

https://docs.python.org/3/howto/regex.html#non-capturing-and-named-groups

3

https://docs.djangoproject.com/en/1.11/_modules/django/http/request/#HttpRequest

4

https://docs.djangoproject.com/en/1.11/_modules/django/http/request/#QueryDict

5

https://docs.djangoproject.com/en/1.11/ref/request-response/#django.http.HttpResponse

三、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 %}和

{% 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 % }

posted @ 2024-08-13 14:27  绝不原创的飞龙  阅读(104)  评论(0)    收藏  举报