Django-入门指南-全-

Django 入门指南(全)

原文:zh.annas-archive.org/md5/2CE5925D7287B88DF1D43517EEF98569

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

多年来,Web 开发已经通过框架得到了发展。Web 开发变得更加高效,质量也得到了提高。Django 是一个非常复杂和流行的框架。框架是一组旨在简化和标准化开发的工具。它允许开发人员从非常实用的工具中受益,以最小化开发时间。然而,使用框架进行开发需要了解框架及其正确的使用方法。本书使用逐步教学法帮助初学者开发人员学习如何轻松应对 Django 框架。本书中的示例解释了一个简单 Web 工具的开发:基于文本的任务管理器。

本书涵盖内容

第一章,“Django 在 Web 上的位置”,简要介绍了 Web 的历史和发展。它解释了框架和 MVC 模式是什么。最后介绍了 Django。

第二章,“创建 Django 项目”,涉及安装使用 Django 所需的软件。在本章结束时,您将拥有一个准备好编码的开发环境。

第三章,“使用 Django 的 Hello World!”,描述了在提醒正则表达式后的 Django 路由。最后以一个简单的控制器示例结束,该控制器在用户的浏览器上显示“Hello world!”。

第四章,“使用模板”,解释了 Django 模板的工作原理。它涵盖了模板语言的基础知识以及架构模板和 URL 创建的最佳实践。

第五章,“使用模型”,描述了在 Django 中构建模型。它还解释了如何生成数据库以及如何使用 South 工具进行维护。本章还向您展示了如何通过管理模块设置管理界面。

第六章,“使用 Querysets 获取模型数据”,解释了如何通过模型对数据库执行查询。使用示例来测试不同类型的查询。

第七章,“使用 Django 表单”,讨论了 Django 表单。它解释了如何使用 Django 创建表单以及如何处理它们。

第八章,“使用 CBV 提高生产力”,专注于 Django 的一个独特方面:基于类的视图。本章解释了如何在几秒钟内创建 CRUD 界面。

第九章,“使用会话”,解释了如何使用 Django 会话。不同的实际示例展示了会话变量的使用以及如何充分利用它们。

第十章,“认证模块”,解释了如何使用 Django 认证模块。它涵盖了注册、登录以及对某些页面的访问限制。

第十一章,“使用 Django 进行 AJAX”,描述了 jQuery 库的基础知识。然后,它展示了使用 Django 进行 AJAX 的实际示例,并解释了这些页面的特点。

第十二章,“使用 Django 进行生产”,解释了如何使用 Django Web 服务器(如 Nginx)和 PostgreSQL Web 系统数据库部署网站。

附录,“速查表”,是对 Django 开发人员有用的常见方法或属性的快速参考。

本书所需内容

Django 开发所需的软件如下:

  • Python 3

  • PIP 1.5

  • Django 1.6

本书适合对象

本书适用于希望学习如何使用高质量框架创建网站的 Python 开发人员。本书也适用于使用其他语言(如 PHP)的 Web 开发人员,他们希望提高网站的质量和可维护性。本书适用于具有 Python 基础和 Web 基础知识的任何人,他们希望在当今最先进的框架之一上工作。

惯例

在本书中,您会发现一些文本样式,用于区分不同类型的信息。以下是一些这些样式的例子,以及它们的含义解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名会显示如下:“我们可以通过使用settings.py指令来包含其他上下文。”

代码块设置如下:

from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'Work_msanager.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
)

任何命令行输入或输出都以以下方式书写:

root@debian: wget https://raw.github.com/pypa/pip/master/contrib
/get-pip.py
root@debian:python3 get-pip.py

新术语重要单词以粗体显示。例如,屏幕上看到的单词,菜单或对话框中的单词会以这种方式出现在文本中:“点击高级系统设置。”

注意

警告或重要提示会以这样的框出现。

提示

提示和技巧会以这种方式出现。

第一章:Django 在网络上的位置

近年来,Web 开发发生了重大变化,特别是出现了 Web 框架。我们将学习如何使用 Django 框架创建一个完整的网站。

在本章中,我们将讨论以下内容:

  • 网络的变化

  • Django 的介绍

  • MVC 开发模式

从 Web 1.0 到 Web 2.0

今天你看到的网络并不总是像今天这样。事实上,许多技术,如 CSS,AJAX 或新的 HTML 5 版本都改进了网络。

网络 1.0

25 年前,由于不断增长的新技术,网络诞生了。其中两个非常决定性:

  • HTML 语言是一种显示语言。它允许您使用嵌套标签组织信息。

  • HTTP 协议是一种通信网络协议,允许客户端和服务器进行通信。客户端通常是 Firefox 或 Google Chrome 等浏览器,服务器往往是 Nginx,Apache 或 Microsoft IIS 等 Web 服务器。

起初,开发人员使用<table>标签来组织页面的各种元素,如菜单,标题或内容。网页上显示的图像分辨率较低,以避免使页面变得沉重。用户唯一能够执行的操作是点击超文本链接以导航到其他页面。

这些超文本链接使用户能够通过发送一种类型的数据(页面的 URL)从一个页面导航到另一个页面。统一资源定位符URL)定义了获取资源(如 HTML 页面,图片或 PDF 文件)的唯一链接。用户发送的数据除了 URL 之外没有其他数据。

Web 2.0

Web 2.0 这个术语是由 Dale Dougherty,O'Reilly Media 公司创造的,并在 2004 年 10 月由 Tim O'Reilly 在第一次 Web 2.0 会议上传播。

这个新的网络变得互动和可达到初学者。它成为了许多技术的礼物,包括以下内容:

  • 服务器端语言,如 PHP,Java 服务器页面JSP)或 ASP。这些语言允许您与数据库通信以提供动态内容。这也允许用户通过 HTML 表单发送数据以便使用 Web 服务器处理数据。

  • 数据库存储了大量信息。这些信息可以用来验证用户或显示从较旧到最近的条目列表。

  • 客户端脚本,如 JavaScript,使用户能够在不刷新页面的情况下执行简单的任务。异步 JavaScript 和 XMLAJAX)为当前网络带来了一个重要的功能:客户端和服务器之间的异步交换。由于这一点,无需刷新页面即可享受网站。

如今,Web 2.0 无处不在,它已成为我们日常生活的一部分。Facebook 是 Web 2.0 网站的一个完美例子,用户之间完全互动,并在其数据库中存储了大量信息。Web 应用程序已经被普及,如网络邮件或 Google 网络应用程序。

正是在这种哲学中,Django 出现了。

什么是 Django?

Django 诞生于 2003 年堪萨斯州劳伦斯的一家新闻机构。它是一个使用 Python 创建网站的 Web 框架。它的目标是编写非常快速的动态网站。2005 年,该机构决定以 BSD 许可证发布 Django 源代码。2008 年,Django 软件基金会成立以支持和推进 Django。几个月后发布了框架的 1.00 版本。

![什么是 Django?](img/00002.jpeg)

注意

Django 的口号

完美主义者与截止日期的网络框架。

Django 的口号很明确。这个框架是为了加速网站开发阶段而创建的,但并不是唯一的。事实上,这个框架使用了 MVC 模式,这使我们能够拥有一个一致的架构,正如我们将在下一章中看到的那样。

直到 2013 年,Django 只兼容 Python 2.x 版本,但 2013 年 2 月 26 日发布的 Django 1.5 版本标志着 Python 3 兼容性的开始。

如今,像 Instagram 移动网站、Mozilla.org 和 Openstack.org 这样的大型组织正在使用 Django。

Django - 一个 Web 框架

框架是一组软件,它组织了应用程序的架构,并使开发人员的工作更加轻松。框架可以适应不同的用途。它还提供了实用工具,使程序员的工作更快。因此,一些在网站上经常使用的功能可以被自动化,比如数据库管理和用户管理。

一旦程序员掌握了一个框架,它会极大地提高他们的生产力和代码质量。

MVC 框架

在 MVC 框架存在之前,Web 编程混合了数据库访问代码和页面的主要代码。这将 HTML 页面返回给用户。即使我们将 CSS 和 JavaScript 文件存储在外部文件中,服务器端语言代码仍然存储在至少三种语言之间共享的一个文件中:Python、SQL 和 HTML。

MVC 模式是为了将逻辑与表示分离,并拥有更加具体和真实的内部架构而创建的。模型-视图-控制器MVC)代表了该范式推荐的三个应用程序层:

  • 模型:这些代表数据库中的数据组织。简单地说,我们可以说每个模型定义了数据库中的一个表以及其他模型之间的关系。多亏了它们,每一点数据都存储在数据库中。

  • 视图:这些包含将发送给客户端的所有信息。它们生成最终的 HTML 文档。我们可以将 HTML 代码与视图关联起来。

  • 控制器:这些包含服务器执行的所有操作,对客户端不可见。控制器检查用户是否经过身份验证,或者可以从模板生成 HTML 代码。

MVC 框架

在具有 MVC 模式的应用程序中遵循以下步骤:

  1. 客户端向服务器发送请求,要求显示一个页面。

  2. 控制器通过模型使用数据库。它可以创建、读取、更新或删除任何记录,或对检索到的数据应用任何逻辑。

  3. 模型从数据库发送数据;例如,如果我们有一个在线商店,它会发送产品列表。

  4. 控制器将数据注入视图以生成它。

  5. 视图根据控制器提供的数据返回其内容。

  6. 控制器将 HTML 内容返回给客户端。

MVC 模式使我们能够为每个项目的工作者获得一致性。在一个有网页设计师和开发人员的网络代理公司中,网页设计师是视图的负责人。鉴于视图只包含 HTML 代码,网页设计师不会被开发人员的代码打扰。开发人员编辑他们的模型和控制器。

特别是 Django 使用了 MVT 模式。在这种模式中,视图被模板替换,控制器被视图替换。在本书的其余部分,我们将使用 MVT 模式。因此,我们的 HTML 代码将是模板,我们的 Python 代码将是视图和模型。

为什么使用 Django?

以下是使用 Django 的优势的非尽事例清单:

  • Django 是根据 BSD 许可发布的,这确保了 Web 应用程序可以自由使用和修改,而不会出现任何问题;它也是免费的。

  • Django 是完全可定制的。开发人员可以通过创建模块或覆盖框架方法来轻松地适应它。

  • 这种模块化增加了其他优势。有很多 Django 模块可以集成到 Django 中。你可以得到一些帮助,因为你经常会找到你可能需要的高质量模块。

  • 在这个框架中使用 Python 可以让你享受所有 Python 库的好处,并确保非常好的可读性。

  • Django 是一个旨在完美的框架。它专门为那些希望为他们的应用程序编写清晰代码和良好架构的人而设计。它完全遵循“不要重复自己”(DRY)的理念,这意味着在多个地方保持代码简洁,而不必复制/粘贴相同的部分。

  • 关于质量,Django 集成了许多有效的方法来执行单元测试。

  • Django 得到了一个良好的社区支持。这是一个非常重要的资产,因为它可以让您快速解决问题和修复错误。多亏了社区,我们还可以找到展示最佳实践的代码示例。

Django 也有一些缺点。当开发人员开始使用一个框架时,他/她会开始一个学习阶段。这个阶段的持续时间取决于框架和开发人员。如果开发人员了解 Python 和面向对象编程,那么 Django 的学习阶段相对较短。

还可能出现一个新版本的框架发布,修改了一些语法。例如,Django 1.5 版本改变了模板中 URL 的语法。(更多细节,请访问docs.djangoproject.com/en/1.5/ref/templates/builtins/#url。)尽管如此,文档提供了每个 Django 更新的详细信息。

总结

在本章中,我们研究了使 Web 进化为 Web 2.0 的变化。我们还研究了将逻辑与表示分离的 MVC 的运作方式。最后,我们介绍了 Django 框架。

在下一章中,我们将使用 Python、PIP 和 Django 建立我们的开发环境。

第二章:创建 Django 项目

在本章结束时,您将拥有开始使用 Django 进行编程所需的所有必要元素。使用 Django 开发的网站是包含一个或多个应用程序的项目。实际上,当一个网站变得更加重要时,将其在逻辑上分成几个模块变得必要。然后,这些模块被放置在对应于网站的项目中。在本书中,我们不需要创建许多应用程序,但在某些情况下它们可能非常有用。实际上,如果有一天您创建了一个应用程序,并且希望在另一个项目中使用它,您将需要将该应用程序复制并调整以适应新项目。

要能够使用 Django,您需要安装以下软件:

  • Python 3,享受第三版的创新。

  • setuptools 是一个简化外部 Python 模块安装的模块。但是,它无法管理卸载模块。

  • PIP 通过删除软件包、使用更简单的语法和提供其他好处来扩展 setuptools 的可能性。

  • Django,我们将通过 PIP 安装。

这些安装将与 Windows、Linux 和 Mac OS X 兼容。

安装 Python 3

要使用到目前为止我们谈到的所有工具,我们首先需要安装 Python 3。以下部分描述了如何在不同的操作系统上安装 Python。

为 Windows 安装 Python 3

要下载 Python 可执行文件,请访问www.python.org/download/并下载Python MSI文件。请确保您选择与您的平台相关的正确版本。Python 安装可能需要管理员帐户。

对于 Python 安装的所有阶段,您可以将所有设置保留为默认值。如果安装正确完成,您应该看到以下对话框窗口打开:

为 Windows 安装 Python 3

为 Linux 安装 Python 3

要在 Linux 上设置 Python 3,我们可以使用以下命令的包管理器 APT:

root@debian:apt-get install python3

提示

下载示例代码

您可以从您在www.packtpub.com的帐户中为您购买的所有 Packt 图书下载示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便直接通过电子邮件接收文件。

我们需要确认 APT 提出的修改。

为 Mac OS 安装 Python 3

最新版本的 Mac OS 已经安装了 Python 的一个版本。但是,安装了 Python 的第 2 版,我们想要安装第 3 版。为此,请访问www.python.org/download/并下载正确的版本。然后,打开扩展名为.dmp的文件。最后,运行扩展名为.mpkg的文件。如果出现诸如Python 无法打开,因为它来自未知开发者的错误,请执行以下步骤:

  1. Finder中,找到 Python 安装位置。

  2. 按下ctrl键,然后单击应用程序图标。

  3. 从快捷菜单中选择打开

  4. 单击打开

安装 setuptools

PIP 是 setuptools 的一个依赖项。我们需要安装 setuptools 才能使用 PIP。以下部分描述了如何在不同的操作系统上安装 setuptools。

为 Windows 安装 setuptools

要下载 setuptools 可执行文件,您必须转到 PyPI 网站pypi.python.org/pypi/setuptools。然后,我们需要单击下载并选择正确的版本。在本书中,我们使用 1.1 版本,如下面的屏幕截图所示:

为 Windows 安装 setuptools

为 Linux 安装 setuptools

使用 APT 时,我们不需要安装 setuptools。实际上,APT 将在安装 PIP 之前自动安装它。

为 Mac OS 安装 setuptools

当我们使用get-pip.py文件安装 PIP 时,setuptools 将直接安装。因此,我们暂时不需要安装它。

安装 PIP

PIP 在 Python 用户中非常受欢迎,并且使用 PIP 是 Django 社区的最佳实践。它处理包安装,执行更新,并删除所有 Python 包扩展。由于这个,我们可以安装所有 Python 所需的包。

如果您安装了 Python 3.4 或更高版本,PIP 已包含在 Python 中。

在 Windows 上安装 PIP

要安装 PIP,首先从pypi.python.org/pypi/pip/1.5.4下载它。

然后,我们需要从可执行文件安装 PIP,但不要忘记定义正确的 Python 安装文件夹,如下面的屏幕截图所示:

为 Windows 安装 PIP

对于下一组步骤,请使用默认选项并完成安装。有了 PIP,我们将安装所有所需的 Python 包。

在 Linux 上安装 PIP

要在 Linux 上安装 PIP 和包括 setuptools 在内的所有组件,您必须使用以下命令使用get-pip.py文件:

root@debian: wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py
root@debian:python3 get-pip.py

在 Mac OS 上安装 PIP

要在 Mac OS 上安装 PIP,我们必须以以下方式使用get-pip.py文件:

curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py sudo python3 get-pip.py

安装 Django

然后,我们将安装我们将要使用的框架。以下部分描述了如何在不同的操作系统上安装 Django。

在 Windows 上安装 Django

要使用 PIP 安装 Django,您必须打开命令提示符并转到Python文件夹中可以找到的Scripts目录。您可以使用以下命令安装 Django:

C:\Python33\Scripts\pip.exe install django=="X.X"

PIP 将在 Python 的site-packages存储库中下载并安装 Django 包。

在 Linux 上安装 Django

为了方便我们刚刚安装的 PIP 的使用,我们必须查找系统上安装的版本并定义一个别名来引用已安装的 PIP 版本。不要忘记以 root 身份执行以下命令:

root@debian:compgen -c | grep pip
root@debian:alias pip=pip-3.2
root@debian:pip install django=="1.6"

第一个命令查找包含单词pip的可用命令。您肯定会找到一行,比如pip-3.2。我们将在这个命令上使用第二个命令定义一个别名。

第三个命令安装 Django 的 1.6 版本。

在 Mac OS 上安装 Django

如果您想更轻松地使用 PIP,可以使用以下命令创建符号链接:

cd /usr/local/binln -s ../../../Library/Frameworks/Python.framework/Version/3.3/bin/pip3 pip

然后,我们可以使用以下命令安装 Django:

pip install django=="1.6"

使用 Django 启动您的项目

在开始使用 Django 之前,您需要为您的应用程序创建一个环境。我们将创建一个 Django 项目。然后,这个项目将包含我们的应用程序。

要创建我们的应用程序的项目,我们需要使用django-admin.py文件运行以下命令(您可以在Python33\Scripts文件夹中找到它):

django-admin.py startproject Work_manager

为了方便使用 Django 命令,我们可以设置 Windows 的环境变量。为此,您必须执行以下步骤:

  1. 在桌面上右键单击我的电脑

  2. 点击高级系统设置

  3. 接下来,点击环境变量

  4. 添加或更新PATH变量:

  • 如果不存在,创建PATH变量并将其值设置为C:\Python33/Scripts

  • 如果存在,将;C:\Python33\Scripts追加到现有值

  1. 现在,您可以使用之前的命令,而无需进入Python33/Scripts文件夹。

注意

有不同的方法来执行前面的命令:

  • 以下命令将在所有情况下执行:
C:\Python33\python.exe C:\Python33\Scripts\django-admin.py startproject Work_manager

  • 如果我们在PATH变量中定义了C:\Python33\Scripts,则将执行以下命令:
C:\Python33\python.exe django-admin.py startproject Work_manager

  • 如果我们在PATH变量中定义了C:\Python33\Scripts并且.py扩展文件被定义为与 Python 一起运行,则将执行以下命令:
django-admin.py startproject Work_manager

这个命令在您运行命令的文件夹中创建一个Work_manager文件夹。我们将在该文件夹中找到一个文件夹和一个文件:

  • manage.py文件将用于在项目上执行操作,比如启动开发服务器或将数据库与模型同步。

  • Work_manager文件夹代表我们项目的一个应用程序。默认情况下,startproject命令会创建一个新的应用程序。

Work_manager文件夹包含两个非常重要的文件:

  • settings.py文件包含我们项目的参数。这个文件对我们所有的应用程序都是通用的。我们用它来定义调试模式,配置数据库,或者定义我们将使用的 Django 包。settings.py文件允许我们做更多的事情,但我们的使用将局限于之前描述的内容。

  • urls.py文件包含我们所有的 URL。通过这个文件,我们在 Django 中进行路由。我们将在下一章中介绍这个。

创建一个应用程序

我们不会在Work_manager文件夹中编写我们的应用程序,因为我们想要创建我们自己的Task_manager应用程序。

为此,请使用startproject命令创建的manage.py文件运行以下命令。您必须在包含manage.py文件的Work_manager文件夹中运行以下命令:

Manage.py startapp TasksManager

这个命令在我们项目的文件夹中创建了一个TasksManager文件夹。这个文件夹包含五个文件:

  • __init__.py文件定义了一个包。Python 需要它来区分标准文件夹和包。

  • admin.py文件目前没有用。它包含需要并入管理模块的模型。

  • models.py文件包含我们应用程序的所有模型。我们在应用程序的开发中经常使用它。模型允许我们创建数据库并存储信息。我们将在第五章中讨论这一点,使用模型

  • tests.py文件包含我们应用程序的单元测试。

  • views.py文件可以包含视图。这个文件将包含在将 HTML 页面发送给客户端之前执行的所有操作。

既然我们知道了 Django 最重要的文件,我们可以配置我们的项目了。

配置应用程序

要配置我们的项目或应用程序,我们需要编辑项目文件夹中的settings.py文件。

这个文件包含变量。这些变量是 Django 在初始化 Web 应用程序时读取的设置。以下是其中的一些变量:

  • DEBUG:在开发过程中,此参数必须设置为True,因为它可以显示错误。当将项目投入生产时,不要忘记将其设置为False,因为错误会提供有关站点安全性的非常敏感的信息。

  • TIME_ZONE:此参数设置了必须计算日期和时间的区域。默认值是UTC

  • DEFAULT_CHARSET:这设置了所使用的字符编码。在task_manager应用程序中,我们使用 UTF-8 编码来简化国际化。为此,您必须添加以下行:

DEFAULT_CHARSET = 'utf-8'
  • LANGUAGE_CODE:这设置了网站上要使用的语言。这是国际化的主要有用参数。

  • MIDDLEWARE_CLASSES:这定义了所使用的不同中间件。

中间件是在请求过程中执行的类和方法,包括在参数中执行的方法。为了简化开发的开始,我们将从该参数中删除一个中间件。这需要您在行前添加#来注释掉该行:

# 'django.middleware.csrf.CsrfViewMiddleware',

我们将在后面的章节中讨论这个中间件,以解释它的操作和重要性。

既然我们已经了解了 Django 的一般设置,我们可以开始开发我们的应用程序了。

总结

在本章中,我们已经安装了使用 Django 所需的所有软件。我们学会了如何创建 Django 项目和应用程序。我们还学会了如何配置应用程序。

在下一章中,我们将以一个包含文本Hello World!的网页示例开始 Django 开发。

第三章:使用 Django 的 Hello World!

在本章中,我们实际上不会开始开发阶段。相反,我们将学习网站的基础知识,以了解 Django,即项目和应用程序的创建。在本章中,我们还将:

  • 学习如何使用正则表达式

  • 创建你的第一个 URL

  • 创建你的第一个视图

  • 测试你的应用程序

在本章结束时,我们将创建我们的第一个网页,显示Hello World!

Django 中的路由

在上一章中,我们编辑了settings.py文件来配置我们的 Django 项目。我们将再次编辑settings.py以添加一个新参数。以下行必须存在于settings.py中:

ROOT_URLCONF = 'Work_manager.urls'

此参数将定义包含我们网站所有 URL 的 Python 文件。我们已经谈到了之前的文件,因为它在Work_manager文件夹中。用于定义ROOT_URLCONF变量的语法意味着 Django 将Workmanager包中的urls.py文件中的 URLs 带到项目的根目录。

我们的应用程序的路由将基于此文件。路由定义了基于发送的 URL 将如何处理客户端请求。

实际上,当控制器接收到客户端请求时,它将进入urls.py文件,并检查 URL 是否是客户端的请求,并使用相应的视图。

例如,在以下 URL 中,Django 将在urls.py中查找search字符串,以了解要采取什么操作:http://localhost/search

这是urls.py文件的样子,它是 Django 创建项目时创建的:

from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'Work_msanager.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),
    url(r'^admin/', include(admin.site.urls)),
)

