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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值