我们将详细介绍此文件的组件:

  • 第一行导入了在 URL 管理中常用的函数。

  • 下面两行对管理模块很有用。我们将通过在行首添加#来进行注释。这些行将在后面的章节中解释。

  • 其余的行定义了urlpatterns变量中的 URL。我们还将审查以url (r '^ admin开头的 URL。

在从 Web 客户端接收到请求后,控制器会线性地遍历 URL 列表,并检查 URL 是否符合正则表达式。如果不符合,控制器将继续检查列表的其余部分。如果符合,控制器将通过在 URL 中发送参数来调用相应视图的方法。如果您想编写 URL,您必须首先了解正则表达式的基础知识。

正则表达式

正则表达式就像一个小语言本身。尽管它们复杂且难以理解,但它们可以以极大的灵活性操纵字符串。它们由一系列字符组成,用于定义模式。

我们不会在本书中探讨所有正则表达式的概念,因为这将需要几章,并使我们偏离本书的主要目标。在编写您的第一个 URL 之前,练习您的正则表达式;许多网站可以帮助您在正则表达式上进行训练。搜索“在线正则表达式匹配器”,您将找到通过 JavaScript 检查您的正则表达式的页面。您还可以通过 Félix López 撰写的书籍Mastering Regular Expressions PythonPackt Publishing进一步探索正则表达式。有一个实用工具可以可视化正则表达式。这个工具叫做Regexper,由 Jeff Avallone 创建。我们将使用它来表示正则表达式的图表。

以下部分探讨了使用的模式、函数和示例,以帮助您更好地理解正则表达式。

未解释的字符

未解释的字符,如字母和数字,在正则表达式中意味着它们存在于字符串中,并且必须按照完全相同的顺序放置。

例如,正则表达式test01将验证test01dktest01test0145g字符串,但不会验证test10tste01

正则表达式test-reg将验证a test-regex,但不会验证test-aregextestregex:

未解释的字符

test01正则表达式的可视化表示

行的开头和结尾

要检查字符串是否必须出现在行的开头或结尾,你必须使用^$字符。如果^出现在字符串的开头,验证将在字符串的开头进行。对于$在结尾的情况也是一样。

以下是一些示例:

  • ^test正则表达式将验证testtest01l,但不会验证dktestttest01行的开头和结尾

  • 正则表达式test$将验证test01test,但不会验证test01行的开头和结尾

  • 正则表达式^test$只验证test行的开头和结尾

任意字符的正则表达式

在正则表达式中,句点(.)表示“任意字符”。因此,当你验证无法推断的字符时,会使用句点。如果你尝试在你的语音中验证句点,使用转义字符\

以下是示例:

  • ^te.t验证testtept任意字符的正则表达式

  • ^test\.me$只验证test.me任意字符的正则表达式

字符类

要验证字符,你可以使用字符类。字符类用方括号括起来,包含所有允许的字符。要验证位置中的所有数字和字母,你必须使用[0123456789a]。例如,^tes[t0e]$只验证三个字符串:testtes0tese

你也可以使用以下预定义类:

  • [0-9]等同于[0123456789]

  • [a-z]匹配所有字母,[abcdefghijklmnopqrstuvwxyz]

  • [A-Z]匹配所有大写字母

  • [a-zA-Z]匹配所有字母

以下是快捷方式:

  • \d等同于[0-9]

  • \w等同于[a-zA-Z0-9_]

  • [0-9]等同于[0123456789]

验证字符的数量

到目前为止,我们学习的一切都是定义一个且仅一个字符的元素。要验证一个字符出现一次或多次,必须使用大括号{x, y},其中x定义了最小出现次数,y是最大出现次数。如果其中一个未指定,将会有一个未定义的值。例如,如果你忘记在{2,}中包含一个元素,这意味着该字符必须至少出现两次。

以下是一些示例:

  • ^test{2, 3}$只验证testttesttt验证字符的数量

  • ^tests{0,1}$只验证testtests验证字符的数量

  • . ^ {1} $验证除一个之外的所有通道:空字符串

以下是快捷方式:

  • *等同于{0}

  • ?等同于{0, 1}

  • +等同于{1}

正则表达式非常强大,即使在 Django 编程之外也会非常有用。

创建我们的第一个 URL

Django 的一个有趣特性之一是包含了一个开发服务器。事实上,在网站开发阶段,开发人员不需要设置 Web 服务器。然而,当你将网站投入生产时,你将需要安装一个真正的 Web 服务器,因为它不适用于生产环境。

事实上,Django 服务器并不安全,几乎无法承受大量负载。这并不意味着你的网站会变得缓慢且充满缺陷;它只是意味着你必须在生产中使用一个真正的 Web 服务器。

要使用开发服务器,我们需要使用manage.py runserver 命令文件。我们必须启动命令提示符并进入项目根目录(使用cd命令浏览文件夹)来执行命令:

manage.py runserver 127.0.0.1:8000

这个命令启动了 Django 开发服务器。让我们逐步解释控制:

  • runserver参数启动开发服务器。

  • 127.0.0.1是我们网络适配器的内部 IP 地址。这意味着我们的服务器只会监听和响应在其上启动的计算机。如果我们在一个局域网中,并且希望使我们的网站在除我们之外的计算机上可用,我们将输入我们的本地 IP 地址而不是127.0.0.1。值127.0.0.1是参数的默认值。

  • 8000定义了服务器的监听端口。这个设置对于在一台计算机上运行多个 web 服务器非常有用。

如果命令执行正确,窗口应该显示0 errors found的消息,如下面的截图所示:

创建我们的第一个 URL

要查看结果,我们必须打开浏览器并输入以下 URL:http://localhost:8000

Django 通过显示以下消息确认我们的开发环境是正常的:

创建我们的第一个 URL

这个消息也意味着我们没有指定的 URL。我们将向我们的文件添加两个 URL:

url (r'^$', 'TasksManager.views.index.page), 
url (r'^index$', 'TasksManager.views.index.page') 

提示

你应该始终了解 Django 中的错误,特别是在 Django 的 GitHub 页面上:github.com/django

在我们输入的 URL 中,我们定义了第一个参数(正则表达式),它将验证 URL。我们将在下一章讨论第二个参数。

让我们回到浏览器,用F5键刷新页面。Django 将显示一个ViewDoesNotExist at /的错误。

这意味着我们的模块不存在。你必须研究你的错误;在这个例子中,我们有一个错误。有了这个错误,我们将直接修复不起作用的部分。

我们经常遇到的另一个问题是404 页面未找到错误。我们可以通过在浏览器中输入http://localhost:8000/test404来生成它。这个错误意味着没有 URL 验证test404字符串。

我们必须注意错误,因为看到并解决它们可以节省我们很多时间。

创建我们的第一个视图

现在我们已经创建了我们的 URL 并由路由系统解释,我们必须确保一个视图(在 MVC 模式中是一个控制器)满足客户的需求。

这是urls.py中存在的第二个参数的功能。这个参数将定义扮演视图角色的方法。例如,我们的第一个 URL:

url (r'^$', 'TasksManager.views.index.page'), 

首先,正如我们在学习正则表达式时所看到的,这个 URL 只有在浏览http://localhost:8000 URL 时才有效。URL 中的第二个参数意味着在index.py文件中有一个名为page的方法将处理请求。index.py文件位于TasksManager应用程序根目录下的views包中。

当我们希望 Python 识别一个文件夹为包时,我们需要创建一个包含__init__.py文件的文件夹,我们可以将其留空。

你可以选择另一种结构来存储你的视图。你必须选择最适合你的项目的结构。从第一行代码开始,对你的项目有一个长期的愿景,以定义高质量的架构。

在我们的index.py文件中,我们将创建一个名为page()的方法。这个方法将向客户端返回一个 HTML 页面。页面是通过 HTTP 协议返回的,所以我们将使用HttpResponse()函数及其导入。这个HttpResponse()函数的参数返回我们将返回给浏览器的 HTML 内容。为了简化阅读这个例子,我们没有使用正确的 HTML 结构,因为我们只是向客户端返回Hello world!,如下面的代码所示:

# - * - Coding: utf -8 - * -
from django.http import HttpResponse
# View for index page.
def page (request) :
 return HttpResponse ("Hello world!" )

在前面的例子中,我们在page()方法之前添加了一个注释。注释非常重要。它们帮助你快速理解你的代码。

我们还设置了 UTF-8 字符的编码。这将提高我们的应用与其他语言的兼容性。我们不一定在书中后面指出它,但建议使用它。

测试我们的应用程序

要测试我们的第一个页面,我们将不得不使用runserver命令,这是我们在本章中早些时候看到的。为了做到这一点,您必须运行命令并在浏览器中刷新您的页面,http://localhost:8000

如果您在浏览器中看到Hello World!而没有错误出现,这意味着您已经按照之前的步骤进行了操作。如果您忘记了某些东西,请不要犹豫在互联网上找到您的错误;其他人可能也经历过同样的情况。

然而,我们必须改善我们的观点,因为目前我们并不尊重 MVC 模型。我们将创建一个模板来分离 Python 代码的 HTML,并且具有更多的灵活性。

总结

在本章中,我们学习了正则表达式的基础知识。这是一个强大的工具,用于操作字符串。我们学会了如何操作系统路由 URL。我们还创建了我们的第一个视图,将一个字符串返回给客户端。在下一章中,我们将学习如何使用 Django 创建可维护的模板。

第四章:使用模板

正如我们在第一章中所看到的,我们解释了 MVC 和 MVT 模型,模板是允许我们生成返回给客户端的 HTML 代码的文件。在我们的视图中,HTML 代码不与 Python 代码混合。

Django 自带其自己的模板系统。然而,由于 Django 是模块化的,可以使用不同的模板系统。这个系统由一个语言组成,将用于制作我们的动态模板。

在本章中,我们将学习如何做以下事情:

  • 将数据发送到模板

  • 在模板中显示数据

  • 在模板中显示对象列表

  • 在 Django 中使用过滤器处理链

  • 有效使用 URL

  • 创建基础模板以扩展其他模板

  • 在我们的模板中插入静态文件

在模板中显示 Hello world!

我们将创建我们应用程序的第一个模板。为此,我们必须首先编辑settings.py文件,以定义将包含我们模板的文件夹。我们将首先将项目文件夹定义为PROJECT_ROOT,以简化迁移到另一个系统:

PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
TEMPLATE_DIRS = (os.path.join(PROJECT_ROOT, '../TasksManager/templates')
  # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
  # Always use forward slashes, even on Windows.
  # Don't forget to use absolute paths, not relative paths.
)

现在 Django 知道在哪里查找模板,我们将创建应用程序的第一个模板。为此,请使用文件浏览器,并在TasksManager/templates/en/public/文件夹中添加index.html文件。我们不需要创建__init__.py文件,因为这些文件不包含任何 Python 文件。

以下是index.html文件的内容:

<html>
  <head>
    <title>
      Hello World Title
    </title>
  </head>
  <body>
    <h1>
      Hello World Django
    </h1>
    <article>
      Hello world !
    </article>
  </body>
</html>

尽管模板是正确的,但我们需要更改视图以指示其使用。我们将使用以下内容修改index.py文件:

from django.shortcuts import render
# View for index page. 
def page(request):
  return render(request, 'en/public/index.html')

如果我们测试这个页面,我们会注意到模板已经被视图考虑进去了。

从视图向模板注入数据

在改进我们的模板之前,我们必须将变量发送到模板。数据的注入是基于这些变量,因为模板将执行某些操作。事实上,正如我们在 MVC 模式的解释中所看到的,控制器必须将变量发送到模板以便显示它们。

有几个函数可以将变量发送到模板。两个主要函数是render()render_to_response()render()函数与render_to_response()非常相似。主要区别在于,如果我们使用render,我们不需要指定context_instance = RequestContext(request)以发送当前上下文。这是稍后在本书中使用 CSRF 中间件的上下文。

我们将改变我们的视图,以在我们的模板中注入变量。这些变量将对使用模板语言非常有用。以下是我们修改后的视图:

from django.shortcuts import render
"""
View for index page. 
"""

def page(request):
  my_variable = "Hello World !"
  years_old = 15
  array_city_capitale = [ "Paris", "London", "Washington" ]
  return render(request, 'en/public/index.html', { "my_var":my_variable, "years":years_old, "array_city":array_city_capitale })

创建动态模板

Django 自带完整的模板语言。这意味着我们将使用模板标签,这将允许我们在模板中具有更多的灵活性,并显示变量,执行循环,并设置过滤器。

HTML 和模板语言在模板中混合在一起;然而,模板语言非常简单,与 HTML 代码相比只是少数。网页设计师可以轻松修改模板文件。

在模板中集成变量

在我们的控制器中,我们发送了一个名为my_var的变量。我们可以以以下方式在<span>标签中显示它。在我们的模板标签的<article>标签中添加以下行:

<span> {{my_var}} </ span> 

因此,因为我们的变量包含string = "Hello World!",将生成以下 HTML 代码:

<span> Hello World! </span>

我们将学习如何为变量或函数创建条件,以便在以下示例中过滤变量中的数据。

条件语句

语言模板还允许条件结构。请注意,对于显示变量,使用双大括号{{}},但一旦我们有一个作为条件或循环的操作,我们将使用{%%}

我们的控制器发送一个可以定义年龄的years变量。条件结构的一个示例是,当您可以更改控制器中变量的值以观察更改时。在我们的<article>标签中添加以下代码:

<span>
  {% if years <10 %}
    You are a children
  {% elif years < 18 %}
    You are a teenager
  {% else %}
    You are an adult!
  {% endif %}
</span>

在我们的情况下,当我们将值15发送到生成的模板时,使用的代码如下:

<span> You are a teenager </span>

在模板中循环

循环允许您阅读表或数据字典的元素。在我们的控制器中,我们发送了一个名为array_city的数据表,其中包含城市的名称。要以列表形式查看所有这些城市的名称,我们可以在模板中编写以下内容:

<ul>
  {% for city in array_city %}
    <li>
      {{ city }}
    </li>
  {% endfor %}
</ul>

此循环将遍历array_city表,并将每个元素放入我们在<li>标签中显示的city变量中。使用我们的示例数据,此代码将生成以下 HTML 代码:

<ul>
  <li>Paris</li>
  <li>London</li>
  <li>Washington</li>
</ul>

使用过滤器

过滤器是在将数据发送到模板之前修改数据的有效方法。我们将在以下部分中查看一些过滤器的示例,以更好地理解它们。

大写和小写过滤器

小写过滤器将转换为小写字母,而大写过滤器将转换为大写字母。在接下来的部分中给出的示例中包含my_hello变量,其值为Hello World!

小写过滤器

小写过滤器的代码如下:

<span> {{ my_hello | lower }} </span>

此代码生成以下 HTML 代码:

<span> hello </span>

大写过滤器

大写过滤器的代码如下:

<span> {{ my_hello | upper }} </span>

此代码生成以下 HTML 代码:

<span> HELLO </span>

capfirst 过滤器

capfirst 过滤器将第一个字母转换为大写。具有myvar = "hello"变量的示例如下:

<span>{{ my_hello | capfirst }}</span>

此代码生成以下 HTML 代码:

<span> Hello </span>

复数过滤器

复数过滤器可以轻松处理复数形式。通常,开发人员由于时间不足而选择简单的解决方案。解决方案是显示频道:您的购物车中有 2 个产品

Django 简化了这种类型的字符串。如果变量表示复数值,复数过滤器将在单词末尾添加后缀,如下所示:

You have {{ product }} nb_products {{ nb_products | pluralize }} in our cart.

如果nb_products12,则此频道将显示以下三个频道:

You have 1 product in our cart.
You have 2 products in our cart.
I received {{ nb_diaries }} {{ nb_diaries|pluralize : "y , ies "}}.

如果nb_diaries12,则上述代码将显示以下两个链:

I received one diary.
I received two diaries.

在上一个示例中,我们首次使用了带参数的过滤器。要为过滤器设置参数,必须使用以下语法:

{{ variable | filter:"parameters" }}

此过滤器有助于提高您网站的质量。当网站显示正确的句子时,它看起来更专业。

转义和安全以避免 XSS 过滤器

XSS 过滤器用于转义 HTML 字符。此过滤器有助于防止 XSS 攻击。这些攻击是基于黑客注入客户端脚本的。以下是 XSS 攻击的逐步描述:

  • 攻击者找到一个表单,以便内容将显示在另一个页面上,例如商业网站的评论字段。

  • 黑客编写 JavaScript 代码以使用此表单中的标记进行黑客攻击。提交表单后,JavaScript 代码将存储在数据库中。

  • 受害者查看页面评论,JavaScript 运行。

风险比简单的alert()方法更重要,以显示消息。使用这种类型的漏洞,黑客可以窃取会话 ID,将用户重定向到伪造的网站,编辑页面等。

更具体地说,过滤器更改以下字符:

  • < 被转换为 &lt;

  • > 被转换为 &gt;

  • ' 被转换为 '

  • " 被转换为 &quot;

  • & 被转换为 &amp;

我们可以使用{% autoescape %} tag自动转义块的内容,该标签带有 on 或 off 参数。默认情况下,autoescape 是启用的,但请注意,在较旧版本的 Django 中,autoescape 未启用。

当启用 autoescape 时,如果我们想将一个变量定义为可信任的变量,我们可以使用 safe 过滤器对其进行过滤。以下示例显示了不同的可能场景:

<div>
  {% autoescape on %}
  <div>
    <p>{{ variable1 }}</p>
    <p>
      <span>
        {{ variable2|safe }}
      </span>
      {% endautoescape %}
      {% autoescape off %}
    </p>
  </div>
    <span>{{ variable3 }}</span>
    <span>{{ variable4|escape }}</span>
  {% endautoescape %}
  <span>{{ variable5 }}</span>
</div>

在这个例子中:

  • variable1autoescape转义

  • variable2没有被转义,因为它被过滤为安全的

  • variable3没有被转义,因为autoescape被定义为关闭

  • variable4被转义,因为它已经使用转义过滤器进行了过滤

  • variable5被转义,因为autoescape是关闭的

linebreaks 过滤器

linebreaks 过滤器允许您将换行符转换为 HTML 标记。一个单独的换行符被转换为<br />标记。一个换行符后跟一个空格将变成一个段落分隔,</p>

<span>{{ text|linebreaks }}</span>

truncatechars 过滤器

truncatechars 过滤器允许您从一定长度截断字符串。如果超过这个数字,字符串将被截断,Django 会添加字符串“...”。

包含“欢迎来到 Django”的变量的示例如下:

{{ text|truncatechars:14 }}

这段代码输出如下:

"Welcome in ..."

创建 DRY URL

在学习什么是 DRY 链接之前,我们首先会提醒您 HTML 链接是什么。每天,当我们上网时,我们通过点击链接来改变页面或网站。这些链接被重定向到 URL。以下是一个指向google.com的示例链接:

<a href="http://www.google.com">Google link !</a>

我们将在我们的应用程序中创建第二个页面,以创建第一个有效的链接。在urls.py文件中添加以下行:

url(r'^connection$', 'TasksManager.views.connection.page'),

然后,创建一个对应于前面 URL 的视图:

from django.shortcuts import render
# View for connection page. 
def page(request):
  return render(request, 'en/public/connection.html')

我们将为新视图创建第二个模板。让我们复制第一个模板,并将副本命名为connection.html,并修改Connection中的Hello world。我们可以注意到这个模板不符合 DRY 哲学。这是正常的;我们将在下一节学习如何在不同模板之间共享代码。

我们将在我们的第一个index.html模板中创建一个 HTML 链接。这个链接将引导用户到我们的第二个视图。我们的<article>标签变成了:

<article>
  Hello world !
  <br />
  <a href="connection">Connection</a>
</article>

现在,让我们用开发服务器测试我们的网站,并打开浏览器到我们网站的 URL。通过测试网站,我们可以检查链接是否正常工作。这是一个好事,因为现在你能够用 Django 制作一个静态网站,而且这个框架包含一个方便的工具来管理 URL。

Django 永远不会在href属性中写入链接。事实上,通过正确地填写我们的urls.py文件,我们可以引用 URL 的名称和地址。

为了做到这一点,我们需要改变包含以下 URL 的urls.py文件:

url(r'^$', 'TasksManager.views.index.page', name="public_index"),
url(r'^connection/$', 'TasksManager.views.connection.page', name="public_connection"),

给我们的每个 URL 添加 name 属性可以让我们使用 URL 的名称来创建链接。修改您的index.html模板以创建 DRY 链接:

<a href="{% url 'public_connection' %}">Connection</a>

再次测试新网站;请注意,链接仍然有效。但是目前,这个功能对我们来说是没有用的。如果 Google 决定改进以网站名称结尾的 URL 的索引,您将不得不更改所有的 URL。要在 Django 中做到这一点,您只需要更改第二个 URL 如下:

url(r'^connection-TasksManager$', 'TasksManager.views.connection.page', name="public_connection"),

如果我们再次测试我们的网站,我们可以看到更改已经正确完成,并且urls.py文件中的更改对网站的所有页面都有效。当您需要使用参数化 URL 时,您必须使用以下语法将参数集成到 URL 中:

{% url "url_name" param %}
{% url "url_name" param1, param2 %}

扩展模板

模板的传承允许您定义一个超级模板和一个从超级模板继承的子模板。在超级模板中,可以定义子模板可以填充的块。这种方法允许我们通过在超级模板中应用通用代码到多个模板来遵循 DRY 哲学。我们将使用一个例子,index.html模板将扩展base.html模板。

以下是我们必须在template文件夹中创建的base.html模板代码:

<html>
  <head>
    <title>
      % block title_html %}{% endblock %}
    </title>
  </head>
  <body>
    <h1>
      Tasks Manager - {% block h1 %}{% endblock %}
    </h1>
    <article>
      {% block article_content %}{% endblock %}
    </article>
  </body>
</html>

在前面的代码中,我们定义了子模板可以覆盖的三个区域:title_htmlh1article_content。以下是index.html模板代码:

{% extends "base.html" %}
{% block title_html %}
  Hello World Title
{% endblock %}
{% block h1 %}
  {{ bloc.super }}Hello World Django
{% endblock %}
{% block article_content %}
  Hello world !
{% endblock %}

在这个模板中,我们首先使用了 extends 标签,它扩展了base.html模板。然后,block 和 endblock 标签允许我们重新定义base.html模板中的内容。我们可以以相同的方式更改我们的connection.html模板,这样base.html的更改就可以在两个模板上进行。

可以定义尽可能多的块。我们还可以创建超级模板,以创建更复杂的架构。

在模板中使用静态文件

诸如 JavaScript 文件、CSS 或图像之类的静态文件对于获得人体工程学网站至关重要。这些文件通常存储在一个文件夹中,但在开发或生产中修改此文件夹可能会很有用。

根据 URL,Django 允许我们定义一个包含静态文件的文件夹,并在需要时轻松修改其位置。

要设置 Django 查找静态文件的路径,我们必须通过添加或更改以下行来更改我们的settings.py文件:

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(PROJECT_ROOT, '../TasksManager/static/'),
)

我们将为我们未来的静态文件定义一个合适的架构。选择早期一致的架构非常重要,因为它使应用程序支持以及包括其他开发人员变得更容易。我们的静态文件架构如下:

static/
  images/
  javascript/
    lib/
  css/
  pdf/

我们为每种静态文件创建一个文件夹,并为 JavaScript 库定义一个lib文件夹,如 jQuery,我们将在本书中使用。例如,我们更改了我们的base.html文件。我们将添加一个 CSS 文件来管理我们页面的样式。为了做到这一点,我们必须在</title></head>之间添加以下行:

<link href="{% static "css/style.css" %}" rel="stylesheet" type="text/css" />

在我们的静态模板中使用标签,我们还必须通过在使用静态标签之前放置以下行来加载系统:

{% load staticfiles %}

我们将在/static/css文件夹中创建style.css文件。这样,浏览器在开发过程中不会生成错误。

摘要

在本章中,我们学习了如何创建模板并将数据发送到模板,以及如何在模板中使用条件、循环和过滤器。我们还讨论了如何为灵活的 URL 结构创建 DRY URLs,扩展模板以满足 DRY 哲学,以及如何使用静态文件。

在下一章中,我们将学习如何结构化我们的数据以保存在数据库中。

第五章:使用模型

我们刚刚创建的网站只包含静态数据;但是,我们想要存储数据以自动化所有任务。这就是为什么有模型;它们将在我们的视图和数据库之间建立联系。

像许多框架一样,Django 提出了使用抽象层进行数据库访问。这个抽象层称为对象关系映射ORM)。这允许您使用 Python 实现对象来访问数据,而不必担心使用数据库。使用这个 ORM,我们不需要为简单和稍微复杂的操作使用 SQL 查询。这个 ORM 属于 Django,但还有其他的,比如SQLAlchemy,它是一个质量很高的 ORM,特别是在 Python TurboGears 框架中使用。

模型是从Model类继承的对象。Model类是一个专门设计用于数据持久性的 Django 类。

我们在模型中定义字段。这些属性允许我们在模型内组织数据。要在数据库和 SQL 之间建立连接,我们可以说一个模型在数据库中由一个表表示,而模型属性在表中由一个字段表示。

在本章中,我们将解释:

  • 如何设置对数据库的访问

  • 如何安装 South 进行数据库迁移

  • 如何创建简单的模型

  • 如何在模型之间创建关系

  • 如何扩展我们的模型

  • 如何使用管理模块

数据库和 Django

Django 可以与许多数据库进行接口。但是,在我们的应用程序开发过程中,我们使用了 Django 中包含的 SQLite 库。

我们将修改settings.py以设置与数据库的连接:

DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.sqlite3', 
    'NAME': os.path.join(PROJECT_ROOT, 'database.db'), 
    'USER': '',                     
    'PASSWORD': '',                 
    'HOST': '',                     
    'PORT': '',                     
  }
}

以下是前面代码中提到的属性的描述:

  • ENGINE属性指定要使用的数据库类型。

  • NAME属性定义了 SQLite 数据库的路径和最终名称。我们在我们的代码中使用os.path.join的语法,并且它与所有操作系统兼容。数据库文件将包含在项目目录中。

  • 其他属性在使用数据库服务器时很有用,但由于我们将使用 SQLite,因此不需要定义它们。

使用 South 进行迁移

South是 Django 的一个非常有用的扩展。它在更改字段时简化了数据库的迁移。它还保留了数据库结构更改的历史记录。

我们现在谈论它是因为必须在创建数据库之前安装它才能正常工作。

Django 1.7 集成了迁移系统。您将不再需要使用 South 来进行 Django 应用的迁移。您可以在docs.djangoproject.com/en/dev/topics/migrations/找到有关集成到 Django 1.7 中的迁移系统的更多信息。

安装 South

要安装 South,我们使用pip命令。我们已经用它来安装 Django。要做到这一点,请运行以下命令:

pip install South

在实际使用 South 之前,我们必须更改settings.py文件,以便 South 能够在 Django 中良好集成。为此,您必须转到INSTALLED_APPS并添加以下行(根据版本的不同,安装 South 可能已经添加了这行):

'south',
'TasksManager',

使用 South 扩展

在我们进行第一次迁移和生成数据库之前,我们还必须创建模式迁移。为此,我们必须运行以下命令:

manage.py schemamigration TasksManager --initial 

然后,我们必须执行初始迁移:

manage.py syncdb --migrate 

Django 要求我们首先创建一个帐户。这个帐户将是超级用户。记住您输入的登录名和密码;您以后会需要这些信息。

South 现在已经完全可用。每次我们需要修改模型时,我们都会进行迁移。但是,为了正确进行迁移,您必须牢记以下事项:

  • 永远不要执行 Django 的syncdb命令。第一次运行syncdb --migrate后,永远不要再次运行它。之后使用migrate

  • 始终在新字段中放置默认值;否则,我们将被要求分配一个值。

  • 每次我们完成编辑我们的模型时,我们必须按正确的顺序执行以下两个命令:

manage.py schemamigration TasksManager –auto
manage.py migrate TasksManager

创建简单模型

要创建模型,我们必须已经深入研究了应用程序。模型是任何应用程序的基础,因为它们将存储所有数据。因此,我们必须仔细准备它们。

关于我们的Tasksmanager应用程序,我们需要一个用户来保存在项目上执行的任务。我们将创建两个模型:User_djangoProject

我们需要将我们的模型存储在models.py文件中。我们将编辑TasksManager文件夹中的models.py文件。我们不需要修改配置文件,因为当您需要模型时,我们将不得不导入它。

文件已经存在并且有一行。以下一行允许您导入 Django 的基本模型:

from django.db import models

用户资料模型

要创建UserProfile模型,我们要问自己一个问题,即“我们需要保存关于用户的哪些数据?”。我们需要以下数据:

  • 用户的真实姓名

  • 一个将标识每个用户的昵称

  • 一个对用户身份验证有用的密码

  • 电话号码

  • 出生日期(这不是必要的,但我们必须研究日期!)

  • 用户上次连接的日期和时间

  • 电子邮件地址

  • 年龄(以年为单位)

  • 用户帐户的创建日期

  • 专业化,如果是主管

  • 用户类型

  • 如果您是开发人员,那么您就是主管

所需的模型如下:

class UserProfile(models.Model):
  name = models.CharField(max_length=50, verbose_name="Name")
  login = models.CharField(max_length=25, verbose_name="Login")
  password = models.CharField(max_length=100, verbose_name="Password")
  phone = models.CharField(max_length=20, verbose_name="Phone number" , null=True, default=None, blank=True)
  born_date = models.DateField(verbose_name="Born date" , null=True, default=None, blank=True)
  last_connection = models.DateTimeField(verbose_name="Date of last connection" , null=True, default=None, blank=True)
  email = models.EmailField(verbose_name="Email")
  years_seniority = models.IntegerField(verbose_name="Seniority", default=0)
  date_created = models.DateField(verbose_name="Date of Birthday", auto_now_add=True)

我们还没有定义专业化、用户类型和主管,因为这些点将在下一部分中看到。

在前面的代码中,我们可以看到Django_user继承自Model类。这个Model类有我们需要操作模型的所有方法。我们也可以重写这些方法来定制模型的使用。

在这个类中,我们通过添加一个属性来添加我们的字段,并指定值。例如,名字字段是一个字符字符串类型,最大长度为 50 个字符。verbose_name属性将是我们在表单中定义字段的标签。以下是常用的字段类型列表:

  • CharField:这是一个具有有限字符数的字符字符串

  • TextField:这是一个具有无限字符的字符字符串

  • IntegerField:这是一个整数字段

  • DateField:这是一个日期字段

  • DateTimeField:这个字段包括日期以及小时、分钟和秒的时间

  • DecimalField:这是一个可以精确定义的小数

提示

Django 自动保存一个自动递增的id字段。因此,我们不需要定义主键。

项目模型

为了保存我们的项目,我们需要以下数据:

  • 标题

  • 描述

  • 客户名称

这些因素使我们能够定义以下模型:

class Project(models.Model):
  title = models.CharField(max_length=50, verbose_name="Title")
  description = models.CharField(max_length=1000, verbose_name="Description")
  client_name = models.CharField(max_length=1000, verbose_name="Client name")

为了遵守良好的实践,我们本来不需要为客户定义一个文本字段,而是定义一个与客户表的关系。为了简化我们的第一个模型,我们为客户名称定义一个文本字段。

模型之间的关系

关系是连接我们的模型的元素。例如,在这个应用程序的情况下,一个任务与一个项目相关联。实际上,开发人员为特定项目执行任务,除非它是一个更一般的任务,但这超出了我们项目的范围。我们定义一对多类型的关系,以表示一个任务总是涉及一个单一项目,但一个项目可以与许多任务相关联。

还有两种其他类型的关系:

  • 一对一关系将模型分为两部分。生成的数据库将创建两个通过关系链接的表。我们将在身份验证模块的章节中看到一个例子。

  • 多对多关系定义与同一类型的任何模型连接的关系。例如,一个作者可以出版多本书,一本书可能有几个作者。

创建具有关系的任务模型

对于任务模型,我们需要以下元素:

  • 用几个词定义任务的一种方式

  • 有关任务的更多详细描述

  • 过去的生活

  • 它的重要性

  • 它所附属的项目

  • 创建它的开发人员

这使我们能够编写以下模型:

class Task(models.Model):
  title = models.CharField(max_length=50, verbose_name="Title")
  description = models.CharField(max_length=1000, verbose_name="Description")
  time_elapsed = models.IntegerField(verbose_name="Elapsed time" , null=True, default=None, blank=True)
  importance = models.IntegerField(verbose_name="Importance")
  project = models.ForeignKey(Project, verbose_name="Project" , null=True, default=None, blank=True)
  app_user = models.ForeignKey(UserProfile, verbose_name="User")

在这个模型中,我们定义了两种外键字段类型:projectapp_user。在数据库中,这些字段包含它们所附属的记录的登录详细信息在另一个表中。

定义与Project模型的关系的project字段有两个额外的属性:

  • Null:这决定了元素是否可以定义为空。project字段中存在此属性的事实意味着任务不一定与项目相关联。

  • Default:这设置字段将具有的默认值。也就是说,如果我们在保存模型之前没有指定项目的值,任务将不会与域相关联。

扩展模型

继承模型允许为两个不同的模型使用共同的字段。例如,在我们的App_user模型中,我们无法确定随机记录是开发人员还是监督员。

一个解决方案是创建两个不同的模型,但我们将不得不复制所有共同的字段,如名称、用户名和密码,如下所示:

class Supervisor(models.Model):
  # Duplicated common fields
  specialisation = models.CharField(max_length=50, verbose_name="Specialisation")

class Developer(models.Model):
  # Duplicated common fields
  supervisor = models.ForeignKey(Supervisor, verbose_name="Supervisor")

复制代码是一件遗憾的事,但这是 Django 和 DRY 必须遵循的原则。这就是为什么有一个继承模型的原因。

实际上,遗留模型用于定义一个主模型(或超级模型),其中包含多个模型的共同字段。子模型会自动继承超级模型的字段。

没有比一个例子更明确的了;我们将修改我们的DeveloperSupervisor类,使它们继承App_user

class Supervisor(UserProfile):
  specialisation = models.CharField(max_length=50, verbose_name="Specialisation")

class Developer(UserProfile):
  supervisor = models.ForeignKey(Supervisor, verbose_name="Supervisor")

遗留数据库的结果允许我们创建三个表:

  • App_user模型的表,包含模型属性的字段

  • Supervisor模型的表,包含一个专业的文本字段和一个与App_user表有外键关系的字段

  • 一个Developer表,有两个字段:一个与Supervisor表关联的字段,一个与App_user表关联的字段

现在我们已经分开了两种类型的用户,我们将修改与App_user的关系,因为只有开发人员会记录他们的任务。在Tasks模型中,我们有以下行:

app_user = models.ForeignKey(App_user, verbose_name="User")

这段代码转换如下:

developer = models.ForeignKey(Developer, verbose_name="User")

为了使数据库命令生成工作,我们必须按正确的顺序放置模型。实际上,如果我们定义与尚未定义的模型的关系,Python 将引发异常。目前,模型需要按照描述的顺序定义。稍后,我们将看到如何解决这个限制。

在下一章中,我们将对模型执行查询。这需要数据库与模型同步。在开始下一章之前,我们必须先迁移 South。

要执行迁移,我们必须使用本章开头看到的命令。为了简化迁移,我们还可以在 Python 文件夹中创建一个批处理文件,其中我们将放入以下行:

manage.py schemamigration TasksManager --auto
manage.py migrate
pause

以下是一个 bash 脚本,您可以在Work_manager文件夹中创建,可以在 Debian Linux 上执行相同的操作:

#!/bin/bash
manage.py runserver 127.0.0.1:8000

这样,当您迁移 South 时,它将执行此文件。pause命令允许您在不关闭窗口的情况下查看结果或显示的错误。

管理员模块

管理模块非常方便,并且在 Django 中默认包含。这是一个可以轻松维护数据库内容的模块。这不是一个数据库管理器,因为它无法维护数据库的结构。

您可能会问的一个问题是,“除了管理工具数据库之外还有什么?”答案是管理模块完全集成了 Django 并使用这些模型。

以下是它的优点:

  • 它管理模型之间的关系。这意味着如果我们想保存一个新的开发人员,该模块将提出所有主管的列表。这样,它就不会创建一个不存在的关系。

  • 它管理 Django 权限。您可以根据模型和 CRUD 操作为用户设置权限。

  • 它很快就建立起来了。

基于 Django 模型而不是数据库,这个模块允许用户编辑记录的数据。

安装模块

要实现管理模块,请编辑settings.py文件。在INSTALLED_APPS设置中,您需要添加或取消注释以下行:

'django.contrib.admin'

您还必须通过添加或取消注释以下行来编辑urls.py文件:

from django.contrib import admin
admin.autodiscover()
url (r'^admin', include(admin.site.urls)),

导入管理模块的行必须在文件的开头与其他导入一起。运行autodiscover()方法的行必须在导入之后并在urlpatterns定义之前找到。最后,最后一行是一个应该在urlpatterns中的 URL。

我们还必须在TasksManager文件夹中创建一个admin.py文件,在其中我们将定义要集成到管理模块中的样式:

from django.contrib import admin
from TasksManager.models import UserProfile, Project, Task , Supervisor , Developer
admin.site.register(UserProfile)
admin.site.register(Project)
admin.site.register(Task)
admin.site.register(Supervisor)
admin.site.register(Developer)

现在我们已经配置了管理模块,我们可以轻松地管理我们的数据。

使用模块

要使用管理模块,我们必须连接到刚刚定义的 URL:http://localhost:8000/admin/

我们必须在创建数据库时连接定义的登录:

  1. 一旦我们连接,模型列表就会出现。

  2. 如果我们点击Supervisor模型链接,我们会到达一个页面,我们可以通过窗口右上角的按钮添加一个主管:使用模块

  3. 通过点击这个按钮,我们加载一个由表单组成的页面。这个表单自动提供了管理日期和时间的实用工具:使用模块

让我们添加一个新的主管,然后添加一个开发人员。当您想选择主管时,您可以在下拉框中看到我们刚刚创建的主管。右侧的绿色十字架允许您快速创建一个主管。

在接下来的章节中,我们将为我们的模型定义str方法。这将改进管理模块中对象的显示列表。

模型的高级用法

我们学习了允许我们创建简单应用程序的模型的基础知识。有时,需要定义更复杂的结构。

为同一模型使用两个关系

有时,将两个(或更多)外键存储在单个模型中是有用的。例如,如果我们希望两个开发人员并行工作在同一任务上,我们必须在我们的模型中使用related_name属性。例如,我们的Task模型包含以下行的两个关系:

developer1 = models.ForeignKey (Developer , verbose_name = "User" , related_name = "dev1" )
developer2 = models.ForeignKey (Developer , verbose_name = "User" , related_name = "dev2" )

在本书的后续部分,我们将不使用这两个关系。为了有效地遵循本书,我们必须返回到我们之前定义的Task模型。

注意

在这里,我们定义了同一任务上的两个开发人员。最佳实践建议我们在Task模型中创建一个多对多的关系。详细参数允许您指定一个中间表来存储附加数据。这是一个可选步骤。这种关系的示例如下:

#Relationship to add to the Task model
developers = models.ManyToManyField(Developer , through="DeveloperWorkTask")
class DeveloperWorkTask(models.Model):
  developer = models.ForeignKey(Developer)
  task = models.ForeignKey(Task)
  time_elapsed_dev = models.IntegerField(verbose_name="Time elapsed", null=True, default=None, blank=True)

定义 str 方法

如在管理模块使用部分中已经提到的,__str__()方法将允许更好地查看我们的模型。这个方法将设置用于显示我们的模型实例的字符串。当 Django 与 Python 3 不兼容时,这个方法被__unicode__()方法替换。

例如,当我们添加了一个开发者时,定义主管的下拉列表显示了“主管对象”行。显示主管的姓名会更有帮助。为了做到这一点,改变我们的App_user类并添加str()方法:

class UserProfile ( models.Model ) :
# Fields...
def __str__ (self):
  return self.name

这个方法将返回主管的姓名以便显示,并允许您轻松管理管理:

定义 str 方法

总结

在本章中,我们学习了使用 South 进行迁移。我们还学习了如何创建简单的模型和模型之间的关系。此外,我们还学习了如何安装和使用管理模块。在下一章中,我们将学习如何操作我们的数据。我们将学习如何对数据进行四种主要操作:添加、读取(和研究)、修改和删除。

第六章:使用 Querysets 获取模型的数据

Querysets 用于数据检索,而不是直接构建 SQL 查询。它们是 Django 使用的 ORM 的一部分。ORM 用于通过抽象层将视图和控制器连接起来。开发人员可以使用对象模型类型,而无需编写 SQL 查询。我们将使用 querysets 来检索我们通过模型存储在数据库中的数据。这四个操作通常被 CRUD (创建读取更新删除) 所总结。

本章中讨论的示例旨在向您展示查询集的工作原理。下一章将向您展示如何使用表单,以及如何将来自客户端的数据保存在模型中。

在本章结束时,我们将知道如何:

  • 在数据库中保存数据

  • 从数据库中检索数据

  • 更新数据库中的数据

在数据库上持久化模型的数据

使用 Django 进行数据存储很简单。我们只需要在模型中填充数据,并使用方法将它们存储在数据库中。Django 处理所有的 SQL 查询;开发人员不需要编写任何查询。

填充模型并将其保存在数据库中

在将模型实例的数据保存到数据库之前,我们需要定义模型所需字段的所有值。我们可以在我们的视图索引中显示示例。

以下示例显示了如何保存模型:

from TasksManager.models import Project # line 1
from django.shortcuts import render
def page(request):
  new_project = Project(title="Tasks Manager with Django", description="Django project to getting start with Django easily.", client_name="Me") # line 2
  new_project.save() # line 3
  return render(request, 'en/public/index.html', {'action':'Save datas of model'})

我们将解释我们视图的新行:

  • 我们导入我们的 models.py 文件;这是我们将在视图中使用的模型

  • 然后,我们创建我们的 Project 模型的一个实例,并用数据填充它

  • 最后,我们执行 save() 方法,将当前数据保存在实例中

我们将通过启动开发服务器(或 runserver)来测试此代码,然后转到我们的 URL。在 render() 方法中,我们定义的 action 变量的值将被显示。要检查查询是否执行,我们可以使用管理模块。还有用于管理数据库的软件。

我们需要通过更改 line 2 中的值来添加更多记录。要了解如何做到这一点,我们需要阅读本章。

从数据库中获取数据

在使用 Django 从数据库中检索数据之前,我们使用 SQL 查询来检索包含结果的对象。使用 Django,根据我们是要获取一个记录还是多个记录,有两种检索记录的方式。

获取多条记录

要从模型中检索记录,我们必须首先将模型导入视图,就像我们之前保存数据到模型中一样。

我们可以按以下方式检索和显示 Project 模型中的所有记录:

from TasksManager.models import Project
from django.shortcuts import render
def page(request):
  all_projects = Project.objects.all()
  return render(request, 'en/public/index.html', {'action': "Display all project", 'all_projects': all_projects})

显示项目的代码模板如下:

{% extends "base.html" %}
{% block title_html %}
  Projects list
{% endblock %}
{% block h1 %}
  Projects list
{% endblock %}
{% block article_content %}
  <h3>{{ action }}</h3>
  {% if all_projects|length > 0 %}
  <table>
    <thead>
      <tr>
        <td>ID</td>
        <td>Title</td>
      </tr>
    </thead>
    <tbody>
    {% for project in all_projects %}
      <tr>
        <td>{{ project.id }}</td>
        <td>{{ project.title }}</td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
  {% else %}
  <span>No project.</span>
  {% endif %}
{% endblock %}

all() 方法可以链接到 SQL SELECT * FROM 查询。现在,我们将使用 filter() 方法来过滤我们的结果,并进行等效于 SELECT * FROM Project WHERE field = value 查询。

以下是筛选模型记录的代码:

from TasksManager.models import Project
from django.shortcuts import render
def page(request):
  action='Display project with client name = "Me"'
  projects_to_me = Project.objects.filter(client_name="Me")
  return render(request, 'en/public/index.html', locals())

我们使用了一种新的语法将变量发送到模板。locals() 函数将所有本地变量发送到模板,这简化了渲染行。

提示

最佳实践建议您逐个传递变量,并且只发送必要的变量。

filter() 方法中的每个参数都定义了查询的过滤器。实际上,如果我们想要进行两个过滤,我们将编写以下代码行:

projects_to_me = Project.objects.filter(client_name="Me", title="Project test")

这行代码等同于以下内容:

projects_to_me = Project.objects.filter(client_name="Me")
projects_to_me = projects_to_me.filter(title="Project test") 

第一行可以分成两行,因为 querysets 是可链接的。可链接方法是返回查询集的方法,因此可以使用其他查询集方法。

使用 all()filter() 方法获得的响应是查询集类型。查询集是可以迭代的模型实例集合。

仅获取一条记录

我们将在本章中看到的方法返回 Model 类型的对象,这些对象将用于记录关系或修改恢复的模型实例。

要使用查询集检索单个记录,我们应该像下面这行代码一样使用get()方法:

first_project = Project.objects.get(id="1")

get()方法在作为filter()方法使用时接受过滤参数。但是,设置检索单个记录的过滤器时要小心。

如果get()的参数是client_name = "Me",如果我们有超过两条记录与client_name对应,它将生成错误。

从查询集实例中获取模型实例

我们说过只有get()方法才能检索模型的实例。这是正确的,但有时从查询集中检索模型的实例也是有用的。

例如,如果我们想要获取客户Me的第一条记录,我们将写:

queryset_project = Project.objects.filter(client_name="Me").order_by("id")
# This line returns a queryset in which there are as many elements as there are projects for the Me customer

first_item_queryset = queryset_project[:1]
# This line sends us only the first element of this queryset, but this element is not an instance of a model

project = first_item_queryset.get()
# This line retrieves the instance of the model that corresponds to the first element of queryset

这些方法是可链接的,所以我们可以写下面的一行代码,而不是前面的三行代码:

project = Project.objects.filter(client_name="Me").order_by("id")[:1].get()

使用 get 参数

现在我们已经学会了如何检索记录,也知道如何使用 URL,我们将创建一个页面,用于显示项目的记录。为此,我们将看到一个新的 URL 语法:

url(r'^project-detail-(?P<pk>\d+)$', 'TasksManager.views.project_detail.page', name="project_detail"),

这个 URL 包含一个新的字符串,(?P<pk>\d+)。它允许具有十进制参数的 URL 是有效的,因为它以\d结尾。结尾处的+字符表示参数不是可选的。<pk>字符串表示参数的名称是pk

Django 的系统路由将直接将此参数发送到我们的视图。要使用它,只需将其添加到我们的page()函数的参数中。我们的视图变成了以下内容:

from TasksManager.models import Project
from django.shortcuts import render
def page(request, pk):
  project = Project.objects.get(id=pk)
  return render(request, 'en/public/project_detail.html', {'project' : project})

然后,我们将创建我们的en/public/project_detail.html模板,从base.html扩展,并在article_content块中添加以下代码:

<h3>{{ project.title }}</h3>
<h4>Client : {{ project.client_name }}</h4>
<p>
  {{ project.description }}
</p>

我们刚刚编写了我们的第一个包含参数的 URL。我们以后会用到这个,特别是在关于基于类的视图的章节中。

保存外键

我们已经从模型中记录了数据,但到目前为止,我们从未在关系数据库中记录过。以下是一个我们将在本章后面解释的关系记录的例子:

from TasksManager.models import Project, Task, Supervisor, Developer
from django.shortcuts import render
from django.utils import timezone
def page(request):
  # Saving a new supervisor
  new_supervisor = Supervisor(name="Guido van Rossum", login="python", password="password", last_connection=timezone.now(), email="python@python.com", specialisation="Python") # line 1
  new_supervisor.save()
  # Saving a new developer
  new_developer = Developer(name="Me", login="me", password="pass", last_connection=timezone.now(), email="me@python.com", supervisor=new_supervisor)
  new_developer.save()
  # Saving a new task
  project_to_link = Project.objects.get(id = 1) # line 2
  new_task = Task(title="Adding relation", description="Example of adding relation and save it", time_elapsed=2, importance=0, project=project_to_link, developer=new_developer) # line 3
  new_task.save()
  return render(request, 'en/public/index.html', {'action' : 'Save relationship'})

在这个例子中,我们加载了四个模型。这四个模型用于创建我们的第一个任务。实际上,一个职位与一个项目和开发人员相关联。开发人员附属于监督者。

根据这种架构,我们必须首先创建一个监督者来添加一个开发人员。以下列表解释了这一点:

  • 我们创建了一个新的监督者。请注意,扩展模型无需额外的步骤来记录。在Supervisor模型中,我们定义了App_user模型的字段,没有任何困难。在这里,我们使用timezone来记录当天的日期。

  • 我们寻找第一个记录的项目。这行代码的结果将在project_to_link变量中记录Model类实例的遗留。只有get()方法才能给出模型的实例。因此,我们不应该使用filter()方法。

  • 我们创建了一个新的任务,并将其分配给代码开头创建的项目和刚刚记录的开发人员。

这个例子非常全面,结合了我们从一开始学习的许多元素。我们必须理解它,才能继续在 Django 中编程。

更新数据库中的记录

Django 中有两种机制可以更新数据。实际上,有一种机制可以更新一条记录,另一种机制可以更新多条记录。

更新模型实例

更新现有数据非常简单。我们已经看到了如何做到这一点。以下是一个修改第一个任务的例子:

from TasksManager.models import Project, Task
from django.shortcuts import render
def page(request):
  new_project = Project(title = "Other project", description="Try to update models.", client_name="People")
  new_project.save()
  task = Task.objects.get(id = 1)
  task.description = "New description"
  task.project = new_project
  task.save()
  return render(request, 'en/public/index.html', {'action' : 'Update model'})

在这个例子中,我们创建了一个新项目并保存了它。我们搜索了我们的任务,找到了id = 1。我们修改了描述和项目,使其与任务相关联。最后,我们保存了这个任务。

更新多条记录

要一次编辑多条记录,必须使用带有查询集对象类型的update()方法。例如,我们的People客户被名为Nobody的公司购买,因此我们需要更改所有client_name属性等于People的项目:

from TasksManager.models import Project
from django.shortcuts import render
def page(request):
  task = Project.objects.filter(client_name = "people").update(client_name="Nobody")
  return render(request, 'en/public/index.html', {'action' : 'Update for many model'})

查询集的update()方法可以更改与该查询集相关的所有记录。这个方法不能用于模型的实例。

删除记录

要删除数据库中的记录,我们必须使用delete()方法。删除项目比更改项目更容易,因为该方法对查询集和模型实例都是相同的。一个例子如下:

from TasksManager.models import Task
from django.shortcuts import render
def page(request):
  one_task = Task.objects.get(id = 1)
  one_task.delete() # line 1
  all_tasks = Task.objects.all()
  all_tasks.delete() # line 2
  return render(request, 'en/public/index.html', {'action' : 'Delete tasks'})

在这个例子中,第 1 行删除了id = 1的污渍。然后,第 2 行删除了数据库中所有现有的任务。

要小心,因为即使我们使用了一个 Web 框架,我们仍然掌握着数据。在这个例子中不需要确认,也没有进行备份。默认情况下,具有ForeignKey的模型删除规则是CASCADE值。这个规则意味着如果我们删除一个模板实例,那么对这个模型有外键的记录也将被删除。

获取关联记录

我们现在知道如何在数据库中创建、读取、更新和删除当前记录,但我们还没有恢复相关的对象。在我们的TasksManager应用程序中,检索项目中的所有任务将是有趣的。例如,由于我们刚刚删除了数据库中所有现有的任务,我们需要创建其他任务。我们特别需要在本章的其余部分为项目数据库创建任务。

使用 Python 及其面向对象模型的全面实现,访问相关模型是直观的。例如,当login = 1时,我们将检索所有项目任务:

from TasksManager.models import Task, Project
from django.shortcuts import render
def page(request):
  project = Project.objects.get(id = 1)
  tasks = Task.objects.filter(project = project)
  return render(request, 'en/public/index.html', {'action' : 'Tasks for project', 'tasks':tasks})

现在我们将查找id = 1时的项目任务:

from TasksManager.models import Task, Project
from django.shortcuts import render
def page(request):
  task = Task.objects.get(id = 1)
  project = task.project
  return render(request, 'en/public/index.html', {'action' : 'Project for task', 'project':project})

现在我们将使用关系来访问项目任务。

查询集的高级用法

我们学习了允许您与数据交互的查询集的基础知识。在特定情况下,需要对数据执行更复杂的操作。

在查询集中使用 OR 运算符

在查询集过滤器中,我们使用逗号来分隔过滤器。这一点隐含地意味着逻辑运算符AND。当应用OR运算符时,我们被迫使用Q对象。

这个Q对象允许您在模型上设置复杂的查询。例如,要选择客户MeNobody的项目,我们必须在视图中添加以下行:

from TasksManager.models import Task, Project
from django.shortcuts import render
from django.db.models import Q
def page(request):
  projects_list = Project.objects.filter(Q(client_name="Me") | Q(client_name="Nobody"))
  return render(request, 'en/public/index.html', {'action' : 'Project with OR operator', 'projects_list':projects_list})

使用小于和大于的查找

使用 Django 查询集,我们不能使用<>运算符来检查一个参数是否大于或小于另一个参数。

您必须使用以下字段查找:

  • __gte:这相当于 SQL 的大于或等于运算符,>=

  • __gt:这相当于 SQL 的大于运算符,>

  • __lt:这相当于 SQL 的小于运算符,<

  • __lte:这相当于 SQL 的小于或等于运算符,<=

例如,我们将编写一个查询集,可以返回持续时间大于或等于四小时的所有任务:

tasks_list = Task.objects.filter(time_elapsed__gte=4)

执行排除查询

在网站的上下文中,排除查询可能很有用。例如,我们想要获取持续时间不超过四小时的项目列表:

from TasksManager.models import Task, Project
from django.shortcuts import renderdef page(request):
  tasks_list = Task.objects.filter(time_elapsed__gt=4)
  array_projects = tasks_list.values_list('project', flat=True).distinct()
  projects_list = Project.objects.all()
  projects_list_lt4 = projects_list.exclude(id__in=array_projects)
  return render(request, 'en/public/index.html', {'action' : 'NOT IN SQL equivalent', 'projects_list_lt4':projects_list_lt4})
In the first queryset, we first retrieve the list of all the tasks for which `time_elapsed` is greater than `4`In the second queryset, we got the list of all the related projects in these tasksIn the third queryset, we got all the projectsIn the fourth queryset, we excluded all the projects with tasks that last for more than `4` hours

进行原始 SQL 查询

有时,开发人员可能需要执行原始的 SQL 查询。为此,我们可以使用raw()方法,将 SQL 查询定义为参数。以下是一个检索第一个任务的示例:

first_task = Project.objects.raw("SELECT * FROM TasksManager_project")[0]

要访问第一个任务的名称,只需使用以下语法:

first_task.title

总结

在本章中,我们学习了如何通过 Django ORM 处理数据库。确实,借助 ORM,开发人员不需要编写 SQL 查询。在下一章中,我们将学习如何使用 Django 创建表单。

第七章:使用 Django 表单

我们都知道 HTML 表单。这是一个包含<input><select>标签的<form>标签。用户可以填写或编辑这些项目并将它们返回到服务器。这是存储客户端提供的数据的首选方式。像 Django 这样的框架利用了 HTML 表单来使其更好。

Django 表单继承自Form类对象。这是一个我们将设置属性的对象。这些属性将是表单中的字段,我们将定义它们的类型。

在本章中,我们将学习如何执行以下操作:

  • 创建 HTML 表单

  • 处理表单发送的数据

  • 创建 Django 表单

  • 验证和操作从 Django 表单发送的数据

  • 基于模型创建表单

  • 自定义错误消息并使用小部件

Django 表单的优点如下:

  • 对抗 CSRF 漏洞可以很容易地实现。我们将在此后讨论 CSRF 漏洞。

  • 数据验证是自动的。

  • 表单可以很容易地定制。

但比较标准 HTML 表单和 Django 表单的最佳方法是通过一个例子来练习:添加开发者的表单。

不使用 Django 表单添加开发者

在本节中,我们将向您展示如何在不使用 Django 表单的情况下添加开发者。这个例子将展示使用 Django 可以节省多少时间。

将以下 URL 添加到您的urls.py文件中:

url(r'^create-developer$', 'TasksManager.views.create_developer.page', name="create_developer"),

HTML 表单模板

我们将在视图之前创建一个模板。实际上,我们将用包含表单的模板填充视图。我们没有把所有字段都放在模型中,因为代码太长了。使用更短的代码学习更好。以下是我们的模板template/en/public/create_developer.html

{% extends "base.html" %}
{% block title_html %}
  Create Developer 
{% endblock %}
{% block h1 %}
  Create Developer
{% endblock %}
{% block article_content %}
  <form method="post" action="{% url "create_developer" %}" >
    <table>
      <tr>
        <td>Name</td>
        <td>
          <input type="text" name="name" />
        </td>
      </tr>
      <tr>
        <td>Login</td>
        <td>
          <input type="text" name="login" />
        </td>
      </tr>
      <tr>
        <td>Password</td>
        <td>
          <input type="text" name="password" />
        </td>
      </tr>
      <tr>
        <td>Supervisor</td>
        <td>
          <select name="supervisor">
            {% for supervisor in supervisors_list %}
              <option value="{{ supervisor.id }}">{{ supervisor.name }}</option>
            {% endfor %}
          </select>
        </td>
      </tr>
      <tr>
        <td></td>
        <td>
          <input type="submit" value="Valid" />
          </td>
      </tr>
    </table>
  </form>
{% endblock %}

请注意,模板令人印象深刻,但它是一个极简的表单。

视图使用 POST 数据接收

以下截图显示了我们将创建的网页:

使用 POST 数据接收的视图

将处理此表单的视图如下。将视图保存在文件views/create_developer.py中:

from django.shortcuts import render
from django.http import HttpResponse
from TasksManager.models import Supervisor, Developer
# View for create_developer
def page(request):
  error = False
  # If form has posted
  if request.POST: 
  # This line checks if the data was sent in POST. If so, this means that the form has been submitted and we should treat it.
    if 'name' in request.POST: 
    # This line checks whether a given data named name exists in the POST variables.
      name = request.POST.get('name', '')
      # This line is used to retrieve the value in the POST dictionary. Normally, we perform filters to recover the data to avoid false data, but it would have required many lines of code.
    else:
      error=True
    if 'login' in request.POST:
      login = request.POST.get('login', '')
    else:
      error=True
    if 'password' in request.POST:
      password = request.POST.get('password', '')
    else:
      error=True
    if 'supervisor' in request.POST:
      supervisor_id = request.POST.get('supervisor', '')
    else:
      error=True
    if not error:
      # We must get the supervisor
      supervisor = Supervisor.objects.get(id = supervisor_id)
      new_dev = Developer(name=name, login=login, password=password, supervisor=supervisor)
      new_dev.save()
      return HttpResponse("Developer added")
    else:
      return HttpResponse("An error as occured")
  else:
    supervisors_list = Supervisor.objects.all()
    return render(request, 'en/public/create_developer.html')

在这个视图中,我们甚至没有检查监督员是否存在。即使代码是功能性的,注意它需要很多行,而且我们没有验证传输数据的内容。

我们使用HttpResponse()方法,这样我们就不必创建额外的模板。当字段输入不正确时,我们也没有关于客户端错误的详细信息。

如果您想验证您的代码是否正常工作,请不要忘记在管理模块中检查数据。

要尝试这个表单,您可以在index.html文件的article_content块中添加以下行:

<a href="{% url "create_developer" %}">Create developer</a>

使用 Django 表单添加开发者

Django 表单使用从Form类继承的对象。这个对象将处理我们在前面的例子中手动完成的大部分工作。

在显示表单时,它将生成表单模板的内容。如果需要,我们可以更改对象发送到模板的字段类型。

在接收数据时,对象将检查每个表单元素的内容。如果有错误,对象将向客户端发送明确的错误。如果没有错误,我们可以确定表单数据是正确的。

CSRF 保护

跨站请求伪造CSRF)是一种针对加载包含恶意请求的页面的用户的攻击。恶意脚本利用受害者的身份验证执行不需要的操作,如更改数据或访问敏感数据。

在 CSRF 攻击期间执行以下步骤:

  1. 攻击者进行脚本注入。

  2. 执行 HTTP 查询以获取网页。

  3. 下载包含恶意脚本的网页。

  4. 恶意脚本执行。CSRF 保护

在这种攻击中,黑客还可以修改对网站用户可能至关重要的信息。因此,对于 Web 开发人员来说,了解如何保护他们的网站免受这种攻击是非常重要的,而 Django 将在此方面提供帮助。

要重新启用 CSRF 保护,我们必须编辑settings.py文件,并取消以下行的注释:

'django.middleware.csrf.CsrfViewMiddleware',

此保护确保已发送的数据确实是从特定属性页面发送的。您可以通过两个简单的步骤来检查:

  1. 在创建 HTML 或 Django 表单时,我们插入一个将存储在服务器上的 CSRF 令牌。当表单被发送时,CSRF 令牌也将被发送。

  2. 当服务器接收到来自客户端的请求时,它将检查 CSRF 令牌。如果有效,它将验证请求。

不要忘记在启用保护的站点的所有表单中添加 CSRF 令牌。HTML 表单也涉及其中,我们刚刚创建的表单不包括令牌。为了使先前的表单与 CSRF 保护一起工作,我们需要在标签和<form> </form>中添加以下行:

{% csrf_token %}

带有 Django 表单的视图

我们将首先编写包含表单的视图,因为模板将显示在视图中定义的表单。Django 表单可以存储在项目文件的根目录下的forms.py等其他文件中。我们直接将它们包含在视图中,因为表单只会在此页面上使用。根据项目,您必须选择最适合您的架构。我们将在views/create_developer.py文件中创建我们的视图,代码如下:

from django.shortcuts import render
from django.http import HttpResponse
from TasksManager.models import Supervisor, Developer
from django import forms
# This line imports the Django forms package
class Form_inscription(forms.Form):  
# This line creates the form with four fields. It is an object that inherits from forms.Form. It contains attributes that define the form fields.
  name = forms.CharField(label="Name", max_length=30)
  login      = forms.CharField(label="Login", max_length=30)
  password   = forms.CharField(label="Password", widget=forms.PasswordInput)
  supervisor = forms.ModelChoiceField(label="Supervisor", queryset=Supervisor.objects.all())
# View for create_developer
def page(request):
  if request.POST:
    form = Form_inscription(request.POST)
    # If the form has been posted, we create the variable that will contain our form filled with data sent by POST form.
    if form.is_valid():
    # This line checks that the data sent by the user is consistent with the field that has been defined in the form.
      name          = form.cleaned_data['name']
    # This line is used to retrieve the value sent by the client. The collected data is filtered by the clean() method that we will see later. This way to recover data provides secure data.
      login         = form.cleaned_data['login']
      password      = form.cleaned_data['password']
      supervisor    = form.cleaned_data['supervisor'] 
      # In this line, the supervisor variable is of the Supervisor type, that is to say that the returned data by the cleaned_data dictionary will directly be a model.
      new_developer = Developer(name=name, login=login, password=password, email="", supervisor=supervisor)
      new_developer.save()
      return HttpResponse("Developer added")
    else:
      return render(request, 'en/public/create_developer.html', {'form' : form})
      # To send forms to the template, just send it like any other variable. We send it in case the form is not valid in order to display user errors:
    else:
    form = Form_inscription()
    # In this case, the user does not yet display the form, it instantiates with no data inside.
    return render(request, 'en/public/create_developer.html', {'form' : form})

此截图显示了表单的显示以及错误消息的显示:

带有 Django 表单的视图

Django 表单的模板

我们为此视图设置模板。模板将会更短:

{% extends "base.html" %}
{% block title_html %}
  Create Developer
{% endblock %}
{% block h1 %}
  Create Developer
{% endblock %}
{% block article_content %}
  <form method="post" action="{% url "create_developer" %}" >
    {% csrf_token %} 
    <!-- This line inserts a CSRF token. -->
    <table>
      {{ form.as_table }}
    <!-- This line displays lines of the form.-->
    </table>
    <p><input type="submit" value="Create" /></p>
  </form>
{% endblock %}

由于完整的表单操作在视图中,模板只需执行as_table()方法来生成 HTML 表单。

先前的代码以表格形式显示数据。生成 HTML 表单结构的三种方法如下:

  • as_table:这会在<tr> <td>标签中显示字段

  • as_ul:这会在<li>标签中显示表单字段

  • as_p:这会在<p>标签中显示表单字段

因此,我们通过 Django 表单快速编写了一个带有错误处理和 CSRF 保护的安全表单。在附录中,速查表,您可以找到表单中不同可能的字段。

基于模型的表单

ModelForms 是基于模型的 Django 表单。这些表单的字段是从我们定义的模型自动生成的。实际上,开发人员经常需要创建与数据库中的字段对应的表单,以适应非 MVC 网站。

这些特殊的表单有一个save()方法,将在新记录中保存表单数据。

监督员创建表单

首先,我们将以添加监督员为例。为此,我们将创建一个新页面。为此,我们将创建以下 URL:

url(r'^create-supervisor$', 'TasksManager.views.create_supervisor.page', name="create_supervisor"),

我们的视图将包含以下代码:

from django.shortcuts import render
from TasksManager.models import Supervisor
from django import forms
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def page(request):
  if len(request.POST) > 0:
    form = Form_supervisor(request.POST)
    if form.is_valid():
      form.save(commit=True) 
      # If the form is valid, we store the data in a model record in the form.
      return HttpResponseRedirect(reverse('public_index'))
      # This line is used to redirect to the specified URL. We use the reverse() function to get the URL from its name defines urls.py.
    else:
      return render(request, 'en/public/create_supervisor.html', {'form': form})
  else:
    form = Form_supervisor()
    return render(request, 'en/public/create_supervisor.html', {'form': form})
class Form_supervisor(forms.ModelForm): 
# Here we create a class that inherits from ModelForm.
  class Meta:
  # We extend the Meta class of the ModelForm. It is this class that will allow us to define the properties of ModelForm.
    model = Supervisor
    # We define the model that should be based on the form.
    exclude = ('date_created', 'last_connexion', )
    # We exclude certain fields of this form. It would also have been possible to do the opposite. That is to say with the fields property, we have defined the desired fields in the form.

如在exclude = ('date_created', 'last_connexion', )行中所示,可以限制表单字段。excludefields属性都必须正确使用。实际上,这些属性接收要排除或包括的字段的元组作为参数。它们可以描述如下:

  • exclude:这在管理员可访问的表单的情况下使用。因为,如果您在模型中添加一个字段,它将包含在表单中。

  • fields:这在表单对用户可访问的情况下使用。实际上,如果我们在模型中添加一个字段,用户将看不到它。

例如,我们有一个网站,销售免版税图像,其中有一个基于 ModelForm 的注册表单。管理员在用户的扩展模型中添加了一个信用字段。如果开发人员在某些字段中使用了exclude属性,并且没有添加信用,用户将能够获取他/她想要的信用。

我们将恢复我们之前的模板,在那里我们将更改<form>标签的action属性中存在的 URL:

{% url "create_supervisor" %}

这个例子向我们展示了 ModelForms 可以通过拥有一个可以定制的表单(例如修改验证)来节省大量开发时间。

在下一章中,我们将看到如何通过基于类的视图更快。

Django 表单的高级用法

我们已经学习了允许您创建简单表单并进行少量定制的表单的基础知识。有时,定制数据验证和错误显示,或使用特殊图形等方面是有用的。

扩展验证表单

对表单字段执行特定验证是有用的。Django 使这变得容易,同时提醒您表单的优势。我们将以添加开发者表单的例子来说明密码的审核。

为此,我们将以以下方式更改我们视图中的表单(在create_developer.py文件中):

class Form_inscription(forms.Form):
  name       = forms.CharField(label="Name", max_length=30)
  login = forms.CharField(label = "Login")
  password = forms.CharField(label = "Password", widget = forms.PasswordInput)
  # We add another field for the password. This field will be used to avoid typos from the user. If both passwords do not match, the validation will display an error message
  password_bis = forms.CharField(label = "Password", widget = forms.PasswordInput) 
  supervisor = forms.ModelChoiceField(label="Supervisor", queryset=Supervisor.objects.all())
  def clean(self): 
  # This line allows us to extend the clean method that is responsible for validating data fields.
    cleaned_data = super (Form_inscription, self).clean()
    # This method is very useful because it performs the clean() method of the superclass. Without this line we would be rewriting the method instead of extending it.
    password = self.cleaned_data.get('password') 
    # We get the value of the field password in the variable.
    password_bis = self.cleaned_data.get('password_bis')
    if password and password_bis and password != password_bis:
      raise forms.ValidationError("Passwords are not identical.") 
      # This line makes us raise an exception. This way, when the view performs the is_valid() method, if the passwords are not identical, the form is not validated .
    return self.cleaned_data

通过这个例子,我们可以看到 Django 在表单和审核管理方面非常灵活。它还允许您定制错误的显示。

定制错误的显示

有时,显示特定于用户的错误消息可能很重要。例如,公司可能要求密码必须包含某些类型的字符;例如,密码必须至少包含一个数字和多个字母。在这种情况下,最好也在错误消息中指出这一点。实际上,用户更仔细地阅读错误消息而不是帮助消息。

要做到这一点,您必须在表单字段中使用error_messages属性,并将错误消息设置为文本字符串。

还可以根据错误类型定义不同的消息。我们将创建一个包含两种常见错误的字典,并为它们提供消息。我们可以定义这个字典如下:

error_name = {
  'required': 'You must type a name !',
  'invalid': 'Wrong format.'
}

我们将修改create_developer.pyForm_inscription表单的名称字段:

name = forms.CharField(label="Name", max_length=30, error_messages=error_name)

这样,如果用户没有填写name字段,他/她将看到以下消息:您必须输入一个名称!

要将此消息应用于 ModelForm,我们必须转到models.py文件并修改包含name字段的行。

name = models.CharField(max_length=50, verbose_name="Name", error_messages=error_name)

在编辑models.py时,我们不应忘记指定error_name字典。

这些错误消息通过通知用户他/她的错误来提高网站的质量。当验证复杂时,使用自定义字段上的自定义错误非常重要。然而,不要在基本字段上过度使用,因为这对开发人员来说会浪费时间。

使用小部件

小部件是定制表单元素显示的有效方式。实际上,在某些情况下,指定具有特定尺寸的文本区域字段在 ModelForm 中可能是有帮助的。

为了学习使用小部件的实践并继续开发我们的应用程序,我们将创建项目创建页面。这个页面将包含一个 Django 表单,并且我们将在 HTML 的<textarea>标签中设置description字段。

我们需要将以下 URL 添加到urls.py文件中:

url(r'^create_project$', ' TasksManager.views.create_project.page', name='create_project'),

然后,在create_project.py文件中创建我们的视图,代码如下:

from django.shortcuts import render
from TasksManager.models import Project
from django import forms
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
class Form_project_create(forms.Form):
  title = forms.CharField(label="Title", max_length=30)
  description = forms.CharField(widget= forms.Textarea(attrs={'rows': 5, 'cols': 100,}))
  client_name = forms.CharField(label="Client", max_length=50)
def page(request):
  if request.POST:
    form = Form_project_create(request.POST)
    if form.is_valid(): 
      title = form.cleaned_data['title'] 
      description = form.cleaned_data['description']
      client_name = form.cleaned_data['client_name']
      new_project = Project(title=title, description=description, client_name=client_name)
      new_project.save()
      return HttpResponseRedirect(reverse('public_index')) 
    else:
      return render(request, 'en/public/create_project.html', {'form' : form}) 
  else:
    form = Form_project_create() 
  return render(request, 'en/public/create_project.html', {'form' : form})

可以使用我们创建和调整的模板之一。这个表单将与我们创建的所有 Django 表单一样工作。在复制我们已经创建的模板之后,我们只需要更改<form>标签的action属性的标题和 URL。通过访问页面,我们注意到小部件运行良好,并显示更适合长文本的文本区域。

有许多其他小部件可以定制表单。Django 的一个很大的特点是它是通用的,并且随着时间的推移完全可适应。

在表单中设置初始数据

有两种方法可以使用 Django 声明表单字段的初始值。以下示例发生在create_developer.py文件中。

在实例化表单时

以下代码将在name字段中显示new,并在定义主管的<select>字段中选择第一个主管。这些字段可由用户编辑:

form = Form_inscription(initial={'name': 'new', 'supervisor': Supervisor.objects.all()[:1].get().id})

这一行必须替换create_developer.py视图中的以下行:

form = Form_inscription()

在定义字段时

要达到与上一节相同的效果,在name字段中显示new并选择相应字段中的第一个主管;您必须使用以下代码更改声明namesupervisor字段:

name = forms.CharField(label="Name", max_length=30, initial="new")
supervisor = forms.ModelChoiceField(label="Supervisor", queryset=Supervisor.objects.all(), initial=Supervisor.objects.all()[:1].get().id)

摘要

在本章中,我们学习了如何使用 Django 表单。这些表单可以通过自动数据验证和错误显示来节省大量时间。

在下一章中,我们将进一步探讨通用操作,并通过表单节省更多时间。

第八章:通过 CBV 提高生产力

基于类的视图CBV)是从模型生成的视图。简单来说,我们可以说这些就像 ModelForms,因为它们简化了视图并适用于常见情况。

CRUD 是我们在提到数据库上执行的四个主要操作时使用的简写:创建、读取、更新和删除。CBV 是创建执行这些操作的页面的最佳方式。

为创建和编辑模型或数据库表数据创建表单是开发人员工作中非常重复的部分。他们可能会花费很多时间来做这件事(验证、预填字段等)。使用 CBV,Django 允许开发人员在不到 10 分钟内执行模型的 CRUD 操作。它们还有一个重要的优势:如果模型发生变化并且 CBV 做得很好,更改模型将自动更改网站内的 CRUD 操作。在这种情况下,在我们的模型中添加一行代码就可以节省数十甚至数百行代码。

CBV 仍然有一个缺点。它们不太容易使用高级功能或未提供的功能进行自定义。在许多情况下,当您尝试执行具有某些特殊性的 CRUD 操作时,最好创建一个新视图。

您可能会问为什么我们没有直接研究它们-我们本可以节省很多时间,特别是在数据库中添加开发人员时。这是因为这些视图是通用的。它们适用于不需要很多更改的简单操作。当我们需要一个复杂的表单时,CBV 将不起作用,甚至会延长编程时间。

我们应该使用 CBV,因为它们可以节省大量通常用于运行模型上的 CRUD 操作的时间。

在本章中,我们将充分利用我们的TasksManager应用程序。事实上,我们将享受 CBV 所提供的时间节省,以便快速推进这个项目。如果你不能立即理解 CBV 的运作方式,没关系。在前几章中我们已经可以制作网站了。

在本章中,我们将尝试通过以下主题来提高我们的生产力:

  • 我们将使用CreateView CBV 快速构建添加项目页面

  • 我们稍后将看到如何显示对象列表并使用分页系统

  • 然后我们将使用DetailView CBV 来显示项目信息

  • 然后,我们将学习如何使用UpdateView CBV 更改记录中的数据

  • 我们将学习如何更改 CBV 生成的表单

  • 然后,我们将创建一个页面来删除记录

  • 然后,我们最终将创建UpdateView的子类,以使其在我们的应用程序中更加灵活

CreateView CBV

CreateView CBV 允许您创建一个视图,该视图将根据模型自动生成一个表单,并自动保存该表单中的数据。它可以与 ModelForm 进行比较,只是我们不需要创建一个视图。实际上,除了特殊情况外,所有这些代码都将放在urls.py文件中。

极简用法示例

我们将创建一个 CBV,允许我们创建一个项目。这个例子旨在表明,您可以比使用 Django 表单节省更多的时间。我们将能够使用上一章项目中用于创建表单的模板。现在,我们将更改我们的create_project URL 如下:

url (r'^create_project$', CreateView.as_view(model=Project, template_name="en/public/create_project.html", success_url = 'index'), name="create_project"),

我们将在urls.py文件的开头添加以下行:

from django.views.generic import CreateView
from TasksManager.models import Project

在我们的新 URL 中,我们使用了以下新功能:

  • CreateView.as_view:我们调用 CBVCreateViewas_view方法。这个方法将返回一个完整的视图给用户。此外,我们在这个方法中返回多个参数。

  • model:这定义了将应用 CBV 的模型。

  • template_name:这定义了将显示表单的模板。由于 CBV 使用ModelForm,我们不需要更改我们的create_project.html模板。

  • success_url:这定义了一旦更改已经被考虑到我们将被重定向到的 URL。这个参数不是很 DRY,因为我们不能使用 URL 的name属性。当我们扩展我们的 CBV 时,我们将看到如何使用 URL 的名称进行重定向。

就是这样!我们已经添加到urls.py文件中的三行将执行以下操作:

  • 生成表单

  • 生成将表单发送到模板的视图,无论是否有错误。

  • 用户发送数据

我们刚刚使用了 Django 最有趣的功能之一。事实上,仅用三行代码,我们就完成了一个没有任何框架需要超过一百行的工作。我们还将编写一个 CBV,它将允许我们添加一个任务。看一下以下代码:

from TasksManager.models import Project, Task
url (r'^create_task$', CreateView.as_view(model=Task, template_name="en/public/create_task.html", success_url = 'index'), name="create_task"),

然后我们需要复制create_project.html模板并更改base.html模板中的链接。我们的新视图是功能性的,并且我们使用了相同的模板来创建项目。这是一种常见的方法,因为它为开发人员节省了大量时间,但是有一种更严谨的方法可以进行。

要测试代码,我们可以在index.html模板的article_content块的末尾添加以下链接:

<a href="{% url "create_task" %}">Create task</a>

使用 ListView

ListView是一个 CBV,用于显示给定模型的记录列表。该视图生成以发送模板对象,从中我们查看列表。

极简用法示例

我们将看一个显示项目列表并创建指向项目详情的链接的示例。为此,我们必须在urls.py文件中添加以下行:

from TasksManager.models import Project
from django.views.generic.list import ListView

在文件中添加以下 URL:

url (r'^project_list$', ListView.as_view(model=Project, template_name="en/public/project_list.html"), name="project_list"),

我们将通过在article_content块中添加以下行来创建用于以表格形式显示结果的模板,这些行是在扩展base.html模板之后添加的:

<table>
<tr>
  <th>Title</th>
  <th>Description</th>
  <th>Client name</th>
</tr>
{% for project in object_list %}
  <tr>
    <td>{{ project.title }}</td>
    <td>{{ project.description }}</td>
    <td>{{ project.client_name }}</td>
  </tr>
{% endfor %}
</table>

我们创建了与第六章中相同的列表,关于查询集。优点是我们使用了更少的行,并且没有使用任何视图来创建它。在下一部分中,我们将通过扩展此 CBV 来实现分页。

扩展 ListView

可以扩展 ListView CBV 的功能并对其进行自定义。这使我们能够根据网站的需求来调整 CBV。我们可以在as_view方法中定义与参数中相同的元素,但这样更易读,我们还可以覆盖方法。根据 CBV 的类型,将它们分开可以让您:

  • 像我们在 URL 中所做的那样更改模型和模板

  • 更改要执行的查询集

  • 更改发送到模板的对象的名称

  • 指定将重定向用户的 URL

我们将通过修改我们已完成的项目列表来扩展我们的第一个 CBV。我们将对此列表进行两项更改,按标题排序并添加分页。我们将在views/cbv模块中创建ListView.py文件。该文件将包含我们定制的listView。也可以选择架构。例如,我们可以创建一个名为project.py的文件来存储所有关于项目的 CBV。该文件将包含以下代码:

from django.views.generic.list import ListView 
# In this line, we import the ListView class
from TasksManager.models import Project

class Project_list(ListView): 
# In this line, we create a class that extends the ListView class.
  model=Project
  template_name = 'en/public/project_list.html' 
# In this line, we define the template_name the same manner as in the urls.py file.
  paginate_by = 5 
In this line, we define the number of visible projects on a single page.
  def get_queryset(self): 
In this line, we override the get_queryset() method to return our queryset.
    queryset=Project.objects.all().order_by("title")
    return queryset
ListView.py file:
url (r'^project_list$', Project_list.as_view(), name="project_list"),

从现在开始,新页面是功能性的。如果我们测试它,我们会意识到只有前五个项目被显示。的确,在Project_list对象中,我们定义了每页五个项目的分页。要浏览列表,我们需要在模板的article_content块结束之前添加以下代码:

{% if is_paginated %}
  <div class="pagination">
    <span>
    {% if page_obj.has_previous %}
      <a href="{% url "project_list" %}?page={{ page_obj.previous_page_number }}">Previous</a>
    {% endif %}
    <span style="margin-left:15px;margin-right:15px;">
      Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
    </span>
    {% if page_obj.has_next %}
      <a href="{% url "project_list" %}?page={{ page_obj.next_page_number }}">Next</a>
    {% endif %}
    </span>
  </div>
{% endif %}

模板的这一部分允许我们在页面底部创建到前后页面的链接。通过这个示例,我们非常快速地创建了一个带分页的项目排序列表。扩展 CBV 非常方便,可以让我们适应更复杂的用途。在完成此完整示例后,我们将创建一个 CBV 来显示开发人员列表。这个列表将在本书的后面很有用。在导入ListView类之后,我们必须添加以下 URL:

url (r'^developer_list$', ListView.as_view(model=Developer, template_name="en/public/developer_list.html"), name="developer_list"),

然后我们使用base.html的继承模板,并将以下代码放入article_content块中:

<table>
  <tr>
    <td>Name</td>
    <td>Login</td>
    <td>Supervisor</td>
  </tr>
  {% for dev in object_list %}
    <tr>
      <td><a href="">{{ dev.name }}</a></td>
      <td>{{ dev.login }}</td>
      <td>{{ dev.supervisor }}</td>
    </tr>
  {% endfor %}
</table>

我们会注意到开发人员的名字是一个空链接。当我们创建显示开发人员详细信息的页面时,您应该重新填写它。这就是我们将在下一节中使用DetailView来做的。

DetailView CBV

DetailView CBV 允许我们显示来自注册模型的信息。这是我们将学习的第一个具有 URL 参数的 CBV。为了查看记录的详细信息,它将发送其 ID 到 CBV。我们将学习一些例子。

极简主义用法示例

首先,我们将创建一个页面,显示任务的详细信息。为此,我们将通过在urls.py文件中添加以下行来创建 URL:

from django.views.generic import DetailView
from TasksManager.models import Task
url (r'^task_detail_(?P<pk>\d+)$', DetailView.as_view(model=Task, template_name="en/public/task_detail.html"), name="task_detail"),

在这个 URL 中,我们添加了参数发送方面。我们已经在早期的章节中讨论过这种类型的 URL,当时我们涵盖了查询集。

注意

这一次,我们真的需要命名参数pk;否则,CBV 将无法工作。pk表示主键,它将包含您想要查看的记录的 ID。

关于模板,我们将创建en/public/task_detail.html模板,并将以下代码放置在article_content块中:

<h4>
  {{ object.title }}
</h4>
<table>
  <tr>
    <td>Project : {{ object.project }}</td>
    <td>Developer : {{ object.app_user }}</td>
  </tr>
  <tr>
    <td>Importence : {{ object.importence }}</td>
    <td>Time elapsed : {{ object.time_elapsed }}</td>
  </tr>
</table>
<p>
  {{ object.description }}
</p>

在这段代码中,我们引用了外键DeveloperProject。在模板中使用这种语法,我们调用了相关模型的__unicode__()。这使得项目的标题能够显示出来。为了测试这段代码,我们需要创建一个参数化 URL 的链接。将这行添加到您的index.html文件中:

<a href="{% url "task_detail" "1" %}">Detail first view</a><br />

这行将允许我们查看第一个任务的细节。您可以尝试在表格的每一行中创建任务列表和DetailView的链接。这就是我们要做的。

扩展 DetailView

现在我们将创建一个页面,显示开发人员及其任务的详细信息。为了完成这个任务,我们将通过在views/cbv模块中创建一个DetailView.py文件来覆盖DetailView类,并添加以下代码行:

from django.views.generic import DetailView
from TasksManager.models import Developer, Task

class Developer_detail(DetailView): 
  model=Developer
  template_name = 'en/public/developer_detail.html'
  def get_context_data(self, **kwargs):
    # This overrides the get_context_data() method.
    context = super(Developer_detail, self).get_context_data(**kwargs) 
    # This allows calling the method of the super class. Without this line we would not have the basic context.
    tasks_dev = Task.objects.filter(developer = self.object) 
    # This allows us to retrieve the list of developer tasks. We use self.object, which is a Developer type object already defined by the DetailView class.
    context['tasks_dev'] = tasks_dev 
    # In this line, we add the task list to the context.
    return context

我们需要在urls.py文件中添加以下行:

from TasksManager.views.cbv.DetailView import Developer_detail 
url (r'^developer_detail_(?P<pk>\d+)$', Developer_detail.as_view(), name="developer_detail"),

为了查看主要数据和开发任务,我们创建developer_detail.html模板。在从base.html扩展后,我们必须在article_content块中输入以下行:

<h4>
  {{ object.name }}
</h4>
<span>Login : {{ object.login }}</span><br />
<span>Email : {{ object.email }}</span>
<h3>Tasks</h3>
<table>
  {% for task in tasks_dev %}
  <tr>
    <td>{{ task.title }}</td>
    <td>{{ task.importence }}</td>
    <td>{{ task.project }}</td>
  </tr>
  {% endfor %}
</table>

这个例子让我们看到了如何在使用 CBV 时向模板发送数据。

UpdateView CBV

UpdateView是将轻松创建和编辑表单的 CBV。与没有 MVC 模式的开发相比,这是节省更多时间的 CBV。与DetailView一样,我们将不得不将记录的登录信息发送到 URL。为了解决UpdateView,我们将讨论两个例子:

  • 为了让主管能够编辑任务,改变任务

  • 减少执行任务所需的时间

极简主义用法示例

这个示例将展示如何创建一个页面,允许主管修改任务。与其他 CBV 一样,我们将在urls.py文件中添加以下行:

from django.views.generic import UpdateView
url (r'^update_task_(?P<pk>\d+)$', UpdateView.as_view(model=Task, template_name="en/public/update_task.html", success_url="index"), name="update_task"),

我们将编写一个与我们用于CreateView的模板非常相似的模板。唯一的区别(除了按钮文本之外)将是表单的action字段,我们将其定义为空。我们将看到如何在本章末尾填写该字段。现在,我们将利用浏览器在字段为空时提交表单到当前页面的事实。它仍然可见,因此用户可以编写要包含在我们的article_content块中的内容。看一下以下代码:

<form method="post" action="">
  {% csrf_token %} 
  <table>
    {{ form.as_table }} 
  </table>
  <p><input type="submit" value="Update" /></p>
</form>

这个例子真的很简单。如果我们在success_url属性中输入了 URL 的名称,它本来可以更加 DRY。

扩展 UpdateView CBV

在我们的应用程序中,任务的生命周期如下:

  • 主管创建任务而不设置任何持续时间

  • 当开发人员完成任务时,他们会保存他们的工作时间。

我们将在后者上工作,开发者只能更改任务的持续时间。在这个例子中,我们将覆盖UpdateView类。为此,我们将在views/cbv模块中创建一个UpdateView.py文件。我们需要添加以下内容:

from django.views.generic import UpdateView
from TasksManager.models import Task
from django.forms import ModelForm
from django.core.urlresolvers import reverse

class Form_task_time(ModelForm): 
# In this line, we create a form that extends the ModelForm. The UpdateView and CreateView CBV are based on a ModelForm system.
  class Meta:
    model = Task
    fields = ['time_elapsed'] 
    # This is used to define the fields that appear in the form. Here there will be only one field.

class Task_update_time(UpdateView):
  model = Task
  template_name = 'en/public/update_task_developer.html'
form_class = Form_task_time 
# In this line, we impose your CBV to use the ModelForm we created. When you do not define this line, Django automatically generates a ModelForm.
  success_url = 'public_empty' 
  # This line sets the name of the URL that will be seen once the change has been completed.
  def get_success_url(self): 
  # In this line, when you put the name of a URL in the success_url property, we have to override this method. The reverse() method returns the URL corresponding to a URL name.
    return reverse(self.success_url)

我们可以使用以下 URL 来使用这个 CBV:

from TasksManager.views.cbv.UpdateView import Task_update_time
url (r'^update_task_time_(?P<pk>\d+)$', Task_update_time.as_view(), name = "update_task_time"),

对于update_task_developer.html模板,我们只需要复制update_task.html模板并修改其标题。

DeleteView CBV

DeleteView CBV 可以轻松删除记录。与普通视图相比,它并不节省很多时间,但它不会受到不必要视图的负担。我们将展示一个任务删除的例子。为此,我们需要在views/cbv模块中创建DeleteView.py文件。事实上,我们需要覆盖它,因为我们将输入我们想要重定向的 URL 的名称。我们只能将 URL 放在success_url中,但我们希望我们的 URL 尽可能 DRY。我们将在DeleteView.py文件中添加以下代码:

from django.core.urlresolvers import reverse
from django.views.generic import DeleteView
from TasksManager.models import Task

class Task_delete(DeleteView):
  model = Task
  template_name = 'en/public/confirm_delete_task.html'
  success_url = 'public_empty'
  def get_success_url(self):
    return reverse(self.success_url)

在上述代码中,该模板将用于确认删除。事实上,DeleteView CBV 将在删除之前要求用户确认。我们将在urls.py文件中添加以下行,以添加删除的 URL:

from TasksManager.views.cbv.DeleteView import Task_delete
url(r'task_delete_(?P<pk>\d+)$', Task_delete.as_view(), name="task_delete"),

为了完成我们的任务抑制页面,我们将通过在article_content块中扩展base.html创建confirm_delete_task.html模板,并添加以下内容:

<h3>Do you want to delete this object?</h3>
<form method="post" action="">
  {% csrf_token %} 
  <table>
    {{ form.as_table }} 
  </table>
  <p><input type="submit" value="Delete" /></p>
</form>

通过扩展 CBV 进一步

通过扩展它们,CBV 允许我们在页面创建过程中节省大量时间,通过对我们的模型执行 CRUD 操作。通过扩展它们,可以将它们适应我们的使用,并节省更多时间。

使用自定义类 CBV 更新

为了完成我们的抑制页面,在本章中,我们已经看到 CBV 允许我们不受不必要的视图的负担。然而,我们创建了许多相似的模板,并且我们只是为了使用 DRY URL 而覆盖了 CBV。我们将修复这些小缺陷。在本节中,我们将创建一个 CBV 和通用模板,使我们能够:

  • 直接在urls.py文件中使用这个 CBV

  • 输入重定向的 URL 的name属性

  • 从一个模板中受益,用于这些 CBV 的所有用途

在编写我们的 CBV 之前,我们将修改models.py文件,为每个模型赋予verbose_name属性和verbose_name_plural。为此,我们将使用Meta类。例如,Task模型将变成以下内容:

class Task(models.Model):
  # fields
  def __str__(self):
    return self.title
  class Meta:
    verbose_name = "task"
    verbose_name_plural = "tasks"

我们将在views/cbv文件夹中创建一个UpdateViewCustom.py文件,并添加以下代码:

from django.views.generic import UpdateView
from django.core.urlresolvers import reverse

class UpdateViewCustom(UpdateView):
  template_name = 'en/cbv/UpdateViewCustom.html' 
  # In this line, we define the template that will be used for all the CBVs that extend the UpdateViewCustom class. This template_name field can still be changed if we need it.
  url_name="" 
  # This line is used to create the url_name property. This property will help us to define the name of the current URL. In this way, we can add the link in the action attribute of the form.
  def get_success_url(self):
  # In this line, we override the get_success_url() method by default, this method uses the name URLs.
    return reverse(self.success_url)
  def get_context_data(self, **kwargs): 
  # This line is the method we use to send data to the template.
    context = super(UpdateViewCustom, self).get_context_data(**kwargs) 
    # In this line, we perform the super class method to send normal data from the CBV UpdateView.
    model_name = self.model._meta.verbose_name.title() 
    # In this line, we get the verbose_name property of the defined model.
    context['model_name'] = model_name 
    # In this line, we send the verbose_name property to the template.
    context['url_name'] = self.url_name \
    # This line allows us to send the name of our URL to the template.
    return context

然后,我们需要创建显示表单的模板。为此,我们需要创建UpdateViewCustom.html文件,并添加以下内容:

{% extends "base.html" %}
{% block title_html %}
  Update a {{ model_name }} 
  <!-- In this line, we show the type of model we want to change here. -->
{% endblock %}
{% block h1 %}
  Update a {{ model_name }} 
{% endblock %}
{% block article_content %}
  <form method="post" action="{% url url_name object.id %}"> <!-- line 2 -->
  <!-- In this line, we use our url_name property to redirect the form to the current page. -->
    {% csrf_token %} 
    <table>
      {{ form.as_table }} 
    </table>
    <p><input type="submit" value="Update" /></p>
  </form>
{% endblock %}

为了测试这些新的 CBV,我们将以以下方式更改update_task URL:

url (r'^update_task_(?P<pk>\d+)$', UpdateViewCustom.as_view(model=Task, url_name="update_task", success_url="public_empty"), name="update_task"),

以下是一个屏幕截图,显示了 CBV 将显示的内容:

使用自定义类 CBV 更新

摘要

在本章中,我们学会了如何使用 Django 最强大的功能之一:CBV。借助它们,开发人员可以运行高效的 CRUD 操作。

我们还学会了如何通过在项目列表上添加分页或在显示有关用户信息的页面上显示开发者的工作来改变 CBV 以适应我们的使用。

在下一章中,我们将学习如何使用会话变量。我们将通过一个实际的例子来探讨这个问题。在这个例子中,我们将修改任务列表,以显示最后访问的任务。

第九章:使用会话

会话是根据用户存储在服务器上的变量。在许多网站上,将用户数据保留为标识符、购物篮或配置项是有用的。为此,Django 将这些信息存储在数据库中。然后,它随机生成一个字符串作为哈希码,传输给客户端作为 cookie。这种工作方式允许您存储有关用户的大量信息,同时最大限度地减少服务器和客户端之间的数据交换,例如服务器可以生成的标识符类型。

在本章中,我们将做以下事情:

  • 研究会话变量在 Django 框架中的工作方式

  • 学习如何创建和检索会话变量

  • 通过一个实际而有用的例子来研究会话变量

  • 让我们意识到使用会话变量的安全性

Firebug 是 Firefox 的一个插件。这是一个对于 Web 开发人员来说非常方便的工具;它允许您做以下事情:

  • 显示 JavaScript 控制台以读取错误

  • 从浏览器中读取和编辑页面的 HTML 代码

  • 查看网站使用的 cookies

使用会话

使用 Firebug 实现的 cookies

在使用 Firebug 实现的这个截图中,我们注意到我们有两个 cookies:

  • sessionid:这是我们的会话 ID。Django 将通过这个标识符知道它正在处理哪个用户。

  • csrftoken:这个 cookie 是典型的 Django。我们在关于表单的章节中已经谈到过它。它在本章中不会被使用。

以下是存储会话数据的表的截图:

使用会话

会话对于认证系统特别有用。实际上,在许多情况下,当用户连接到网站时,我们会将他们的标识符记录在会话变量中。因此,每个 HTTP 请求,用户都会发送这个标识符来通知网站他们的状态。这也是使管理模块工作的重要系统,我们将在后面的章节中看到。然而,如果会话不经常被删除,它们有一个缺点:它们会在数据库中占用更多的空间。要在 Django 中使用会话,必须启用django.contrib.sessions.middleware.SessionMiddleware中间件,并且浏览器必须接受 cookies。

会话的生命周期如下所述:

  1. 没有任何会话的用户向网站发出 HTTP 请求。

  2. 服务器生成一个会话标识符,并将其与用户请求的页面一起发送到浏览器。

  3. 每当浏览器发出请求时,它将自动发送会话标识符。

  4. 根据系统管理员的配置,服务器定期检查是否有过期的会话。如果是这种情况,它可能会被删除。

使用会话

创建和获取会话变量

使用 Django,存储在数据库中、生成哈希码并与客户端交换将是透明的。会话存储在由request变量表示的上下文中。要在会话变量中保存一个值,我们必须使用以下语法:

request.session['session_var_name'] = "Value"

一旦会话变量被注册,您必须使用以下语法来恢复它:

request.session['session_var_name']

要使用这些行,我们必须确保与请求上下文进行交互。实际上,在某些情况下,比如 CBV,我们无法简单地访问请求上下文。

一个例子 - 显示最后一个被查看的任务

在这个例子中,我们将展示一个使用会话变量的实际例子。一般来说,开发人员会查看要做的任务。他/她选择一个任务,研究它,然后记录和注明花费的时间。我们将在会话变量中存储最后访问的任务的标识符,并将其显示在待完成任务列表的顶部。

为此,我们将不再使用DetailView CBV 来显示任务的详细信息,而是使用一个真正的视图。首先,我们必须定义一个 URL,以便查看我们的视图。为此,我们将使用以下代码修改task_detail URL:

url (r'^task_detail_(?P<pk>\d+)$', 'TasksManager.views.task_detail.page', name="task_detail"),

我们将在views/task_detail.py文件中创建我们的视图,使用以下代码:

from django.shortcuts import render
from TasksManager.models import Task
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def page(request, pk):
  check_task = Task.objects.filter(id = pk) 
  # This line is used to retrieve a queryset of the elements whose ID property matches to the parameter pk sent to the URL. We will use this queryset in the following line : task = check_task.get().

  try:
  # This is used to define an error handling exception to the next line.
    task = check_task.get()
    # This line is used to retrieve the record in the queryset.
  except (Task.DoesNotExist, Task.MultipleObjectsReturned):
  # This allows to process the two kind of exceptions: DoesNotExist and MultipleObjectsReturned. The DoesNotExist exception type is raised if the queryset has no records. The MultipleObjectsReturned exception type is raised if queryset contains multiple records.
    return HttpResponseRedirect(reverse('public_empty'))
    # This line redirects the user if an exception is thrown. We could also redirect to an error page.
  else:
    request.session['last_task'] = task.id
    # This line records the ID property of the task in a session variable named last_task.
    #In this line, we use the same template that defines the form CBV DetailView. Without having to modify the template, we send our task in a variable named object.
  return render(request, 'en/public/task_detail.html', {'object' : task})

然后,我们将使用ListView CBV 创建任务列表。为此,我们必须将以下 URL 添加到urls.py文件中:

url (r'^task_list$', 'TasksManager.views.task_list.page', name="task_list"),

该 URL 的相应视图如下:

from django.shortcuts import render
from TasksManager.models import Task
from django.core.urlresolvers import reverse
def page(request):
  tasks_list = Task.objects.all() 
  # This line is used to retrieve all existing tasks databases.
  last_task = 0 
  # In this line, we define last_task variable with a null value without generating a bug when using the render() method.
  if 'last_task' in request.session: 
  # This line is used to check whether there is a session variable named last_task.
    last_task = Task.objects.get(id = request.session['last_task'])
    # In this line, we get the recording of the last task in our last_task variable.
    tasks_list = tasks_list.exclude(id = request.session['last_task'])
    # In this line, we exclude the last task for the queryset to not have duplicates.
  return render(request, 'en/public/tasks_list.html', {'tasks_list': tasks_list, 'last_task' : last_task})

然后,我们将为我们的列表创建模板。这个例子将是完整的,因为这个列表将创建、读取、更新和删除任务。以下代码必须放在tasks_list.html文件中:

{% extends "base.html" %}
{% block title_html %}
  Tasks list
{% endblock %}
{% block article_content %}
  <table>
  <tr>
    <th>Title</th>
    <th>Description</th>
    <th colspan="2"><a href="{% url "create_task" %}">Create</a></th>
  </tr>
  {% if last_task %} 
  <!-- This line checks to see if we have a record in the last_task variable. If this variable has kept the value 0, the condition will not be validated. In this way, the last accessed task will display at the beginning of the list.-->
    <tr class="important">
      <td><a href="{% url "task_detail" last_task.id %}">{{ last_task.title }}</a></td>
      <td>{{ last_task.description|truncatechars:25 }}</td>
      <td><a href="{% url "update_task" last_task.id %}">Edit</a></td>
      <td><a href="{% url "task_delete" last_task.id %}">Delete</a></td>
    </tr>
  {% endif %}
  {% for task in tasks_list %}
  <!-- This line runs through the rest of the tasks and displays. -->
    <tr>
      <td><a href="{% url "task_detail" task.id %}">{{ task.title }}</a></td>
      <td>{{ task.description|truncatechars:25 }}</td>
      <td><a href="{% url "update_task" task.id %}">Edit</a></td>
      <td><a href="{% url "task_delete" task.id %}">Delete</a></td>
    </tr>
  {% endfor %}
  </table>
{% endblock %}

为了使这个例子完整,我们必须在我们创建的style.css文件中添加以下行:

tr.important td {
  font-weight:bold;
}

这些行用于突出显示最后一个被查询的任务的行。

关于会话安全

会话变量不可被用户修改,因为它们是由服务器存储的,除非在您的网站中选择存储客户端发送的数据。然而,有一种利用系统会话的缺陷。事实上,如果用户无法更改他们的会话变量,他们可能会尝试篡夺另一个用户的会话。

我们将想象一个现实的攻击场景。我们在一家公司,该公司使用网站来集中每个员工的电子邮件和日程安排。我们指定的员工 Bob 对他的同事 Alicia 非常感兴趣。他想读她的电子邮件以了解更多关于她的信息。有一天,当她去休息室喝咖啡时,Bob 坐在 Alicia 的电脑前。像所有员工一样,他使用相同的密码以便于管理,并且可以轻松地连接到 Alicia 的 PC。幸运的是,浏览器已经打开。此外,浏览器定期联系服务器以查看是否有新消息到达,以便会话没有时间过期。他下载了一个工具,比如 Firebug,可以读取 cookies。他检索哈希值,擦除痕迹,然后返回到他的电脑。他更改了浏览器中的ID会话 cookies;因此,他可以访问关于 Alicia 的所有信息。此外,在没有加密的情况下,这种攻击可以在嗅探网络流量的本地网络中远程执行。这被称为会话固定。为了保护自己免受这种攻击,可以采取一些措施:

  • 使用 SSL 等加密通信服务器和客户端之间的通信。

  • 要求用户在访问敏感信息之前输入密码,例如银行信息。

  • 对 IP 地址和会话号码进行审计。如果用户更改 IP 地址,则断开用户的连接。尽管有这个措施,攻击者仍然可以进行 IP 欺骗来窃取受害者的 IP。

摘要

在本章中,我们成功保存了与用户相关的数据。这些数据将在整个会话期间存储。用户无法直接修改它。

我们还研究了安全会话。请记住,用户会话可能会被攻击者窃取。根据项目的规模,有必要采取措施来保护网站。

在下一章中,我们将学习如何使用认证模块。它将允许我们创建用户并限制已登录用户访问某些页面。

第十章:认证模块

认证模块在为用户创建空间时节省了大量时间。以下是该模块的主要优势:

  • 与用户相关的主要操作得到了简化(连接、帐户激活等)

  • 使用该系统可以确保一定级别的安全性

  • 页面的访问限制可以很容易地完成

这是一个非常有用的模块,我们甚至在不知不觉中已经使用了它。事实上,对管理模块的访问是通过认证模块执行的。我们在生成数据库时创建的用户是站点的第一个用户。

这一章大大改变了我们之前编写的应用程序。在本章结束时,我们将有:

  • 修改我们的 UserProfile 模型,使其与模块兼容

  • 创建了一个登录页面

  • 修改了添加开发人员和监督员页面

  • 增加对连接用户的访问限制

如何使用认证模块

在本节中,我们将学习如何通过使我们的应用程序与模块兼容来使用认证模块。

配置 Django 应用程序

通常情况下,我们不需要为管理模块在我们的TasksManager应用程序中工作做任何特殊的操作。事实上,默认情况下,该模块已启用,并允许我们使用管理模块。但是,可能会在禁用了 Web Django 认证模块的站点上工作。我们将检查模块是否已启用。

settings.py文件的INSTALLED_APPS部分中,我们必须检查以下行:

'django.contrib.auth',

编辑 UserProfile 模型

认证模块有自己的用户模型。这也是我们创建UserProfile模型而不仅仅是用户的原因。它是一个已经包含一些字段的模型,比如昵称和密码。要使用管理模块,必须在Python33/Lib/site-package/django/contrib/auth/models.py文件中使用用户模型。

我们将修改models.py文件中的UserProfile模型,将其变为以下内容:

class UserProfile(models.Model):
  user_auth = models.OneToOneField(User, primary_key=True)
  phone = models.CharField(max_length=20, verbose_name="Phone number", null=True, default=None, blank=True)
  born_date = models.DateField(verbose_name="Born date", null=True, default=None, blank=True)
  last_connexion = models.DateTimeField(verbose_name="Date of last connexion", null=True, default=None, blank=True)
years_seniority = models.IntegerField(verbose_name="Seniority", default=0)
def __str__(self):
  return self.user_auth.username

我们还必须在models.py中添加以下行:

from django.contrib.auth.models import User

在这个新模型中,我们有:

  • 创建了与导入的用户模型的OneToOneField关系

  • 删除了用户模型中不存在的字段

OneToOne关系意味着对于每个记录的UserProfile模型,都会有一个用户模型的记录。在做所有这些的过程中,我们深度修改了数据库。鉴于这些变化,并且因为密码以哈希形式存储,我们将不使用 South 进行迁移。

可以保留所有数据并使用 South 进行迁移,但是我们应该开发一个特定的代码来将UserProfile模型的信息保存到用户模型中。该代码还应该为密码生成哈希,但这将是很长的过程,而且不是本书的主题。要重置 South,我们必须执行以下操作:

  • 删除TasksManager/migrations文件夹以及该文件夹中包含的所有文件

  • 删除database.db文件

要使用迁移系统,我们必须使用关于模型的章节中已经使用过的以下命令:

manage.py schemamigration TasksManager --initial
manage.py syncdb –migrate

删除数据库后,我们必须删除create_developer.py中的初始数据。我们还必须删除developer_detail的 URL 和index.html中的以下行:

<a href="{% url "developer_detail" "2" %}">Detail second developer (The second user must be a developer)</a><br />

添加用户

允许您添加开发人员和监督员的页面不再起作用,因为它们与我们最近的更改不兼容。我们将更改这些页面以整合我们的样式更改。create_supervisor.py文件中包含的视图将包含以下代码:

from django.shortcuts import render
from TasksManager.models import Supervisor
from django import forms
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
def page(request):
  if request.POST:
    form = Form_supervisor(request.POST)
    if form.is_valid(): 
      name           = form.cleaned_data['name']
      login          = form.cleaned_data['login']
      password       = form.cleaned_data['password']
      specialisation = form.cleaned_data['specialisation']
      email          = form.cleaned_data['email']
      new_user = User.objects.create_user(username = login, email = email, password=password)
      # In this line, we create an instance of the User model with the create_user() method. It is important to use this method because it can store a hashcode of the password in database. In this way, the password cannot be retrieved from the database. Django uses the PBKDF2 algorithm to generate the hash code password of the user.
      new_user.is_active = True
      # In this line, the is_active attribute defines whether the user can connect or not. This attribute is false by default which allows you to create a system of account verification by email, or other system user validation.
      new_user.last_name=name
      # In this line, we define the name of the new user.
      new_user.save()
      # In this line, we register the new user in the database.
      new_supervisor = Supervisor(user_auth = new_user, specialisation=specialisation)
      # In this line, we create the new supervisor with the form data. We do not forget to create the relationship with the User model by setting the property user_auth with new_user instance.
      new_supervisor.save()
      return HttpResponseRedirect(reverse('public_empty')) 
    else:
      return render(request, 'en/public/create_supervisor.html', {'form' : form})
  else:
    form = Form_supervisor()
  form = Form_supervisor()
  return render(request, 'en/public/create_supervisor.html', {'form' : form})
class Form_supervisor(forms.Form):
  name = forms.CharField(label="Name", max_length=30)
  login = forms.CharField(label = "Login")
  email = forms.EmailField(label = "Email")
  specialisation = forms.CharField(label = "Specialisation")
  password = forms.CharField(label = "Password", widget = forms.PasswordInput)
  password_bis = forms.CharField(label = "Password", widget = forms.PasswordInput) 
  def clean(self): 
    cleaned_data = super (Form_supervisor, self).clean() 
    password = self.cleaned_data.get('password') 
    password_bis = self.cleaned_data.get('password_bis')
    if password and password_bis and password != password_bis:
      raise forms.ValidationError("Passwords are not identical.") 
    return self.cleaned_data

create_supervisor.html模板保持不变,因为我们正在使用 Django 表单。

您可以更改create_developer.py文件中的page()方法,使其与认证模块兼容(您可以参考可下载的 Packt 代码文件以获得进一步的帮助):

def page(request):
  if request.POST:
    form = Form_inscription(request.POST)
    if form.is_valid():
      name          = form.cleaned_data['name']
      login         = form.cleaned_data['login']
      password      = form.cleaned_data['password']
      supervisor    = form.cleaned_data['supervisor'] 
      new_user = User.objects.create_user(username = login, password=password)
      new_user.is_active = True
      new_user.last_name=name
      new_user.save()
      new_developer = Developer(user_auth = new_user, supervisor=supervisor)
      new_developer.save()
      return HttpResponse("Developer added")
    else:
      return render(request, 'en/public/create_developer.html', {'form' : form})
  else:
    form = Form_inscription()
    return render(request, 'en/public/create_developer.html', {'form' : form})

我们还可以修改developer_list.html,内容如下:

{% extends "base.html" %}
{% block title_html %}
    Developer list
{% endblock %}
{% block h1 %}
    Developer list
{% endblock %}
{% block article_content %}
    <table>
        <tr>
            <td>Name</td>
            <td>Login</td>
            <td>Supervisor</td>
        </tr>
        {% for dev in object_list %}
            <tr>
                <!-- The following line displays the __str__ method of the model. In this case it will display the username of the developer -->
                <td><a href="">{{ dev }}</a></td>
                <!-- The following line displays the last_name of the developer -->
                <td>{{ dev.user_auth.last_name }}</td>
                <!-- The following line displays the __str__ method of the Supervisor model. In this case it will display the username of the supervisor -->
                <td>{{ dev.supervisor }}</td>
            </tr>
        {% endfor %}
    </table>
{% endblock %}

登录和注销页面

现在您可以创建用户,必须创建一个登录页面,以允许用户进行身份验证。我们必须在urls.py文件中添加以下 URL:

url(r'^connection$', 'TasksManager.views.connection.page', name="public_connection"),

然后,您必须创建connection.py视图,并使用以下代码:

from django.shortcuts import render
from django import forms
from django.contrib.auth import authenticate, login
# This line allows you to import the necessary functions of the authentication module.
def page(request):
  if request.POST:
  # This line is used to check if the Form_connection form has been posted. If mailed, the form will be treated, otherwise it will be displayed to the user.
    form = Form_connection(request.POST) 
    if form.is_valid():
      username = form.cleaned_data["username"]
      password = form.cleaned_data["password"]
      user = authenticate(username=username, password=password)
      # This line verifies that the username exists and the password is correct.
      if user:
      # In this line, the authenticate function returns None if authentication has failed, otherwise it returns an object that validates the condition.
        login(request, user)
        # In this line, the login() function allows the user to connect.
    else:
      return render(request, 'en/public/connection.html', {'form' : form})
  else:
    form = Form_connection()
  return render(request, 'en/public/connection.html', {'form' : form})
class Form_connection(forms.Form):
  username = forms.CharField(label="Login")
  password = forms.CharField(label="Password", widget=forms.PasswordInput)
  def clean(self):
    cleaned_data = super(Form_connection, self).clean()
    username = self.cleaned_data.get('username')
    password = self.cleaned_data.get('password')
    if not authenticate(username=username, password=password):
      raise forms.ValidationError("Wrong login or password")
    return self.cleaned_data

然后,您必须创建connection.html模板,并使用以下代码:

{% extends "base.html" %}
{% block article_content %}
  {% if user.is_authenticated %}
  <-- This line checks if the user is connected.-->
    <h1>You are connected.</h1>
    <p>
      Your email : {{ user.email }}
      <-- In this line, if the user is connected, this line will display his/her e-mail address.-->
    </p>
  {% else %}
  <!-- In this line, if the user is not connected, we display the login form.-->
    <h1>Connexion</h1>
    <form method="post" action="{{ public_connection }}">
      {% csrf_token %}
      <table>
        {{ form.as_table }}
      </table>
      <input type="submit" class="button" value="Connection" />
    </form>
  {% endif %}
{% endblock %}

当用户登录时,Django 将在会话变量中保存他/她的数据连接。此示例已经允许我们验证登录和密码对用户是透明的。确实,authenticate()login()方法允许开发人员节省大量时间。Django 还为开发人员提供了方便的快捷方式,例如user.is_authenticated属性,用于检查用户是否已登录。用户更喜欢网站上有注销链接,特别是在从公共计算机连接时。我们现在将创建注销页面。

首先,我们需要创建带有以下代码的logout.py文件:

from django.shortcuts import render
from django.contrib.auth import logout
def page(request):
    logout(request)
    return render(request, 'en/public/logout.html')

在先前的代码中,我们导入了身份验证模块的logout()函数,并将其与请求对象一起使用。此函数将删除请求对象的用户标识符,并删除其会话数据。

当用户注销时,他/她需要知道网站实际上已断开连接。让我们在logout.html文件中创建以下模板:

{% extends "base.html" %}
{% block article_content %}
  <h1>You are not connected.</h1>
{% endblock %}

限制对已连接成员的访问

当开发人员实现身份验证系统时,通常是为了限制匿名用户的访问。在本节中,我们将看到控制对我们网页访问的两种方式。

限制对视图的访问

身份验证模块提供了简单的方法来防止匿名用户访问某些页面。确实,有一个非常方便的装饰器来限制对视图的访问。这个装饰器称为login_required

在接下来的示例中,我们将使用设计师以以下方式限制对create_developer模块中的page()视图的访问:

  1. 首先,我们必须使用以下行导入装饰器:
from django.contrib.auth.decorators import login_required
  1. 然后,我们将在视图声明之前添加装饰器:
@login_required
def page(request): # This line already exists. Do not copy it.
  1. 通过添加这两行,只有已登录用户才能访问添加开发人员的页面。如果尝试在未连接的情况下访问页面,您将意识到这并不是很实用,因为获得的页面是 404 错误。要改进此问题,只需告诉 Django 连接 URL 是什么,通过在settings.py文件中添加以下行:
LOGIN_URL = 'public_connection'
  1. 通过这一行,如果用户尝试访问受保护的页面,他/她将被重定向到登录页面。您可能已经注意到,如果您未登录并单击创建开发人员链接,则 URL 包含一个名为 next 的参数。以下是 URL 的屏幕截图:限制对视图的访问

  2. 此参数包含用户尝试查看的 URL。身份验证模块在用户连接时将用户重定向到该页面。为此,我们将修改我们创建的connection.py文件。我们添加导入render()函数以导入redirect()函数的行:

from django.shortcuts import render, redirect
  1. 要在用户登录后重定向用户,我们必须在包含代码 login(request, user)的行之后添加两行。需要添加两行:
if request.GET.get('next') is not None:
  return redirect(request.GET['next'])

当用户会话已过期并且希望查看特定页面时,此系统非常有用。

限制对 URL 的访问

我们所见的系统不仅仅限制对 CBV 生成的页面的访问。为此,我们将使用相同的装饰器,但这次是在urls.py文件中。

我们将添加以下行以导入装饰器:

from django.contrib.auth.decorators import login_required

我们需要更改对应于名为create_project的 URL 的行:

url (r'^create_project$', login_required(CreateView.as_view(model=Project, template_name="en/public/create_project.html", success_url = 'index')), name="create_project"),

使用login_required装饰器非常简单,可以让开发人员不浪费太多时间。

摘要

在本章中,我们修改了我们的应用程序,使其与认证模块兼容。我们创建了允许用户登录和注销的页面。然后,我们学习了如何限制已登录用户对某些页面的访问。

在下一章中,我们将通过添加 AJAX 请求来提高应用程序的可用性。我们将学习 jQuery 的基础知识,然后学习如何使用它来向服务器发出异步请求。此外,我们还将学习如何处理来自服务器的响应。

第十一章:在 Django 中使用 AJAX

AJAX 是异步 JavaScript 和 XML 的缩写。这项技术允许浏览器使用 JavaScript 与服务器异步通信。不一定需要刷新网页来执行服务器上的操作。

已发布许多基于 AJAX 的 Web 应用程序。Web 应用程序通常被描述为只包含一个页面的网站,并且使用 AJAX 服务器执行所有操作。

如果不使用库,使用 AJAX 需要大量代码行才能与多个浏览器兼容。包含 jQuery 后,可以轻松进行 AJAX 请求,同时与许多浏览器兼容。

在本章中,我们将涵盖:

  • 使用 JQuery

  • JQuery 基础

  • 在任务管理器中使用 AJAX

使用 jQuery

jQuery 是一个旨在有效操作 HTML 页面的 DOM 的 JavaScript 库。DOM文档对象模型)是 HTML 代码的内部结构,jQuery 极大地简化了处理过程。

以下是 jQuery 的一些优点:

  • DOM 操作可以使用 CSS 1-3 选择器

  • 它集成了 AJAX

  • 可以使用视觉效果来使页面动画化

  • 良好的文档,有许多示例

  • 围绕 jQuery 创建了许多库

jQuery 基础

在本章中,我们使用 jQuery 进行 AJAX 请求。在使用 jQuery 之前,让我们先了解其基础知识。

jQuery 中的 CSS 选择器

在样式表中使用的 CSS 选择器可以有效地检索具有非常少代码的项目。这是一个非常有趣的功能,它以以下语法实现在 HTML5 选择器 API 中:

item = document.querySelector('tag#id_content');

jQuery 还允许我们使用 CSS 选择器。要使用 jQuery 执行相同的操作,必须使用以下语法:

item = $('tag#id_content');

目前,最好使用 jQuery 而不是选择器 API,因为 jQuery 1.x.x 保证与旧版浏览器的兼容性很好。

获取 HTML 内容

可以使用html()方法获取两个标签之间的 HTML 代码:

alert($('div#div_1').html());

这行将显示一个警报,其中包含<div id="div_1">标签的 HTML 内容。关于输入和文本区域标签,可以以与val()方法相同的方式恢复它们的内容。

在元素中设置 HTML 内容

更改标签的内容非常简单,因为我们使用了与恢复相同的方法。两者之间的主要区别在于我们将一个参数发送到方法。

因此,以下指令将在 div 标签中添加一个按钮:

$('div#div_1').html($('div#div_1').html()+'<button>JQuery</button>');

循环元素

jQuery 还允许我们循环所有与选择器匹配的元素。为此,您必须使用each()方法,如下例所示:

var cases = $('nav ul li').each(function() {
  $(this).addClass("nav_item");
});

导入 jQuery 库

要使用 jQuery,必须首先导入库。将 jQuery 添加到网页有两种方法。每种方法都有其自己的优势,如下所述:

  • 下载 jQuery 并从我们的 Web 服务器导入。使用此方法,我们可以控制库,并确保文件在我们自己的网站上也是可访问的。

  • 使用 Google 托管书店的托管库,可从任何网站访问。优点是我们避免向我们的服务器发出 HTTP 请求,从而节省了一些功率。

在本章中,我们将在我们的 Web 服务器上托管 jQuery,以免受主机的限制。

我们将在应用程序的所有页面中导入 jQuery,因为我们可能需要多个页面。此外,浏览器的缓存将保留 jQuery 一段时间,以免频繁下载。为此,我们将下载 jQuery 1.11.0 并保存在TasksManager/static/javascript/lib/jquery-1.11.0.js文件中。

然后,您必须在base.html文件的 head 标签中添加以下行:

<script src="img/jquery-1.11.0.js' %}"></script>
{% block head %}{% endblock %}

通过这些更改,我们可以在网站的所有页面中使用 jQuery,并且可以在扩展base.html的模板中的head块中添加行。

在任务管理器中使用 AJAX

在这一部分,我们将修改显示任务列表的页面,以便在 AJAX 中执行删除任务。为此,我们将执行以下步骤:

  1. task_list页面上添加一个删除按钮。

  2. 创建一个 JavaScript 文件,其中包含 AJAX 代码和处理 AJAX 请求返回值的函数。

  3. 创建一个 Django 视图来删除任务。

我们将通过修改tasks_list.html模板来添加删除按钮。为此,您必须将tasks_list中的for任务循环更改为以下内容:

{% for task in tasks_list %}
  <tr id="task_{{ task.id }}">
    <td><a href="{% url "task_detail" task.id %}">{{ task.title }}</a></td>
    <td>{{ task.description|truncatechars:25 }}</td>
    <td><a href="{% url "update_task" task.id %}">Edit</a></td>
    <td><button onclick="javascript:task_delete({{ task.id }}, '{% url "task
_delete_ajax" %}');">Delete</button></td>
  </tr>
{% endfor %}

在上面的代码中,我们向<tr>标签添加了一个id属性。这个属性将在 JavaScript 代码中很有用,当页面接收到 AJAX 响应时,它将删除任务行。我们还用一个执行 JavaScript task_delete() 函数的删除按钮替换了删除链接。新按钮将调用task_delete()函数来执行 AJAX 请求。这个函数接受两个参数:

  • 任务的标识符

  • AJAX 请求的 URL

我们将在static/javascript/task.js文件中创建这个函数,添加以下代码:

function task_delete(id, url){
  $.ajax({
    type: 'POST', 
    // Here, we define the used method to send data to the Django views. Other values are possible as POST, GET, and other HTTP request methods.
    url: url, 
    // This line is used to specify the URL that will process the request.
    data: {task: id}, 
    // The data property is used to define the data that will be sent with the AJAX request.
    dataType:'json', 
    // This line defines the type of data that we are expecting back from the server. We do not necessarily need JSON in this example, but when the response is more complete, we use this kind of data type.
    success: task_delete_confirm,
    // The success property allows us to define a function that will be executed when the AJAX request works. This function receives as a parameter the AJAX response.
    error: function () {alert('AJAX error.');} 
    // The error property can define a function when the AJAX request does not work. We defined in the previous code an anonymous function that displays an AJAX error to the user.
  });
}
function task_delete_confirm(response) {
  task_id = JSON.parse(response); 
  // This line is in the function that receives the AJAX response when the request was successful. This line allows deserializing the JSON response returned by Django views.
  if (task_id>0) {
    $('#task_'+task_id).remove(); 
    // This line will delete the <tr> tag containing the task we have just removed
  }
  else {
    alert('Error');
  }
}

我们必须在tasks_list.html模板中的title_html块之后添加以下行,以在模板中导入task.js

{% load static %}
{% block head %}
  <script src="img/task.js' %}"></script>
{% endblock %}

我们必须在urls.py文件中添加以下 URL:

  url(r'^task-delete-ajax$', 'TasksManager.views.ajax.task_delete_ajax.page', name="task_delete_ajax"),

这个 URL 将使用view/ajax/task_delete_ajax.py文件中包含的视图。我们必须创建带有__init__.py文件的 AJAX 模块,以及我们的task_delete_ajax.py文件,内容如下:

from TasksManager.models import Task
from django.http import HttpResponse
from django import forms
from django.views.decorators.csrf import csrf_exempt
# We import the csrf_exempt decorator that we will use to line 4.
import json
# We import the json module we use to line 8.
class Form_task_delete(forms.Form):
# We create a form with a task field that contains the identifier of the task. When we create a form it allows us to use the Django validators to check the contents of the data sent by AJAX. Indeed, we are not immune that the user sends data to hack our server.
  task       = forms.IntegerField()
@csrf_exempt
# This line allows us to not verify the CSRF token for this view. Indeed, with AJAX we cannot reliably use the CSRF protection.
def page(request):
  return_value="0"
  # We create a variable named return_value that will contain a code returned to our JavaScript function. We initialize the value 0 to the variable.
  if len(request.POST) > 0:
    form = Form_task_delete(request.POST)
    if form.is_valid():
    # This line allows us to verify the validity of the value sent by the AJAX request.
      id_task = form.cleaned_data['task']
      task_record = Task.objects.get(id = id_task)
      task_record.delete()
      return_value=id_task
      # If the task been found, the return_value variable will contain the value of the id property after removing the task. This value will be returned to the JavaScript function and will be useful to remove the corresponding row in the HTML table.
  # The following line contains two significant items. The json.dumps() function will return a serialized JSON object. Serialization allows encoding an object sequence of characters. This technique allows different languages to share objects transparently. We also define a content_type to specify the type of data returned by the view.
  return HttpResponse(json.dumps(return_value), content_type = "application/json")

总结

在本章中,我们学习了如何使用 jQuery。我们看到了如何使用这个库轻松访问 DOM。我们还在我们的TasksManager应用程序上创建了一个 AJAX 请求,并编写了处理这个请求的视图。

在下一章中,我们将学习如何部署基于 Nginx 和 PostgreSQL 服务器的 Django 项目。我们将逐步看到并讨论安装步骤。

第十二章:使用 Django 进行生产

当网站的开发阶段完成并且您希望使其对用户可访问时,您必须部署它。以下是要执行此操作的步骤:

  • 完成开发

  • 选择物理服务器

  • 选择服务器软件

  • 选择服务器数据库

  • 安装 PIP 和 Python 3

  • 安装 PostgreSQL

  • 安装 Nginx

  • 安装 virtualenv 并创建虚拟环境

  • 安装 Django,South,Gunicorn 和 psycopg2

  • 配置 PostgreSQL

  • 将 Work_manager 调整为生产

  • 初始 South 迁移

  • 使用 Gunicorn

  • 启动 Nginx

完成开发

在部署之前进行一些测试非常重要。实际上,当网站部署后,问题更难解决;这对开发人员和用户来说可能是巨大的时间浪费。这就是为什么我再次强调:您必须进行充分的测试!

选择物理服务器

物理服务器是托管您的网站的机器。在家中托管自己的网站是可能的,但这不适合专业网站。事实上,由于许多网站用户使用该网站,因此需要使用网络主机。有许多不同类型的住宿,如下所示:

  • 简单托管:这种类型的托管适用于需要高质量服务但没有很多功率的网站。通过这种住宿,您无需处理系统管理,但它不允许与专用服务器一样的灵活性。这种类型的托管在 Django 网站上也有另一个缺点:尚未有许多提供与 Django 兼容的住宿。

  • 专用服务器:这是最灵活的住宿类型。我们租用(或购买)一台服务器,由提供互联网连接和其他服务的网络主机提供。根据所需的配置不同,价格也不同,但功能强大的服务器非常昂贵。这种类型的住宿要求您处理系统管理,除非您订阅外包服务。外包服务允许您使用系统管理员来照顾服务器,并获得报酬。

  • 虚拟服务器:虚拟服务器与专用服务器非常相似。它们通常价格较低,因为一些虚拟服务器可以在单个物理服务器上运行。主机经常提供额外的服务,如服务器热备份或复制。

选择住宿类型应基于您的需求和财政资源。

以下是提供 Django 的主机的非详尽列表:

  • alwaysdata

  • WebFaction

  • DjangoEurope

  • DjangoFoo Hosting

选择服务器软件

在开发阶段,我们使用了 Django 附带的服务器。该服务器在开发过程中非常方便,但不适合生产网站。事实上,开发服务器既不高效也不安全。您必须选择另一种类型的服务器来安装它。有许多 Web 服务器;我们选择了其中两个:

  • Apache HTTP 服务器:根据 Netcraft 的数据,自 1996 年以来,这一直是最常用的 Web 服务器。这是一个模块化服务器,允许您安装模块而无需编译服务器。近年来,它的使用越来越少。根据 Netcraft 的数据,2013 年 4 月,市场份额为 51%。

  • Nginx:Nginx 以其性能和低内存消耗而闻名。它也是模块化的,但模块需要在编译中集成。2013 年 4 月,Netcraft 知道的所有网站中有 14%使用了 Nginx 作为其 Web 服务器。

选择服务器数据库

选择服务器数据库非常重要。实际上,该服务器将存储网站的所有数据。在数据库中寻求的主要特征是性能,安全性和可靠性。

选择取决于以下三个标准的重要性:

  • Oracle:这个数据库是由 Oracle Corporation 开发的系统数据库。有这个数据库的免费开源版本,但其功能有限。这不是一个免费的数据库。

  • MySQL:这是属于 Oracle 的数据库系统(自从收购 Sun Microsystems 以来)。它是 Web 上广泛使用的数据库,包括LAMPLinux Apache MySQL PHP)平台。它以双 GPL 和专有许可证进行分发。

  • PostgreSQL:这是一个根据 BSD 许可证分发的免费数据库系统。这个系统被认为是稳定的,并提供高级功能(如数据类型的创建)。

  • SQLite:这是我们在开发网站期间使用的系统。它不适合访问量很大的网站。事实上,整个数据库都在一个 SQLite 文件中,并且不允许竞争对手访问数据。此外,没有用户或系统没有安全机制。但是,完全可以用它来向客户演示。

  • MongoDB:这是一个面向文档的数据库。这个数据库系统被归类为 NoSQL 数据库,因为它使用了使用BSON二进制 JSON)格式的存储架构。这个系统在数据库分布在多台服务器之间的环境中很受欢迎。

部署 Django 网站

在本书的其余部分,我们将使用 HTTP Nginx 服务器和 PostgreSQL 数据库。本章的解释将在 GNU / Linux Debian 7.3.0 32 位系统上进行。我们将从一个没有任何安装的新的 Debian 操作系统开始。

安装 PIP 和 Python 3

对于以下命令,您必须使用具有与超级用户帐户相同特权的用户帐户登录。为此,请运行以下命令:

su

在此命令之后,您必须输入 root 密码。

首先,我们更新 Debian 存储库:

apt-get update

然后,我们安装 Python 3 和 PIP,就像在第二章中所做的那样,创建一个 Django 项目

apt-get install python3
apt-get install python3-pip
alias pip=pip-3.2

安装 PostgreSQL

我们将安装四个软件包以便使用 PostgreSQL:

apt-get install libpq-dev python-dev postgresql postgresql-contrib

然后,我们将安装我们的 web Nginx 服务器:

apt-get install nginx

安装 virtualenv 并创建虚拟环境

我们已经像在第二章中所做的那样安装了 Python 和 PIP,但在安装 Django 之前,我们将安装 virtualenv。这个工具用于为 Python 创建虚拟环境,并在同一个操作系统上拥有不同的库版本。事实上,在许多 Linux 系统中,Debian 已经安装了 Python 2 的一个版本。建议您不要卸载它以保持系统的稳定。我们将安装 virtualenv 来设置我们自己的环境,并简化我们未来的 Django 迁移:

pip install virtualenv

然后,您必须创建一个将托管您的虚拟环境的文件夹:

mkdir /home/env

以下命令在/home/env/文件夹中创建一个名为django1.6的虚拟环境:

virtualenv /home/env/django1.6

然后,我们将通过发出以下命令为所有用户提供访问环境文件夹的所有权限。从安全的角度来看,最好限制用户或组的访问,但这将花费很多时间:

cd /home/
chmod -R 777 env/
exit

安装 Django、South、Gunicorn 和 psycopg2

我们将安装 Django 和所有 Nginx 和 Django 通信所需的组件。我们首先激活我们的虚拟环境。以下命令将连接我们到虚拟环境。因此,从此环境中执行的所有 Python 命令只能使用此环境中安装的软件包。在我们的情况下,我们将安装四个仅安装在我们的虚拟环境中的库。对于以下命令,您必须以没有超级用户特权的用户登录。我们不能从 root 帐户执行以下命令,因为我们需要 virtualenv。但是,root 帐户有时会覆盖虚拟环境,以使用系统中的 Python,而不是虚拟环境中存在的 Python。

source /home/env/django1.6/bin/activate
pip install django=="1.6"
pip install South

Gunicorn 是一个扮演 Python 和 Nginx 之间 WSGI 接口角色的 Python 包。要安装它,请发出以下命令:

pip install gunicorn 

psycopg2 是一个允许 Python 和 PostgreSQL 相互通信的库:

pip install psycopg2

要重新连接为超级用户,我们必须断开与虚拟环境的连接:

deactivate

配置 PostgreSQL

对于以下命令,您必须使用具有与超级用户相同特权的用户帐户登录。我们将连接到 PostgreSQL 服务器:

su
su - postgres

以下命令创建一个名为workmanager的数据库:

createdb workmanager

然后,我们将为 PostgreSQL 创建一个用户。输入以下命令后,会要求更多信息:

createuser -P 

以下行是 PostgreSQL 要求的新用户信息和响应(用于本章):

Role name : workmanager
Password : workmanager
Password confirmation : workmanager
Super user : n
Create DB : n
Create new roles : n

然后,我们必须连接到 PostgreSQL 解释器:

psql 

我们在新数据库上给予新用户所有权限:

GRANT ALL PRIVILEGES ON DATABASE workmanager TO workmanager;

然后,我们退出 SQL 解释器和与 PostgreSQL 的连接:

\q
exit

将 Work_manager 适应到生产环境

对于以下命令,您必须以没有超级用户特权的用户登录。

在部署的这个阶段,我们必须复制包含我们的 Django 项目的文件夹。要复制的文件夹是Work_manager文件夹(其中包含Work_managerTasksManager文件夹以及manage.py文件)。我们将其复制到虚拟环境的根目录,即/home/env/django1.6

要复制它,您可以使用您拥有的手段:USB 键,SFTP,FTP 等。然后,我们需要编辑项目的settings.py文件以使其适应部署。

定义数据库连接的部分变为以下内容:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2', 
        'NAME':  'workmanager',      
        'USER': 'workmanager',                     
        'PASSWORD': 'workmanager',                 
        'HOST': '127.0.0.1',                     
        'PORT': '',                     
    }
}

我们必须修改ALLOWED_HOSTS行如下:

ALLOWED_HOSTS = ['*']

另外,重要的是不要使用DEBUG模式。实际上,DEBUG模式可以为黑客提供宝贵的数据。为此,我们必须以以下方式更改DEBUGTEMPLATE_DEBUG变量:

DEBUG = False
TEMPLATE_DEBUG = False

初始 South 迁移

我们激活我们的虚拟环境以执行迁移并启动 Gunicorn:

cd /home/env/django1.6/Work_manager/
source /home/env/django1.6/bin/activate
python3.2 manage.py schemamigration TasksManager --initial
python3.2 manage.py syncdb -–migrate

有时,使用 PostgreSQL 创建数据库时会出现错误,即使一切顺利。要查看数据库的创建是否顺利,我们必须以 root 用户身份运行以下命令,并验证表是否已创建:

su
su - postgres
psql -d workmanager
\dt
\q
exit

如果它们被正确创建,您必须进行一个虚假的 South 迁移,手动告诉它一切顺利:

python3.2 manage.py migrate TasksManager --fake

使用 Gunicorn

然后,我们启动我们的 WSGI 接口,以便 Nginx 进行通信:

gunicorn Work_manager.wsgi

启动 Nginx

另一个命令提示符作为 root 用户必须使用以下命令运行 Nginx:

su
service nginx start

现在,我们的 Web 服务器是功能性的,并且已准备好与许多用户一起工作。

总结

在本章中,我们学习了如何使用现代架构部署 Django 网站。此外,我们使用了 virtualenv,它允许您在同一系统上使用多个版本的 Python 库。

在这本书中,我们学习了什么是 MVC 模式。我们已经为我们的开发环境安装了 Python 和 Django。我们学会了如何创建模板、视图和模型。我们还使用系统来路由 Django 的 URL。我们还学会了如何使用一些特定的元素,比如 Django 表单、CBV 或认证模块。然后,我们使用了会话变量和 AJAX 请求。最后,我们学会了如何在 Linux 服务器上部署 Django 网站。

附录 A. 备忘单

当开发人员学会如何使用技术时,通常需要搜索新的信息或语法。他/她可能会浪费很多时间。本附录的目的是为 Django 开发人员提供快速参考。

模型中的字段类型

以下各节涵盖了模型中字段类型的非穷尽列表。

模型字段是将保存在数据库中的字段。根据所选的数据库系统,字段类型可能会因使用的数据库而异。

类型是以以下方式指定其选项的:

Type (option1 = example_data, option2 = example_data) [information]

数字字段类型

此部分中呈现的字段是数字字段,如整数和小数:

  • SmallIntegerField():定义小整数字段;对于某些数据库,较低值为 256

  • IntegerField():定义整数字段

  • BigIntegerField():精度为 64 位,范围为 -9223372036854775808 到 9223372036854775807

  • DecimalField(max_digits = 8decimal_places = 2)

选项的描述如下:

  • max_digits:设置组成整数的数字的位数

  • decimal_places:设置组成数字的小数部分的位数

字符串字段类型

此部分包含包含字符串的字段类型:

  • CharField(max_length = 250)

  • TextField(max_length = 250):此字段具有在 Django 表单中呈现为<textarea>标签的特点

  • EmailField(max_length = 250):此字段是包含 Django 表单的电子邮件验证程序的CharField

选项的描述如下:

  • max_length:设置组成字符串的最大字符数

时间字段类型

此部分包含包含临时数据的字段类型:

  • DateField(auto_now = falseauto_now_add = true)

  • DateTimeField(auto_now = falseauto_now_add = true)

  • TimeField(auto_now = falseauto_now_add = true)

选项的描述如下:

  • auto_now:这会自动将字段设置为每次保存记录时的当前时间

  • auto_now_add:这会在创建对象时自动将字段设置为当前时间

其他类型的字段

此部分包含不属于先前类别的字段类型:

  • BooleanField()

  • FileField:(upload_to = "path",max_length="250"):此字段用于在服务器上存储文件

  • ImageField(upload_to = "path",max_length="250",height_field =height_img,width_field= width_img):此字段对应于FileField,但对图像进行特殊处理,如存储图像的高度和宽度

选项的描述如下:

  • Upload_to:定义将存储与此字段对应的文件的文件夹。

  • max_lengthFileFieldImageField字段实际上是存储上传文件的路径和名称的文本字段。

  • height_fieldwidth_field:这些以模型的整数字段作为参数。此字段用于存储图像的大小。

模型之间的关系

此部分包含定义模型之间关系的字段类型:

  • ForeignKey(model,related_name = "foreign_key_for_dev",to_field="field_name",limit_choices_to=dict_or_Q,on_delete=)

  • OneToOneField(model,related_name = "foreign_key_for_dev",to_field="field_name",limit_choices_to=dict_or_Q,on_delete=)

  • ManyToManyField(model,related_name = "foreign_key_for_dev",to_field="field_name",limit_choices_to=dict_or_Q,on_delete=)

选项的描述如下:

  • model:在这里,您必须指定要使用的模型类的名称。

  • related_name:这允许您命名关系。当存在多个与同一模型的关系时,这是必不可少的。

  • to_field:这定义了与模型的特定字段的关系。默认情况下,Django 会创建与主键的关系。

  • on_delete:在删除字段时数据库操作可以是CASCADEPROTECTSET_NULLSET_DEFAULTDO_NOTHING

  • limit_choices_to:这定义了限制与关系的记录的查询集。

模型元属性

模型元属性应该在模型中的元类中以以下方式定义:

class Product(models.Model):
  name = models.CharField()
  class Meta:
    verbose_name = "product"

以下属性用于定义放置它们的模型的信息:

  • db_tables:设置存储在数据库中的表的名称

  • verbose_name:为用户设置记录的名称

  • verbose_name_plural:为用户设置多个记录的名称

  • ordering:在列出记录时设置默认顺序

模型字段的常见选项

以下选项适用于模型的所有字段:

  • default:为字段设置默认值。

  • null:为字段启用空值,并且如果在关系字段上定义了此选项,则使关系变为可选。

  • blank:允许您将字段留空。

  • error_messages:指定一系列错误消息。

  • help_text:设置帮助消息。

  • unique:定义不包含重复项的字段。

  • verbose_name:定义一个可供人类阅读的字段名称。不要首字母大写;Django 会自动完成。

  • choices:这定义了字段的可能选择数量。

  • db_column:设置在数据库中创建的字段的名称。

表单字段

可以在表单中使用所有类型的字段模型。实际上,某些类型的模型字段已经被创建用于在表单中特定的用途。例如,TextField模型字段与CharField没有任何不同,除了默认情况下,在表单中,TextField字段显示一个<textarea>标签和一个<input type="text">名称。因此,您可以编写一个表单字段如下:

field1 = forms.TextField()

表单字段的常见选项

以下选项适用于所有表单字段:

  • error_messages:指定一系列错误消息

  • help_text:设置帮助消息

  • required:定义必须填写的字段

  • initial:为字段设置默认值

  • validators:定义验证字段值的特定验证器

  • widget:为字段定义特定的小部件

小部件表单

小部件允许您定义呈现表单字段的 HTML 代码。我们将解释小部件可以生成的 HTML 代码,如下所示:

  • TextInput:对应于<input type="text" />

  • Textarea:对应于<textarea></textarea>

  • PasswordInput:对应于<input type="password" />

  • RadioSelect:这对应于<input type="radio" />

  • Select:对应于<select><option></option></select>

  • CheckboxInput:对应于<input type="checkbox" />

  • FileInput:这对应于<input type="file" />

  • HiddenInput:这对应于<input type="hidden" />

错误消息(表单和模型)

以下是在表单字段输入不正确时可以设置的错误消息的部分列表:

  • required:当用户未在字段中填写数据时显示此消息

  • min_length:当用户未提供足够的数据时显示此消息

  • max_length:当用户超出字段的大小限制时显示此消息

  • min_value:当用户输入的值太低时显示此消息

  • max_value:当用户输入的值太高时显示此消息

模板语言

当开发人员开发模板时,他/她经常需要使用模板语言和过滤器。

模板标签

以下是模板语言的关键元素:

  • {% autoescape on OR off %} {% endautoescape %}:这自动启动自动转义功能,有助于保护显示数据的浏览器(XSS)。

  • {% block block_name %} {% endblock %}: 这设置可以由继承自它们的模板填充的块。

  • {% comment %} {% endcomment %}: 这设置一个不会作为 HTML 发送给用户的注释。

  • {% extends template_name %}: 这会覆盖一个模板。

  • {% spaceless %}: 这会删除 HTML 标签之间的所有空格。

  • {% include template_name %}: 这在当前模板中包含一个名为template_name的模板。包含的模板块不能被重新定义。

字典中的循环

本节向您展示如何循环遍历字典。循环涉及的步骤如下:

  • {% for var in list_var %}: 这允许在list_var字典中循环

  • {% empty %}: 如果字典为空,则显示后续代码

  • {% endfor %}: 这表示循环的结束

条件语句

本节显示了如何执行条件语句:

  • {% if cond %}: 此行检查条件,并在启用时讨论以下代码。

  • {% elif cond %}: 如果第一个条件未经验证,此行将检查另一个条件。如果满足此条件,将处理以下代码。

  • {% else %}: 如果之前的条件都没有被验证,这行将处理以下代码。

  • {% endif %}: 此行结束条件的处理。

模板过滤器

以下是不同的模板过滤器:

  • addslashes: 这在引号前添加斜杠

  • capfirst: 这将首字母大写

  • lower: 这将文本转换为小写

  • upper: 这将文本转换为大写

  • title: 这将每个单词的第一个字符大写

  • cut: 这从给定字符串中删除参数的所有值,例如,{{ value|cut:"*" }}删除所有*字符

  • linebreaks: 这将文本中的换行符替换为适当的 HTML 标记

  • date: 这显示一个格式化的日期,例如,{{ value|date:"D d M Y" }}将显示Wed 09 Jan 2008

  • pluralize: 这允许您显示复数,如下所示:

You have {{ nb_products }} product{{ nb_products|pluralize }} in our cart.
I received {{ nb_diaries }} diar{{ nb_diaries|pluralize:"y,ies" }}.
  • random: 这从列表中返回一个随机元素

  • linenumbers: 这在左侧显示带有行号的文本

  • first: 这显示列表中的第一个项目

  • last: 这显示列表中的最后一个项目

  • safe: 这设置了一个非转义值

  • escape: 这会转义 HTML 字符串

  • escapejs: 这会转义字符以在 JavaScript 字符串中使用

  • default: 如果原始值等于Noneempty,则定义默认值;例如,使用{{ value|default:"nothing" }},如果值为"",它将显示nothing

  • dictsort:这将按键的升序对字典进行排序;例如,{{ value|dictsort:"price"}}将按price对字典进行排序

  • dictsortreversed: 这用于按键的降序对字典进行排序

  • floatformat: 这格式化一个浮点值,以下是示例:

  • 当值为45.332时,{{ value|floatformat:2 }}显示45.33

  • 当值为45.00时,{{ value|floatformat:"-2" }}显示45

查询集方法

以下是查询集方法:

  • all(): 此方法检索模型的所有记录。

  • filter(condition): 此方法允许您过滤查询集。

  • none(): 此方法可以返回一个空的查询集。当您想要清空一个查询集时,此方法很有用。

  • dinstinct(field_name): 此方法用于检索字段的唯一值。

  • values_list(field_name): 此方法用于检索字段的数据字典。

  • get(condition): 此方法用于从模型中检索记录。在使用此方法时,您必须确保它只涉及一个记录。

  • exclude(condition): 此方法允许您排除一些记录。

以下元素是聚合方法:

  • Count(): 这计算返回的记录数

  • Sum(): 这将字段中的值相加

  • Max(): 这检索字段的最大值

  • Min(): 这检索字段的最小值

  • Avg(): 这使用字段的平均值

posted @ 2024-05-20 16:48  绝不原创的飞龙  阅读(17)  评论(0编辑  收藏  举报