Django-Web-开发学习手册-全-

Django Web 开发学习手册(全)

原文:zh.annas-archive.org/md5/C7E16835D8AC71A567CF7E772213E9F7

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Django 是用 Python 编写的,是一个旨在快速构建复杂 Web 应用程序的 Web 应用程序框架,无需任何麻烦。它松散地遵循 MVC 模式,并遵循不重复原则,使数据库驱动的应用程序高效且高度可扩展,并且是迄今为止最受欢迎和成熟的 Python Web 框架。

这本书是一本手册,将帮助您构建一个简单而有效的 Django Web 应用程序。它首先向您介绍 Django,并教您如何设置它并编写简单的程序。然后,您将学习构建您的第一个类似 Twitter 的应用程序。随后,您将介绍标签、Ajax(以增强用户界面)和推文。然后,您将继续创建管理界面,学习数据库连接,并使用第三方库。然后,您将学习调试和部署 Django 项目,并且还将一窥 Django 与 AngularJS 和 Elasticsearch。通过本书的最后,您将能够利用 Django 框架轻松开发出一个功能齐全的 Web 应用程序。

本书内容

第一章《Django 简介》向您介绍了 MVC Web 开发框架的历史,并解释了为什么 Python 和 Django 是实现本书目标的最佳工具。

第二章《入门》向您展示如何在 Unix/Linux、Windows 和 Mac OS X 上设置开发环境。我们还将看到如何创建我们的第一个项目并将其连接到数据库。

第三章《Django 中的代码风格》涵盖了构建网站所需的所有基本主题,例如更好的 Django Web 开发的编码实践,应该使用哪种 IDE 和版本控制。

第四章《构建类似 Twitter 的应用程序》带您了解主要的 Django 组件,并为您的 Twitter 应用程序开发一个工作原型。

第五章《引入标签》教您设计算法来构建标签模型以及在帖子中使用标签的机制。

第六章《使用 AJAX 增强用户界面》将帮助您使用 Django 的 Ajax 增强 UI 体验。

第七章《关注和评论》向您展示如何创建登录、注销和注册页面模板。它还将向您展示如何允许另一个用户关注您以及如何显示最受关注的用户。

第八章《创建管理界面》向您展示了使用 Django 的内置功能的管理员界面的功能,以及如何以自定义方式显示带有侧边栏或启用分页的推文。

第九章《扩展和部署》通过利用 Django 框架的各种功能,为您的应用程序准备部署到生产环境。它还向您展示如何添加对多种语言的支持,通过缓存提高性能,自动化测试,并配置项目以适用于生产环境。

第十章《扩展 Django》讨论了如何改进应用程序的各个方面,主要是性能和本地化。它还教您如何在生产服务器上部署项目。

第十一章《数据库连接》涵盖了各种数据库连接形式,如 MySQL,NoSQL,PostgreSQL 等,这是任何基于数据库的应用程序所需的。

第十二章《使用第三方包》讨论了开源以及如何在项目中使用和实现开源第三方包。

第十三章《调试的艺术》向您展示如何记录和调试代码,以实现更好和更高效的编码实践。

第十四章《部署 Django 项目》向您展示如何将 Django 项目从开发环境移动到生产环境,以及在上线之前需要注意的事项。

第十五章《接下来做什么?》将带您进入下一个级别,介绍 Django 项目中使用的两个最重要和首选组件 AngularJS 和 Elasticsearch。

您需要为本书做好准备

对于本书,您需要在 PC/笔记本电脑上运行最新(最好是)Ubuntu/Windows/Mac 操作系统,并安装 Python 2.7.X 版本。

除此之外,您需要 Django 1.7.x 和您喜欢的任何一个文本编辑器,如 Sublime Text 编辑器,Notepad++,Vim,Eclipse 等。

这本书适合谁

这本书适合想要开始使用 Django 进行 Web 开发的 Web 开发人员。需要基本的 Python 编程知识,但不需要了解 Django。

约定

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

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“username变量是我们想要查看的推文的所有者。”

代码块设置如下:

#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_mytweets.settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

任何命令行输入或输出都写成如下形式:

Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2 
Type "help", "copyright", "credits" or "license" for more information.

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如菜单或对话框中的单词,会在文本中以这种方式出现:“在那个链接中,我们会找到下载按钮,点击下载后,点击下载 Bootstrap。”

注意

警告或重要说明出现在这样的框中。

提示

提示和技巧看起来像这样。

第一章:Django 简介

欢迎来到 Django 开发 2.0 版本!

Django 是一个网页开发框架,网页开发是一种技能。要掌握任何技能,可以遵循著名的"1 万小时"规则,即如果你练习任何东西达到那么长时间,你肯定会成为专家。但那是很长的时间,没有合适的计划,这可能会出错。非常出错。

那么,有没有更好的方法来实现你的目标?有!将你想学习的技能分解成更小的子技能,然后逐个掌握它们。 (程序员称之为"分而治之"规则。)你需要通过研究来确定最重要的子技能。子技能被频繁提及的次数越多,掌握它就越重要。

作为本书的作者,我请求您做出承诺,即在最初令人沮丧的时刻坚持学习本书。学习新技能时会感到沮丧,相信我:当你觉得太简单时,你是在做对的。

在本章中,我们将涵盖以下主题:

  • 首先为什么要进行网页开发?

  • 网页开发中的变化

  • 网页开发中的 MVC 模式

  • 为什么选择 Django

  • Django 内部

首先为什么要进行网页开发?

网站直接在全球观众面前给公司或产品留下第一印象。现在每个初创公司都有一个网站,这有助于向潜在客户或投资者推销他们的想法。

现在一切都在线,所以与其坐视变化,为什么不参与并学习编码呢?学习网页开发是你可以用时间做出的最有价值的投资之一。它不仅会通过让你找到更好的工作来使你受益,而且你还可以以非常简单和直接的方式将你的想法编码成原型。

网页开发必备的要素包括用户界面和用户体验,但遗憾的是这超出了本书的范围。

网页开发中的变化

网页开发在过去几年取得了巨大进步。一些改进列举如下:

  • JavaScript:从编写复杂的选择器到操作文档对象模型DOM)。像jQueryAngularJs这样的库使前端动态变得更简单。JavaScript 甚至发展出了构建生产就绪的服务器端框架node.js

  • 浏览器:从在各种浏览器上简单地打破页面发展到智能地恢复连接,告诉你哪个标签正在播放音乐,或者无缝地渲染实时游戏。

  • 开源:使用他人编写的代码现在比编写自己的代码更可取。这帮助许多项目停止重复造轮子,Django就是最好的例子之一。

  • API 作为脊柱:今天的网络技术可能明天就不一样,或者数据可能不会以相同的方式或在相同的位置表示。换句话说,更多的设备将带有不同的屏幕尺寸。因此,最好将文本与视觉分开。

  • 用户界面:过去,开发团队的宝贵时间被用户界面设计消耗。但是像BootstrapFoundation这样的框架使网页开发变得更加容易。

  • 敏捷开发:在开发周期中快速前进对大多数初创公司是可以接受的。在软件开发周期开始时从未要求完整的需求。因此,持续的客户或利益相关者参与非常重要。Django 框架是这种开发的最合适的框架。正如 Django 的口号所说,"完美主义者的网络框架,有截止日期"

  • 云计算的演变:这在 Web 应用程序的托管端发挥了重要作用,并为上线提供了更快、更可靠和更便宜的解决方案。

  • NoSQL 的诞生:大大降低成本,NoSQL 为开发人员提供了诸如“立即存储,稍后查找价值”和“一起存储任何东西”的自由,使其更适合云环境并具有更强的容错性。

Web 开发中的 MVC 模式

在本书中,您将了解如何使用一个名为 Django 的 Model-View-Controller(MVC)Web 框架,它是用强大而流行的编程语言 Python 编写的。

MVC 基于分离表示的概念。分离表示的理念是在领域对象(模拟我们对真实世界的看法)和表示对象(我们在屏幕上看到的用户界面(UI)元素)之间进行清晰的划分。领域对象应完全独立,并且应该能够支持多个演示,可能同时进行。

这种模式的好处是显而易见的。有了它,设计师可以在不担心数据存储或管理的情况下工作在界面上。开发人员能够编写数据处理的逻辑,而不必深入了解演示细节。因此,MVC 模式迅速进入了 Web 语言,并且严肃的 Web 开发人员开始接受它而不是以前的技术。

本书强调利用 Django 和 Python 创建一个具有今天 Web 2.0 网站常见功能的微型博客 Web 应用程序。本书采用教程风格介绍概念并解释问题的解决方案。它不是 Python 或 Django 的参考手册,因为两者已经有很多资源。本书只假设对标准 Web 技术(HTML 和 CSS)和 Python 编程语言有工作知识。另一方面,Django 将在我们在各章节中构建功能时进行解释,直到我们实现拥有一个可工作的 Web 2.0 应用程序的目标。

多语言支持

Django 通过其内置的国际化系统支持多语言网站。对于那些在拥有多种语言的网站上工作的人来说,这可能非常有价值。该系统使翻译界面变得非常简单。

因此,总之,Django 提供了一组集成和成熟的组件,具有出色的文档,网址为www.djangoproject.com/documentation/

由于其庞大的开发人员和用户社区,现在学习 Web 开发框架的时机是最好的!

为什么选择 Django?

自 MVC 模式传播到 Web 开发以来,与大多数其他语言不同,Python 在 Web 框架方面有了相当多的选择。尽管一开始从众多选择中选择一个可能会令人困惑,但有几个竞争框架只能对 Python 社区有利。

Django 是 Python 的一个可用框架,所以问题是:它有什么特别之处,以至于成为本书的主题?

首先,Django 提供了一组紧密集成的组件。所有这些组件都是由 Django 团队自己开发的。Django 最初是作为一个内部框架开发的,用于管理一系列面向新闻的网站。后来,它的代码在互联网上发布,Django 团队继续使用开源模型进行开发。由于其根源,Django 的组件从一开始就被设计用于集成、可重用性和速度。

Django 的数据库组件,对象关系映射器(ORM),提供了数据模型和数据库引擎之间的桥梁。它支持大量的数据库系统,从一个引擎切换到另一个引擎只是改变一个配置文件的事情。如果决定从一个数据库引擎切换到另一个数据库引擎,这给开发人员带来了很大的灵活性。如果遇到问题,可以在这里找到驱动程序(二进制 Python 包):www.lfd.uci.edu/~gohlke/pythonlibs/

此外,Django 提供了一个整洁的开发环境。它带有一个轻量级的 Web 服务器用于开发和测试。当启用调试模式时,Django 提供非常彻底和详细的错误消息,包含大量的调试信息。所有这些都使得隔离和修复错误变得非常容易。

Django 通过其内置的国际化系统支持多语言网站。对于那些在拥有多种语言的网站上工作的人来说,这可能非常有价值。该系统使得翻译界面变得非常简单。

Django 具备一个 Web 框架所期望的标准功能。这些功能包括以下内容:

  • 一个具有简单但可扩展语法的模板和文本过滤引擎

  • 表单生成和验证 API

  • 可扩展的身份验证系统

  • 用于加速应用程序性能的缓存系统

  • 一个用于生成 RSS 订阅的饲料框架

尽管 Django 没有提供简化使用 Ajax 的 JavaScript 库,但选择一个库并将其与 Django 集成是一件简单的事情,我们将在后面的章节中看到。

因此,总之,Django 提供了一套集成和成熟的组件,并拥有出色的文档,这要归功于其庞大的开发人员和用户社区。有了 Django,现在是学习 Web 开发框架的最佳时机!

Django 内部

我们将提到一些使用 Django 进行更好的 Web 开发的重要原因。以下小节解释了一些最重要的功能。

Django 是成熟的

许多公司直接在生产中使用 Django,并得到了来自世界各地开发人员的持续贡献。一些著名的网站包括PinterestQuora。它已经成为完美的 Web 开发框架。

电池包含

Django 遵循 Python 的电池包含哲学,这意味着 Django 带有许多在 Web 开发过程中解决常见问题的重要额外功能和选项。

组件和模块化框架之间紧密集成

Django 在与第三方模块集成方面非常灵活。存在一个流行的项目(例如数据库领域的mongoDBOpenID主要的SocialAuth),它没有一个用于 Django 集成的应用程序编程接口API)或完整的插件的可能性非常小。

对象关系映射器

这是 Django 项目中最重要的部分之一。Django 的数据库组件,ORM,为 Django 的模态类提供了封装、可移植性、安全性和表现力等功能,这些功能映射到配置的选择数据库。

清晰的 URL 设计

Django 中的 URL 系统非常灵活和强大。它允许您为应用程序中的 URL 定义模式,并定义 Python 函数来处理每个模式。

这使开发人员能够创建既人性化(避免 URL 以.php.aspx等结尾的模式)又搜索引擎友好的 URL。

自动管理界面

Django 带有一个准备好使用的管理界面。这个界面使得管理应用程序数据变得轻而易举。它也非常灵活和可定制。

高级开发环境

此外,Django 提供了一个整洁的开发环境。它配备了一个轻量级的 Web 服务器用于开发和测试。当启用调试模式时,Django 提供非常彻底和详细的错误消息,带有大量的调试信息。所有这些都使得隔离和修复错误变得非常容易。

Django 1.6 和 1.7 中的新功能

在最新版本 1.6 中,Django 带来了一些重大变化,其中一些如下:

  • Python 3 在此版本中得到了官方支持,这意味着它是稳定的,可以用于生产。

  • 布局简单。添加了新的默认值,默认情况下添加了 Django 管理模板,并删除了 Sites 包。

  • 添加了点击劫持防护。

  • 默认数据库是 SQLite3。

  • 随着旧的 API 被弃用,最大的变化是事务得到了改进。DB 层的自动提交默认已启用。

  • 此版本中的 DB 连接是持久的。在 Django 1.5 之前,每个 HTTP 请求都会建立一个新连接,但从 1.6 开始,相同的连接将在请求之间重复使用。

  • 时区默认为 UTC。

  • 简单的应用集成。

  • 可扩展的。

  • 强大的配置机制。

  • 如果没有模型,就不需要models.py文件。

  • 为其子类添加了一个新方法。

  • 允许将游标用作上下文管理器。

  • 为国际化、表单和文件上传添加了许多功能。

  • 它具有更好的功能来避免 CSRF。

  • 除此之外,还引入了一个二进制字段,以及 HTML 5 输入字段(电子邮件、URL 和数字)。

您可以在此处详细阅读新添加的功能:docs.djangoproject.com/en/1.7/releases/1.7/

支持的数据库

Django 对数据有着很好和强大的尊重。正确地对数据进行建模,站点的其余部分就会顺理成章。尽管 Django 是为关系数据库设计的,但非官方的 NoSQL 实现也存在于 Django 中。以下是 Django 支持的关系数据库列表:

  • SQL:SQLite,MySQL 和 PostgreSQL。

  • SQLite:这是 Django 应用程序的默认数据库,主要用于测试目的。

  • PostgreSQL:这是一个广泛使用的开源关系型数据库。我们将基于此构建我们的微博示例。

注意

MySQL 和 PostgreSQL 是 Django 社区中最常用的两种数据库,而 PostgreSQL 是 Django 社区中最受欢迎的。

  • NoSQL:你的数据是否只需要一个表,无论是用户信息还是他们的评论等等?换句话说,是否可以没有插入数据的结构规则或嵌套数据,比如带有评论子文档数组的文章?听起来奇怪吗?是的,它是。在早期,人们使用的是唯一的关系数据库概念,但自从云计算时代开始,程序员们喜欢为每个可能的项目实现 NoSQL 架构。它不存储也不遵循任何正常形式。你不能使用连接,但使用它有许多其他优点。

App Engine、MongoDB、Elasticsearch、Cassandra 和 Redis 是 Django 支持的一些著名的 NoSQL 数据库。MongoDB 在 Django 社区中变得越来越受欢迎。

  • MongoDB:这是一个广泛使用的开源 NoSQL 文档型数据库。我们将用它来创建我们的第二个小型应用程序,用于 URL 缩短。

在本书中,我们将主要处理前述列表中的三个数据库,但其他数据库的实现几乎可以通过最小的配置更改来实现。

有许多由 Django 提供支持的知名网站。其中一些如下:

  • Pinterest:一个内容分享服务,特别是图片和视频

  • Disqus:一个博客评论托管服务

  • Quora:一个基于问题和答案的网站

  • Bitbucket:一个免费的 Git 和 mercurial 代码托管站点

  • Mozilla FirefoxMozilla支持页面

使用本书您将学到的内容

本书侧重于构建微博网站应用程序,并向其添加常见的 Web 2.0 功能。其中一些功能如下:

  • 创建 Django 视图、模型和控制器:这主要涉及学习 Django 框架,即如何在控制器上处理请求,在对存储在数据库中的模型进行必要的操作后呈现视图。

  • 标签和标签云:在微博网站项目中,每条消息都将有一个带有#的标签。这些标签的映射将在本节中处理。

  • 内容定制和搜索:根据关键词或标签搜索消息。

  • Ajax 增强:在搜索或标记期间使用 Ajax 进行自动完成,并对保存的消息或标记进行就地编辑。

  • 朋友网络:列出个人资料的所有朋友并计算其他重要统计数据。

本书不是专注于教授各种 Django 功能,而是使用教程风格来教授如何使用 Django 实现这些功能。因此,它作为官方 Django 文档的补充资源,该文档可以在网上免费获取。

感兴趣吗?太棒了!准备好了吗?我保证这将既有趣又有意思。

总结

在本章中,我们了解了为什么网页开发正在获得优势以及 Web 技术领域发生了什么变化;如何利用 Python 和 Django 框架来利用新的 Web 技术;Django 实际上是什么以及我们可以用它实现什么;最后,支持 Django 的不同类型的数据库。

在下一章中,我们将介绍如何在各种操作系统上(如 Windows、Linux 和 Mac)安装 Python 和 Django,并使用 Django 平台设置我们的第一个项目。

第二章:入门

Python 和 Django 适用于多个平台。在本章中,我们将看到如何在 UNIX/Linux、Windows 和 Mac OS X 上设置我们的开发环境。我们还将看到如何创建我们的第一个项目并将其连接到数据库。

在本章中,我们将涵盖以下主题:

  • 安装 Python

  • 安装 Django

  • 安装数据库系统

  • 创建您的第一个项目

  • 设置数据库

  • 启动开发服务器

安装所需的软件

我们的开发环境包括 Python、Django 和数据库系统。在接下来的章节中,我们将看到如何安装这些软件包。

安装 Python

Django 是用 Python 编写的,因此在设置我们的开发环境的第一步自然是安装 Python。Python 适用于各种操作系统,安装 Python 与安装其他软件包没有什么不同。但是,具体的步骤取决于您的操作系统。

安装时,您需要确保获得 Python 的最新版本。Django 需要 Python 2.7 或更高版本。Python 的最新版本是 3.x 的 3.4.2 和 2.x 版本的 2.7.9。

请阅读与您的操作系统相关的部分以获取安装说明。

在 Windows 上安装 Python

Python 有一个标准的 Windows 用户安装程序。只需前往www.python.org/download/并下载最新版本。接下来,双击.exe.msi文件,按照安装说明逐步进行安装。图形安装程序将指导您完成安装过程,并在“开始”菜单中创建 Python 可执行文件的快捷方式。

安装完成后,我们需要将 Python 目录添加到系统路径中,以便在使用命令提示符时可以访问 Python。要做到这一点,请按照以下步骤操作:

  1. 打开控制面板。

  2. 双击系统和安全图标或文本,然后查找系统(如在 Windows 7 中所示),如下截图所示:在 Windows 上安装 Python

  3. 单击高级系统设置,将弹出一个窗口。

  4. 单击环境变量按钮,将打开一个新的对话框。

  5. 选择Path系统变量并编辑它。

  6. 将 Python 安装路径追加为其值(默认路径通常为c:\PythonXX,其中XX是您的 Python 版本),如下截图所示:在 Windows 上安装 Python

如果要测试安装,请打开运行对话框,输入python,然后按Enter按钮。Python 交互式 shell 应该会打开。

注意

不要忘记使用分号(;)将新路径与之前的路径分隔开。

在 Unix/Linux 上安装 Python

如果您使用 Linux 或其他 Unix 版本,您可能已经安装了 Python。要检查,请打开终端,输入python,然后按Enter按钮。如果您看到 Python 交互式 shell,则已安装 Python。在终端中输入python后,您应该会得到以下输出:

Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2 
Type "help", "copyright", "credits" or "license" for more information.

在 Unix/Linux 上安装 Python

输出的第一行指示您系统上安装的版本(此处为 2.7.6)。

如果您看到错误消息而不是前面的输出,或者安装了旧版本的 Python,请继续阅读。

建议 Unix/Linux 用户通过系统的软件包管理器安装和更新 Python。尽管实际细节因系统而异,但与安装其他软件包没有什么不同。

对于基于 APT 的 Linux 发行版,如DebianUbuntu,打开终端并输入以下内容:

$ sudo apt-get update
$ sudo apt-get install python

如果您有Synaptic Package Manager,只需搜索 Python,标记其安装包,并单击应用按钮。

其他 Linux 发行版的用户应查阅其系统文档,了解如何使用软件包管理器安装软件包。

在 Mac OS X 上安装 Python

Mac OS X 预装了 Python。但是,由于苹果的发布周期,通常是一个旧版本。如果您启动 Python 交互式 shell 并发现版本旧于 2.3,请访问www.python.org/download/mac/并下载适用于您的 Mac OS X 版本的新安装程序。

现在 Python 已经准备就绪,我们几乎可以开始了。接下来,我们将安装virtualenv

安装 virtualenv

使用 virtualenv 可以创建一个隔离的 Python 环境。在开始阶段并不是很需要,但对于依赖管理来说是一个救命稻草(例如,如果您的一个 Web 应用程序需要库的一个版本,而另一个应用程序由于一些遗留或兼容性问题需要同一库的另一个版本,或者如果对一个库或应用程序所做的更改破坏了其他应用程序)。

Virtualenv 可以用来避免这种问题。它将创建自己的环境,这样就不会影响全局设置。它通常会创建自己的目录和共享库,以使 virtualenv 在没有任何外部干扰的情况下工作。如果你有pip 1.3或更高版本,请在全局安装。您可以使用以下命令安装 virtualenv:

$ [sudo] pip install virtualenv

安装 virtualenv

一旦完全下载,virtualenv 将如下所示:

安装 virtualenv

注意

如果您尚未安装 pip,可以使用sudo apt-get install python-pip进行安装。

就这些了!现在您可以使用以下命令创建您的虚拟环境:

$ virtualenv ENV

安装 virtualenv

Virtualenv 有非常详细的在线文档,您在使用 virtualenv 时遇到任何问题都必须遵循。以下内容摘自在线文档:

这将创建ENV/lib/pythonX.X/site-packages,您安装的任何库都将放在这里。它还会创建ENV/bin/python,这是一个使用此环境的 Python 解释器。每次使用该解释器(包括当脚本中有#!/path/to/ENV/bin/python时),都将使用该环境中的库。

我们可以在pypi.python.org/pypi/virtualenv/1.8.2找到 virtualenv 在线文档。

新的virtualenv文件夹还包括 pip 安装程序,因此您可以使用ENV/bin/pip命令将其他软件包安装到环境中。

注意

激活脚本:在新创建的虚拟环境中将有一个bin/activate shell 脚本。对于 Windows 系统,提供了CMDPowershell的激活脚本。

您可以在以下网址阅读更多信息:

virtualenv.readthedocs.org/en/latest/virtualenv.html

在 Unix 系统上,我们可以使用以下命令激活virtualenv脚本:

$ source bin/activate

安装 virtualenv

在 Windows 上,我们可以使用以下命令在命令提示符上激活virtualenv脚本:

: > \path\to\env\Scripts\activate

输入deactivate来撤消更改,如下图所示:

安装 virtualenv

这将更改您的$PATH变量。

要了解有关激活脚本的更多信息,例如您正在使用哪个环境或是否需要激活脚本,请访问以下链接:

virtualenv.readthedocs.org/en/latest/virtualenv.html

安装 Django

安装 Django 非常简单,但在某种程度上取决于您的操作系统。由于 Python 是一种平台无关的语言,Django 有一个包可以在任何操作系统上使用。

要下载 Django,请访问www.djangoproject.com/download/并获取最新的官方版本。本书中的代码是在 Django 1.7(本文撰写时的最新版本)上开发的,但大部分代码应该可以在以后的官方版本上运行。接下来,按照与您的平台相关的说明进行操作。

Django 与操作系统的兼容性- Windows 与 Linux

在处理操作系统时,有一些要注意的地方。在运行 Django 之前,许多软件包和设置需要进行调整,以确保没有任何问题。让我们来看看它们:

  • 一些 Python 软件包在 Windows 上无法正确安装,或者根本无法安装;如果可以安装,当您运行 Django 时会带来很多麻烦

  • 如果您需要部署 Django 应用程序,最好使用类 Unix 系统,因为 99%的情况下,您的部署环境是相同的

  • 如果您的应用程序很复杂,获取所需的依赖项会更容易,无论是 Linux 中的扩展,库等等

在 Windows 上安装 Django

在下载了 Django 存档之后,将其解压到 C 驱动器,并打开命令提示符(从开始 | 附件)。现在,通过发出以下命令,将当前目录更改为您从中提取 Django 的位置:

c:\>cd c:\Django-x.xx

这里,x.xx是您的 Django 版本。

接下来,通过运行以下命令来安装 Django(您需要管理员权限):

注意

如果您的系统没有处理.tar.gz文件的程序,我建议使用7-Zip,它是免费的,可以在www.7-zip.org/上获得。

c:\Django-x.xx>python setup.py install

如果由于某种原因前面的说明没有起作用,您可以手动将存档中的django文件夹复制到 Python 安装目录中的Lib\site-packages文件夹中。这将执行setup.py安装命令的工作。

最后一步是将Django-x.xx\django\bin中的django-admin.py文件复制到系统路径的某个位置,例如c:\windows或您安装 Python 的文件夹。

完成后,您可以安全地删除c:\Django-x.xx文件夹,因为它不再需要了。

就是这样!要测试您的安装,请打开命令提示符并输入以下命令:

c:\>django-admin.py --version

如果您在屏幕上看到 Django 的当前版本,则一切都已设置好。

在 Unix/Linux 和 Mac OS X 上安装 Django

所有 Unix 和 Linux 系统的安装说明都是相同的。您需要在Django-x.xx.tar.gz存档所在的目录中运行以下命令。这些命令将为您提取存档并安装 Django:

$ tar xfz Django-x.xx.tar.gz
$ cd Django-x.xx
$ sudo python setup.py install

前面的说明应该适用于任何 Unix/Linux 系统,以及 Mac OS X。但是,如果您的系统有 Django 的软件包,通过系统的软件包管理器安装 Django 可能会更容易。Ubuntu 有一个;因此,要在 Ubuntu 上安装 Django,只需在 Synaptic 中查找一个名为python-django的软件包,或者运行以下命令:

$ sudo apt-get install python-django

您可以通过运行以下命令来测试您的安装:

$ django-admin.py --version

如果您在屏幕上看到 Django 的当前版本,则一切都已设置好。

安装数据库系统

虽然 Django 不需要数据库来运行,但我们将要开发的应用程序需要。因此,在软件安装的最后一步,我们将确保我们有一个数据库系统来处理我们的数据。

值得注意的是,Django 支持多种数据库引擎:MySQLPostgreSQLMS SQL ServerOracleSQLite。然而,有趣的是,您只需要学习一个 API 就可以使用任何这些数据库系统。这是可能的,因为 Django 的数据库层抽象了对数据库系统的访问。我们稍后会学习这一点,但是现在,您只需要知道,无论您选择哪种数据库系统,您都可以运行本书(或其他地方)开发的 Django 应用程序而无需修改。

如果您使用 Python 2.7 或更高版本,则无需安装任何内容。Python 2.7 带有名为sqlite3的 SQLite 数据库管理系统模块。与客户端-服务器数据库系统不同,SQLite 不需要内存中的常驻进程,并且它将数据库存储在单个文件中,这使其非常适合我们的开发环境。

如果您没有 Python 2.7,您可以通过在www.pysqlite.org/(Windows 用户)下载或通过您的软件包管理器(Unix/Linux)手动安装 SQLite 的 Python 模块。

另一方面,如果您的系统上已经安装了另一个受 Django 支持的数据库服务器,您也可以使用它。我们将在后面的部分中看到,通过编辑配置文件,我们可以告诉 Django 使用哪个数据库系统。

提示

我不需要 Apache 或其他网络服务器吗?

Django 自带自己的网络服务器,在开发阶段我们将使用它,因为它轻量级且预先配置了 Django。但是,Django 也支持 Apache 和其他流行的网络服务器,如 lighttpd、nginx 等。我们将在本书后面的部分中看到,当我们准备部署应用程序时,如何配置 Django 以适用于 Apache。

数据库管理器也是一样。在开发阶段,我们将使用 SQLite,因为它易于设置,但是当我们部署应用程序时,我们将切换到诸如 MySQL 之类的数据库服务器。

正如我之前所说的,无论我们使用什么组件,我们的代码都将保持不变;Django 会处理与网络和数据库服务器的所有通信。

创建您的第一个项目

现在,我们已经准备好了所需的软件,是时候进行有趣的部分了——创建我们的第一个 Django 项目了!

如果您还记得 Django 安装部分,我们使用了一个名为django-admin.py的命令来测试我们的安装。这个实用程序是 Django 项目管理设施的核心,因为它使用户能够执行一系列项目管理任务,包括以下内容:

  • 创建一个新项目

  • 创建和管理项目的数据库

  • 验证当前项目并测试错误

  • 启动开发网络服务器

我们将在本章的其余部分看到如何使用这些任务。

创建一个空项目

要创建您的第一个 Django 项目,请打开终端(或 Windows 用户的命令提示符;即开始 | 运行 | cmd),然后输入以下命令。然后,按Enter

$ django-admin.py startproject django_bookmarks

这个命令将在当前目录中创建一个名为django_bookmarks的文件夹,并在其中创建初始目录结构。让我们看看创建了哪些类型的文件:

django_bookmarks/
|-- django_bookmarks
|   |-- __init__.py
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
`-- manage.py

以下是这些文件的快速解释:

  • __init__.py:Django 项目是 Python 包,这个文件是必需的,用于告诉 Python 这个文件夹应该被视为一个包。

Python 术语中的包是模块的集合,它们用于将类似的文件分组在一起,以防止命名冲突。

  • manage.py:这是另一个用于管理我们项目的实用脚本。您可以将其视为项目版本的django-admin.py文件。实际上,django-admin.pymanage.py共享相同的后端代码。

  • settings.py:这是您的 Django 项目的主要配置文件。在其中,您可以指定各种选项,包括数据库设置、站点语言、需要启用的 Django 功能等。在接下来的章节中,我们将解释此文件的各个部分,但在本章中,我们只会看到如何输入数据库设置。

  • url.py:这是另一个配置文件。您可以将其视为 URL 和处理它们的 Python 函数之间的映射。这个文件是 Django 的强大功能之一,我们将在下一章中看到如何利用它。

当我们开始为应用程序编写代码时,我们将在项目文件夹内创建新文件;因此该文件夹也用作我们代码的容器。

现在您已经对 Django 项目的结构有了一个大致的了解,让我们配置我们的数据库系统。

设置数据库

在本节中,我们将开始使用各种选项和配置文件设置数据库。

好了,现在我们已经准备好了源代码编辑器,让我们打开项目文件夹中的settings.py文件并查看其内容:

"""
Django settings for django_bookmarks project.

For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""

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

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ')9c8g--=vo2*rh$9f%=)=e+@%7e%xe8jptgpfe+(90t7uurfy0'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

TEMPLATE_DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'django_bookmarks.urls'

WSGI_APPLICATION = 'django_bookmarks.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases

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

# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/

STATIC_URL = '/static/'

您可能已经注意到,该文件包含许多控制应用程序各个方面的变量。输入变量的新值就像执行 Python 赋值语句一样简单。此外,该文件有大量注释,注释详细解释了变量的控制内容。

现在让我们关注配置数据库。如前所述,Django 支持多个数据库系统,因此首先,我们必须指定我们将要使用的数据库系统。这由DATABASE_ENGINE变量控制。如果您已安装 SQLite,请将该变量设置为'sqlite3'。否则,请从变量名称旁边的注释中选择与您的数据库引擎匹配的值。

接下来是数据库名称。保持数据库名称默认即可。另一方面,如果您正在使用数据库服务器,您需要执行以下操作:

  • 输入数据库的相关信息:用户名、密码、主机和端口。(SQLite 不需要这些。)

  • 在数据库服务器内创建实际数据库,因为 Django 不会自行执行此操作。例如,在 MySQL 中,可以通过mysql命令行实用程序或 phpMyAdmin 来执行此操作。

最后,我们将告诉 Django 使用表填充已配置的数据库。虽然我们尚未为我们的数据创建任何表(并且在下一章之前也不会这样做),但 Django 需要数据库中的一些表才能正常运行一些功能。创建这些表就像发出以下命令一样简单:

$ python manage.py syncdb

如果一切正确,状态消息将在屏幕上滚动,指示正在创建表。在提示输入超级用户帐户时,请输入您首选的用户名、电子邮件和密码。另一方面,如果数据库配置错误,将打印错误消息以帮助您排除问题。

完成这些操作后,我们就可以启动我们的应用程序了。

提示

使用 python manage.py

运行以python manage.py开头的命令时,请确保您当前位于项目的目录中,其中包含manage.py

启动开发服务器

如前所述,Django 带有一个轻量级的 Web 服务器,用于开发和测试应用程序。该服务器预先配置为与 Django 一起工作,并且更重要的是,每当您修改代码时,它都会重新启动。

要启动服务器,请运行以下命令:

$ python manage.py runserver

接下来,打开浏览器,导航至以下 URL:http://localhost:8000/。您应该会看到欢迎消息,如下截图所示:

启动开发服务器

恭喜!您已经创建并配置了您的第一个 Django 项目。这个项目将是我们构建书签应用程序的基础。在下一章中,我们将开始开发我们的应用程序,网页服务器显示的页面将被我们自己编写的内容替换!

注意

您可能已经注意到,默认情况下,Web 服务器在端口 8000 上运行。如果要更改端口,可以使用以下命令在命令行上指定:

$ python manage.py runserver <port number>

此外,默认情况下,开发服务器只能从本地机器访问。如果您想从网络上的另一台机器访问开发服务器,请使用以下命令行参数:

$ python manage.py runserver 0.0.0.0:<port number>

提示

下载示例代码

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

总结

在这一章中,我们已经准备好了我们的开发环境,创建了我们的第一个项目,并学会了如何启动 Django 开发服务器。我们学会了如何在 Windows 和 Linux 中安装 Django 和 virtualenv。我们学习了 Django 设置的基本机制,甚至学会了如何安装数据库。

现在我们准备开始构建我们的社交书签应用程序!下一章将带您了解主要的 Django 组件,并为我们的书签分享应用程序开发一个工作原型。这将是一个有趣的章节,有很多新东西要学习,所以请继续阅读!

第三章:Django 中的代码风格

由于您来自 Python 背景,您可能已经编写了大量的代码,并且当然也享受过。

Python 代码易于维护,并且适用于小型项目或解决任何竞争性编程比赛;您可以通过将 Python 代码存储在本地或存储在公共文件夹中来实现。但是,如果您正在进行协作项目,特别是 Web 开发,那么这将使一切与传统编码不同。这不仅需要纪律,比如遵循项目的代码语法,还可能需要为您的代码编写大量的文档。在使用任何版本控制工具,比如 GIT 时,您的提交消息(在让其他开发人员更容易理解您正在做什么或已经完成的工作方面起着重要作用)也会广播项目的当前进展。

本章将涵盖您需要遵循的所有基本主题,比如更好的 Django Web 开发的编码实践,使用哪个 IDE,版本控制等等。

我们将在本章学习以下主题:

  • Django 编码风格

  • 使用 IDE 进行 Django Web 开发

  • Django 项目结构

  • 最佳实践——使用版本控制

  • Django 救援团队(在哪里提问 Django 问题)

  • 更快的 Web 开发——使用 Twitter-Bootstrap

注意

本章基于一个重要事实,即代码被阅读的次数远远多于被编写的次数。因此,在您真正开始构建项目之前,我们建议您熟悉 Django 社区为 Web 开发采用的所有标准实践。

Django 编码风格

Django 的大部分重要实践都是基于 Python 的。虽然您可能已经知道它们,但我们仍然会停下来写出所有记录的实践,以便您在开始之前就了解这些概念。当然,在构建项目时,您可以回到本章快速查看。

为了使标准实践成为主流,Python 增强提案被提出,其中一个被广泛采用的开发标准实践是 PEP8,Python 代码的风格指南——Guido van Rossum 编写的 Python 代码的最佳风格。

文档中说道,“PEP8 处理与 Python 文档字符串相关的语义和约定。”更多阅读,请访问legacy.python.org/dev/peps/pep-0008/

理解 Python 中的缩进

当你编写 Python 代码时,缩进起着非常重要的作用。它就像其他语言中的块,比如 C 或 Perl。但是程序员们总是在讨论是否应该使用制表符还是空格,以及如果使用空格,应该使用多少——两个、四个还是八个。使用四个空格进行缩进比使用八个更好,如果有更多的嵌套块,使用八个空格进行每次缩进可能会占用更多字符,无法在单行中显示。但是,这又是程序员的选择。

以下是错误的缩进实践导致的结果:

>>> def a():
...   print "foo"
...     print "bar"
IndentationError: unexpected indent

那么,我们应该使用哪个:制表符还是空格?

选择其中一个,但在同一个项目中不要混合使用制表符和空格,否则维护起来会是一场噩梦。Python 中最流行的缩进方式是使用空格;制表符排在第二位。如果你遇到了混合使用制表符和空格的代码,你应该将其转换为只使用空格。

正确的缩进——我们是否需要每次缩进级别四个空格?

关于这个问题已经有很多混淆,当然,Python 的语法都是关于缩进的。坦率地说:在大多数情况下,确实是这样。因此,强烈建议每个缩进级别使用四个空格,如果你一直使用两个空格的方法,就停止使用它。这没有错,但是当你处理多个第三方库时,你可能最终会得到一个不同版本的代码混乱,最终会变得难以调试。

现在是缩进的问题。当你的代码在一个连续的行中时,你应该垂直对齐,或者你可以选择悬挂缩进。当你使用悬挂缩进时,第一行不应包含任何参数,进一步的缩进应该用来清楚地区分它作为一个连续的行。

注意

悬挂缩进(也称为负缩进)是一种缩进风格,其中所有行都缩进,除了段落的第一行。前面的段落就是悬挂缩进的例子。

以下示例说明了在编写代码时应该如何使用适当的缩进方法:

bar = some_function_name(var_first, var_second,
 var_third, var_fourth) 
# Here indentation of arguments makes them grouped, and stand clear from others.
def some_function_name(
 var_first, var_second, var_third,
 var_fourth):
 print(var_first)
# This example shows the hanging intent.

我们不鼓励以下编码风格,而且在 Python 中也不起作用:

# When vertical alignment is not used, Arguments on the first line are forbidden
foo = some_function_name(var_first, var_second,
 var_third, var_fourth)
# Further indentation is required as indentation is not distinguishable between arguments and source code.
def some_function_name(
 var_first, var_second, var_third,
 var_fourth):
 print(var_first)

虽然不需要额外的缩进,但如果你想使用额外的缩进来确保代码能够工作,你可以使用以下编码风格:

# Extra indentation is not necessary.
if (this
 and that):
 do_something()

提示

理想情况下,你应该将每行限制在最多 79 个字符。这允许使用+字符来查看使用版本控制的差异。为了编辑器的统一性,最好将行限制在 79 个字符。你可以利用剩余的空间做其他用途。

空行的重要性

两个空行和单个空行的重要性如下:

  • 两个空行:双空行可以用于分隔顶层函数和类定义,从而增强代码的可读性。

  • 单个空行:单个空行可以用于以下用例--例如,类中的每个函数可以用单个空行分隔,相关函数可以用单个空行分组。你也可以用单个空行分隔源代码的逻辑部分。

导入一个包

导入一个包是代码可重用的直接影响。因此,总是将导入放在源文件的顶部,紧跟在任何模块注释和文档字符串之后,在模块的全局和常量变量之前。每个导入通常应该在单独的行上。

导入包的最佳方式如下:

import os
import sys

不建议在同一行中导入多个包,例如:

import sys, os

你可以按照以下方式导入包,尽管这是可选的:

from django.http import Http404, HttpResponse

如果你的导入变得更长,你可以使用以下方法来声明它们:

from django.http import (
Http404, HttpResponse, HttpResponsePermanentRedirect
)

分组导入的包

包的导入可以按以下方式分组:

  • 标准库导入:比如sys,os,subprocess等。
import re
import simplejson

  • 相关的第三方导入:这些通常是从 Python 奶酪商店下载的,也就是PyPy(使用 pip install)。这里有一个例子:
from decimal import *

  • 本地应用/特定库的导入:这包括你项目的本地模块,比如模型,视图等。
from models import ModelFoo
from models import ModelBar

Python/Django 的命名约定

每种编程语言和框架都有自己的命名约定。Python/Django 中的命名约定大体上是一样的,但是值得在这里提一下。在创建变量名或全局变量名以及命名类、包、模块等时,你需要遵循这个约定。

这是我们应该遵循的常见命名约定:

  • 正确命名变量:永远不要使用单个字符,例如,'x'或'X'作为变量名。这在你平常的 Python 脚本中可能没问题,但是当你构建一个 web 应用程序时,你必须适当地命名变量,因为它决定了整个项目的可读性。

  • 包和模块的命名:建议模块使用小写和短名称。如果使用下划线可以提高可读性,则可以使用下划线。Python 包也应具有短小写名称,尽管不鼓励使用下划线。

  • 由于模块名称映射到文件名(models.pyurls.py等),因此选择模块名称要相当简短是很重要的,因为一些文件系统不区分大小写并且会截断长名称。

  • 命名类:类名应遵循CamelCase命名约定,内部使用的类可以在其名称中加上下划线。

  • 全局变量名称:首先,应避免使用全局变量,但如果需要使用它们,可以通过__all__来防止全局变量被导出,或者通过在名称中定义带有前缀下划线的方式(旧的传统方式)。

  • 函数名称和方法参数:函数名称应为小写,并用下划线分隔,self作为实例化方法的第一个参数。对于类或方法,使用 CLS 或对象进行初始化。

  • 方法名称和实例变量:使用函数命名规则,必要时使用下划线分隔单词以提高可读性。仅对非公共方法和实例变量使用一个前导下划线。

使用 IDE 进行更快的开发

在源代码编辑器方面市场上有很多选择。有些人喜欢全功能的集成开发环境(IDE),而其他人喜欢简单的文本编辑器。选择完全取决于您;选择您感觉更舒适的。如果您已经使用某个程序来处理 Python 源文件,我建议您继续使用,因为它将与 Django 一起很好地工作。否则,我可以提出一些建议,比如这些:

  • SublimeText:这个编辑器非常轻巧而功能强大。它适用于所有主要平台,支持语法高亮和代码补全,并且与 Python 兼容。该编辑器是开源的,您可以在www.sublimetext.com/找到它

  • PyCharm:我要说,这是最智能的代码编辑器,具有高级功能,如代码重构和代码分析,使开发更清洁。Django 的功能包括模板调试(这是一个赢家),还有快速文档,因此这种查找对于初学者来说是必不可少的。社区版是免费的,您可以在购买专业版之前试用 30 天。

使用 Sublime 文本编辑器设置您的项目

本书中大多数示例将使用Sublime 文本编辑器编写。在本节中,我们将展示如何安装和设置 Django 项目。

  1. 下载和安装:您可以从网站www.sublimetext.com的下载选项卡下载 Sublime。单击下载文件选项进行安装。

  2. 为 Django 设置:Sublime 拥有非常庞大的插件生态系统,这意味着一旦您下载了编辑器,就可以安装插件以添加更多功能。

安装成功后,它将如下所示:

使用 Sublime 文本编辑器设置项目

注意

最重要的是Package Control,它是在 Sublime 内直接安装附加插件的管理器。这将是您唯一手动安装的软件包。它将处理其余的软件包安装。

使用 Sublime 文本编辑器设置项目

使用 Sublime 进行 Python 开发的一些建议如下:

  • Sublime Linter:在您编写 Python 代码时,它会立即提供有关代码的反馈。它还支持 PEP8;此插件将实时突出显示我们在前一节中讨论的有关更好编码的内容,以便您可以修复它们。使用 Sublime 文本编辑器设置项目

  • Sublime CodeIntel:这是由SublimeLint的开发人员维护的。Sublime CodeIntel 具有一些高级功能,例如直接跳转到定义,智能代码完成和导入建议。使用 Sublime 文本编辑器设置项目

您还可以探索其他 Sublime 插件,以提高您的生产力。

设置 PyCharm IDE

您可以使用任何您喜欢的 IDE 进行 Django 项目开发。我们将在本书中使用 pycharm IDE。建议使用此 IDE,因为它将在调试时帮助您使用断点,这将节省您大量时间弄清楚实际出了什么问题。

以下是如何安装和设置pycharm IDE 用于 Django:

  1. 下载和安装:您可以从以下链接检查功能并下载 pycharm IDE:

www.jetbrains.com/pycharm/

设置 PyCharm IDE

  1. 为 Django 设置:为 Django 设置 pycharm 非常容易。您只需导入项目文件夹并提供manage.py路径,如下图所示:设置 PyCharm IDE

Django 项目结构

Django 项目结构在 1.6 版本中已更改。Django(django-admin.py)还有一个startapp命令来创建一个应用程序,因此现在是时候告诉您 Django 中应用程序和项目之间的区别了。

项目是一个完整的网站或应用程序,而应用程序是一个小型的、独立的 Django 应用程序。应用程序基于这样一个原则,即它应该做一件事,并且做得正确。

为了简化从头开始构建 Django 项目的痛苦,Django 通过自动生成基本项目结构文件来为您提供优势,从而可以将任何项目推进到其开发和功能添加阶段。

因此,总之,我们可以说项目是应用程序的集合,应用程序可以作为一个独立实体编写,并且可以轻松地导出到其他应用程序以供重用。

要创建您的第一个 Django 项目,请打开终端(或 Windows 用户的命令提示符),输入以下命令,然后按Enter

$ django-admin.py startproject django_mytweets

此命令将在当前目录中创建一个名为django_mytweets的文件夹,并在其中创建初始目录结构。让我们看看创建了哪些类型的文件。

新结构如下:

django_mytweets///
django_mytweets/
manage.py

这是django_mytweets/的内容:

django_mytweets/
__init__.py
settings.py
urls.py
wsgi.py

以下是这些文件的快速解释:

  • django_mytweets(外部文件夹):此文件夹是项目文件夹。与以前的项目结构相反,在以前的项目结构中,整个项目都保存在一个文件夹中,新的 Django 项目结构在某种程度上暗示着每个项目都是 Django 中的一个应用程序。

这意味着您可以在与 Django 项目相同的级别上导入其他第三方应用程序。此文件夹还包含manage.py文件,其中包括所有项目管理设置。

  • manage.py:这是用于管理我们的项目的实用程序脚本。您可以将其视为项目版本的django-admin.py。实际上,django-admin.pymanage.py共享相同的后端代码。

注意

当我们要调整更改时,将提供有关设置的进一步澄清。

让我们看看manage.py文件:

#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_mytweets.settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

一旦阅读以下代码解释,manage.py文件的源代码将不言自明。

#!/usr/bin/env python

第一行只是声明接下来的文件是一个 Python 文件,然后是导入部分,其中导入了ossys模块。这些模块主要包含与系统相关的操作。

import os
import sys

下一段代码检查文件是否由主函数执行,这是要执行的第一个函数,然后将 Django 设置模块加载到当前路径。由于您已经在运行虚拟环境,这将为所有模块设置路径为当前运行虚拟环境的路径。

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_mytweets.settings")
django_mytweets/ ( Inner folder)
__init__.py

Django 项目是 Python 包,这个文件是必需的,用于告诉 Python 这个文件夹应该被视为一个包。在 Python 的术语中,包是模块的集合,它们用于将类似的文件分组在一起,并防止命名冲突。

  • settings.py:这是 Django 项目的主要配置文件。在其中,您可以指定各种选项,包括数据库设置、站点语言、需要启用的 Django 功能等。在接下来的章节中,随着我们构建应用程序的进展,将解释该文件的各个部分。

默认情况下,数据库配置为使用 SQLite 数据库,这是用于测试目的的建议使用。在这里,我们将只看到如何在设置文件中输入数据库;它还包含基本的设置配置,并且在manage.py文件中稍作修改,它可以被移动到另一个文件夹,比如configconf

要使每个其他第三方应用程序成为项目的一部分,我们需要在settings.py文件中注册它。INSTALLED_APPS是一个包含所有已安装应用程序条目的变量。随着项目的增长,管理起来变得困难;因此,INSTALLED_APPS变量有三个逻辑分区,如下所示:

  • DEFAULT_APPS:此参数包含默认的 Django 已安装应用程序(如管理员)

  • THIRD_PARTY_APPS:此参数包含其他应用程序,比如SocialAuth,用于社交认证

  • LOCAL_APPS:此参数包含您创建的应用程序

  • url.py:这是另一个配置文件。您可以将其视为 URL 和 Django 视图函数之间的映射。这个文件是 Django 更强大的功能之一,我们将在下一章中看到如何利用它。

当我们开始为我们的应用程序编写代码时,我们将在项目文件夹内创建新文件。因此,该文件夹也作为我们代码的容器。

现在您对 Django 项目的结构有了一个大致的了解,让我们配置我们的数据库系统。

最佳实践 - 使用版本控制

版本控制是一个系统,它会记住您在项目中所做的所有更改,随着您的不断进展。在任何时间点,您都可以查看对特定文件所做的更改;随着时间的推移,您可以还原它或进一步编辑它。

对于具有多个贡献者的项目,特别是那些同时在同一文件上工作的项目,版本控制更有意义。版本控制是一个救命稻草,因为它记录了文件的各个版本,并允许选项,比如通过合并或丢弃任一副本来保存两个版本。

我们将使用分布式版本控制,也就是说,每个开发人员都有项目的完整副本(与子版本控制相反,其中存储库托管在系统服务器上)。

Git - 最新和最流行的版本控制工具

Git是我们将用于项目的版本控制工具。它是目前最好的版本控制工具,也是开源的。Git 除了源代码文件外,还可以很好地处理其他类型的文件,比如图像、PDF 等。您可以从以下网址下载 Git:

git-scm.com/downloads

大多数现代集成开发环境已经内置了版本控制系统支持;像 PyCharm、Sublime 这样的 IDE 已经有了可以将 Git 集成到工作目录中的插件。Git 可以使用git命令从终端初始化,并且您可以使用git --help命令查看它提供的更多选项。

Git 的工作原理

作为开发人员,我们有一个与远程服务器(通常称为存储库)同步的本地项目副本,并且可以将其发送到远程存储库。当其他开发人员想要将更改推送到远程存储库时,他们必须首先拉取您的更改。这最大程度地减少了中央存储库上的冲突机会,其中每个开发人员都是同步的。整个工作流程在下一节中显示。

设置您的 Git

任何项目都可以添加到 Git 进行版本控制,将文件夹创建为 Git 存储库。要做到这一点,使用以下命令:

  • $git init:如果要复制现有的 Git 存储库,这可能是您的朋友已经在GitHubBitbucket上托管了它的情况,请使用以下命令:

  • $git clone URL:远程存储库的 URL,如github.com/AlienCoders/web-development.git

暂存区:暂存区是您在提交文件之前必须首先列出所有文件的地方。简而言之,暂存是需要作为中间步骤而不是直接提交的,因为当发生冲突时,它们会在暂存区中标记。只有在冲突解决后才能提交文件。

让我们看看以下命令及其用途:

  • $git add <file-name>$git add:用于批量将所有文件添加到暂存区。

  • $git status:了解您的工作目录的状态,已添加哪些文件,哪些文件尚未添加。

  • $git diff:获取已修改和已暂存的状态,或者获取已修改但尚未暂存的状态。

  • $ git commit -m:要提交所做的更改,首先必须将它们添加到暂存区;然后,您必须使用此命令提交它们。

  • $ git rm <file-name>:如果您错误地将任何文件添加到暂存区,可以使用此命令从暂存区中删除它。

  • $git stash: Git 不跟踪重命名的文件。换句话说,如果您已经重命名了已经暂存的文件,您将不得不再次将它们添加到暂存区,然后提交。您可以通过使用以下命令将更改保存到存储库而不实际提交。

  • $git stash apply:它将所有当前更改保存到堆栈中。然后,您可以继续使用您的更改。一旦您有能力获取您保存的更改,您可以使用此命令。

Git 中的分支

版本控制的另一个概念是分支(Git)。分支就像您的提交路径,默认情况下,所有提交都在主分支上进行。分支主要用于跟踪项目中的功能。每个功能都可以作为分支进行工作;一旦功能完成,就可以将其合并回主分支。

分支的基本工作流程是这样的:您最初有一个主分支,并为每个新功能创建一个新分支。更改将提交到新分支,一旦完成功能,您可以将其合并回主分支。这可以用以下方式直观表示:

  • $git branch:要列出使用 Git 的现有分支,我们需要使用此命令。Git 中的分支

  • git checkout -b <new-branch-name>:使用此命令可以在现有存储库中创建一个新分支。我们可以通过以下块图逻辑地看到它的外观:Git 中的分支

您将收到一条消息,通知您已切换到新分支。如果要切换回旧分支,可以使用以下命令:

  • $git checkout <old-branch-name>:您将看到消息切换到分支<old-branch-name>

  • $git merge <branch-name>:功能完成后,您可以使用此命令将其合并到您选择的分支。这将分支<branch-name>合并到当前分支。要将更改同步回<branch-name>,您可以从当前分支切换到分支<branch-name>并再次合并。您还可以使用标签在提交历史中标记重要的点。

  • 提交后,您可以使用$git tag -a v1.0命令标记重要的提交。

  • 要从远程服务器获取新更改,您可以使用$git fetch命令从 Git 中获取更改。

  • 将更改直接合并到当前分支,您可以使用$git pull命令。

  • 完成更改后,您可以使用$git push命令将其提交并推送到远程存储库。

设置数据库

在本节中,我们将首次开始使用代码。因此,我们将不得不选择一个源代码编辑器来输入和编辑代码。您可以使用任何您喜欢的源代码编辑器。如前所述,我们已经使用 Sublime 文本编辑器来编写本书的代码。

好了,现在您已经准备好一个源代码编辑器,让我们打开项目文件夹中的settings.py并查看其中包含的内容:

# Django settings for django_mytweets project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
    # ('Your Name', 'your_email@domain.com'),
)
MANAGERS = ADMINS
DATABASE_ENGINE = ''   # 'postgresql_psycopg2', 'postgresql',
                       # 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = ''     # Or path to database file 
                       # if using sqlite3.
DATABASE_USER = ''     # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = ''     # Set to empty string for localhost.
                       # Not used with sqlite3.
DATABASE_PORT = ''     # Set to empty string for default.
                       # Not used with sqlite3.

settings.py文件中还有许多行,但我们已经削减了此文件的其余内容。

您可能已经注意到,该文件包含许多控制应用程序各个方面的变量。输入变量的新值就像执行 Python 赋值语句一样简单。此外,该文件有大量注释,并且注释详细解释了变量控制的内容。

现在让我们关注配置数据库。如前所述,Django 支持多个数据库系统,因此,首先,我们必须指定要使用的数据库系统。这由DATABASE_ENGINE变量控制。如果安装了 SQLite,请将变量设置为sqlite3。否则,从变量名称旁边的注释中选择与您的数据库引擎匹配的值。

接下来是数据库名称。我们将为您的数据库选择一个描述性名称;编辑DATABASE_NAME并将其设置为django_mytweetsdb。如果您使用 SQLite,这就是您需要做的。另一方面,如果您使用数据库服务器,请按照以下说明操作:

  • 输入数据库的相关信息-用户名、密码、主机和端口(SQLite 不需要这些)。

  • 在数据库服务器中创建实际数据库,因为 Django 不会自行执行此操作。例如,在 MySQL 中,可以通过mysql命令行实用程序或phpMyAdmin来完成此操作。

进行这些简单的编辑后,settings.py中的数据库部分现在如下所示:

DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'django_mytweetsdb'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

最后,我们将告诉 Django 使用表填充配置的数据库。尽管我们尚未为我们的数据创建任何表(直到下一章我们才会这样做),但 Django 需要数据库中的一些表才能正常运行一些功能。创建这些表就像发出以下命令一样简单:

$ python manage.py syncdb

如果一切正确,状态消息将在屏幕上滚动,指示正在创建表。在提示超级用户帐户时,请输入您首选的用户名、电子邮件和密码。另一方面,如果数据库配置错误,将打印错误消息以帮助您排除故障。

完成后,我们准备启动我们的应用程序。

注意

使用 python manage.py

运行以python manage.py开头的命令时,请确保您当前位于项目的目录中,其中包含manage.py文件。

启动开发服务器

如前所述,Django 带有一个轻量级的 Web 服务器,用于开发和测试应用程序。该服务器预先配置为与 Django 一起工作,更重要的是,每当您修改代码时,它都会重新启动。

要启动服务器,请运行以下命令:

$ python manage.py runserver

接下来,打开浏览器并导航到此 URL:http://localhost:8000/。您应该会看到欢迎消息,如下面的截图所示:

启动开发服务器

恭喜!您已经创建并配置了您的第一个 Django 项目。这个项目将是我们构建书签应用程序的基础。在下一章中,我们将开始开发我们的应用程序,Web 服务器显示的页面将被我们自己编写的内容替换!

正如您可能已经注意到的,Web 服务器默认在端口8000上运行。如果要更改端口,可以使用以下命令在命令行上指定:

$ python manage.py runserver <port number>

此外,默认情况下,开发服务器只能从本地机器访问。如果要从网络上的另一台机器访问开发服务器,请使用以下命令行参数:

$ python manage.py runserver 0.0.0.0:<port number>

更快的 Web 开发

在 Web 开发中,对 Web 项目成功起到重要帮助的一件事是其用户界面和用户体验。尽管 Django 在后端处理所有业务逻辑,但无疑需要一个令人敬畏的前端设计框架,不仅可以在编码时简化开发人员的生活,而且还可以增强整个 Web 项目的用户体验。因此,我们选择在这里解释Twitter Bootstrap

最小化的 Bootstrap

Bootstrap是一个完整的前端框架,超出了本书的范围,无法让您熟悉它的每个方面。您一定会想知道为什么我们会在 Django 书中讨论 Bootstrap。您被告知了一个前端框架。这里的想法是帮助您构建一个可以直接用于生产的 Web 应用程序,并且您将部署到AWSHeroku等云中。一旦您完成本书,您需要您的项目达到生产级别。因此,通过尽可能简化 Bootstrap,您仍然可以构建一个外观出色的 Django Web 应用程序。

有许多种方法可以根据排列和组合来布置您的网页。为了帮助您了解这一点,我们将看一些例子。

线框是 Web 开发的第一步,这意味着它必须处理页面上内容的位置。如果您已经了解了 Web 设计的基础知识,那么这一部分对您来说将更有意义。如果没有,请先阅读一些内容,以对 Web 开发有一个基本的了解。查找divspan之间的区别,然后一切都会对您有意义。您可以从这里了解更多信息:developer.mozilla.org/en-US/Learn/HTML。Bootstrap 基本页面线框分为行和列;每列进一步分为 12 个部分。通过这些子部分,您可以使用排列来设计您的布局。

当我们从开发人员的角度看网站时,我们注意到的第一件事是使用的线框。例如,当您访问www.facebook.com时,您会在页面中央看到您的新闻订阅,左侧是其他重要链接(例如消息、页面和群组的链接)。在右侧,您会看到可以聊天的朋友。

在 Bootstrap 中可以想象相同的布局为 2-8-2。左侧链接的列将是“2 列”,新闻订阅将是“8 列”,聊天部分将是“2 列”。这是一个基本的线框。

提示

请记住,总和始终必须为 12,因为 Bootstrap 中的活动流体网格系统是基于 12 列网格原则的,以获得更好和更灵活的布局。

现在,Bootstrap 不仅用于使网页响应式-它还有许多其他组件可以使网页看起来更好,更清洁。

要在 Django 中使用 Bootstrap,有两种方式:

  • Django 方式pip install django-bootstrap3

  • 手动方式:下载 Bootstrap 资源并将其复制到静态位置

Django 方式

如果您想使用命令安装 Bootstrap,则必须将settings.py文件中的INSTALLED_APPS变量附加到bootstrap3

以下是使用此方法的简单 HTML 表单的示例 Django 模板:

{% load bootstrap3 %}
{%# simple HTML form #%}
<form action="action_url">
    {% csrf_token %}
    {% bootstrap_form sample_form %}
    {% buttons %}
        <button type="submit" class="btn btn-primary">
            {% bootstrap_icon "heart" %} SUBMIT
        </button>
    {% endbuttons %}
</form>

提示

要了解更多并进行探索,您可以参考以下链接:

django-bootstrap3.readthedocs.org/

手动安装 Bootstrap

这种方法适合初学者,但一旦你有信心,你可以通过遵循命令方法来快捷操作。

在这里,我们将学习项目文件的基本包含,并且其余内容将在即将到来的章节中涵盖。一旦您从在线来源(getbootstrap.com)下载了 Bootstrap,解压后的文件夹结构看起来像这样:

|-- css 
|   |-- bootstrap.css 
|   |-- bootstrap.css.map 
|   |-- bootstrap.min.css 
|   |-- bootstrap-theme.css 
|   |-- bootstrap-theme.css.map 
|   `-- bootstrap-theme.min.css 
|-- fonts 
|   |-- glyphicons-halflings-regular.eot 
|   |-- glyphicons-halflings-regular.svg 
|   |-- glyphicons-halflings-regular.ttf 
|   `-- glyphicons-halflings-regular.woff 
`-- js 
 |-- bootstrap.js 
 `-- bootstrap.min.js 

Django 中使用的本地文件约定有两种类型:一种是“静态”,另一种是“媒体”。静态文件指的是项目的资产,如 CSS,JavaScript 等。媒体文件是项目中上传的文件,主要包括图片,用于显示或下载的视频等。

通过将以下行添加到setting.py文件中,可以将静态文件添加到您的项目中:

STATICFILES_DIRS = (
    # put absolute path here as string not relative path.
    # forward slash to be used even in windows.
    os.path.join(
        os.path.dirname(__file__),
        'static',
    ),
)

现在,您只需要在项目目录中创建一个文件夹,并复制所有 Bootstrap 资源。

总结

在本章中,我们准备了开发环境,创建了我们的第一个项目,设置了数据库,并学会了如何启动 Django 开发服务器。我们学习了为我们的 Django 项目编写代码的最佳方式,并了解了默认的 Django 项目结构。我们学习了命名约定,空行的重要性,以及我们应该使用哪种导入风格以及在哪里使用。

我们看到了哪种编辑器和哪种 IDE 更适合基于 Python 和 Django 的 Web 开发。我们学会了如何使用 Git 来保持我们的代码在存储库中更新。我们学习了一些关于 Bootstrap 来进行前端开发。

下一章将带您了解主要的 Django 组件,并帮助开发我们的 Twitter 应用程序的工作原型。这将是一个有趣的章节,有许多新东西要学习,所以请继续阅读!

第四章:构建类似 Twitter 的应用程序

在之前的章节中,我们学习了编写代码的更好方法。牢记这些要点,现在是时候开始真正的 Django 项目开发,并了解视图、模型和模板。

本章中每个部分的第一部分将介绍基础知识以及特定主题中的工作原理。这将包括适当的实践、标准方法和重要术语。

每个部分的第二部分将是我们的 mytweets Django 应用程序开发中该概念的应用。第一部分可以被视为主题的章节描述,第二部分可以被视为我们的 Django 项目形式的练习,这将是一个独特的学习体验。

本章涵盖以下主题:

  • 关于 Django 术语的说明

  • 设置基本模板应用程序

  • 创建 Django 项目的模板结构

  • 设置应用程序的基本引导

  • 创建主页

  • 介绍基于类的视图

  • 我们的 mytweets 项目的 Django 设置

  • 生成用户页面

  • 设计初始数据库模式

  • 用户注册和账户管理

  • 为主页创建模板

关于 Django 术语的说明

Django 是一个 MVC 框架。但是,在整个代码中,控制器被称为视图,视图被称为模板。Django 中的视图是检索和操作数据的组件,而模板是向用户呈现数据的组件。因此,有时称 Django 为模型模板视图MTV)框架。这种不同的术语既不改变 Django 是一个 MVC 框架的事实,也不影响应用程序的开发方式,但请记住这些术语,以避免可能的混淆,如果您以前使用过其他 MVC 框架。

您可以将本章视为主要 Django 组件的深入介绍。您将学习如何使用视图创建动态页面,如何使用模型存储和管理数据库中的数据,以及如何使用模板简化页面生成。

在学习这些功能的同时,您将对 Django 组件如何工作和相互交互形成一个坚实的理解。随后的章节将更深入地探讨这些组件,因为我们开发更多功能并将它们添加到我们的应用程序中。

设置基本模板应用程序

我们的项目将是一个微博网站,每个用户都将有一个公共页面,其中将显示他们发布的时间轴。

在看到开发服务器的欢迎页面后,首先想到的是如何更改它。要创建我们自己的欢迎页面,我们需要定义一个 URL 形式的应用程序入口点,并告诉 Django 在访问此 URL 时调用特定的 Python 函数。我们将自己编写这个 Python 函数,并让它显示我们自己的欢迎消息。

本节基本上是对我们在上一章中进行的配置的重做,但意图是将所有说明放在一起,以便项目引导需要更少的页面查找。

创建虚拟环境

我们将使用以下命令设置 Django 的虚拟环境,以使其正常工作:

$ virtualenv django_env

输出如下:

New python executable in django_env/bin/python
Installing setuptools, pip...done.

我们现在需要激活虚拟环境并设置所有环境变量,以便所有 Python 安装都将被路由到此环境目录,而不会影响其他设置:

$ source django_env/bin/activate

输出如下:

(django_env)ratan@lenovo:~/code$

安装 Django

虽然您已经安装了 Django,但我们将再次进行安装,因为 Django 将由virtualenv管理,其他项目或用户(或您自己)在其他地方工作时不会被搞乱。

$pip install django

您可能会收到以下错误:

bad interpreter: No such file or directory

如果是这样,请在不带空格的路径中创建您的虚拟环境。很可能,在您创建虚拟环境的位置存在一个包含空格的目录,例如,/home/ratan/folder name with space$virtualenv django_env

如果是这样,请将目录名称更改为以下内容:

/home/ratan/folder_name_with_no_space$virtualenv django_env

我们可以使用命令pip install django继续进行 Django 安装。

输出将如下所示:

Downloading/unpacking django
Downloading Django-1.6.5-py2.py3-none-any.whl (6.7MB): 6.7MB downloaded
Installing collected packages: django
Successfully installed django
Cleaning up...

现在,在我们开始创建 Django 应用程序之前,我们将确保 Git 已安装。使用以下命令查找我们安装的 Git 版本:

$git --version

输出将如下所示:

git version 1.9.1

这证实了我们已安装了 Git。当然,你一定想知道我们是否会在这个项目中使用版本控制。答案是肯定的:随着项目的进行,我们将对大部分项目文件进行版本控制。

创建 Django 项目的模板结构

在本节中,我们将为项目创建结构,例如,为我们的项目创建一个名为mytweets的文件夹,安装所需的包等。运行以下命令:

$django-admin.py startproject mytweets

这将创建名为mytweets的文件夹,我们将使用它作为我们的项目目录。在当前文件夹中,我们看到两个子文件夹:environmentmytweets。现在的问题是我们是否要对我们的环境文件夹进行版本控制。我们不会,因为这些文件非常特定于您当前的系统。它们不会帮助任何人设置与我们相同的环境。然而,在 Python 中还有另一种方法:使用pip freeze命令。这实际上会拍摄您的 Django 应用程序中当前安装的所有库的快照,然后您可以将该列表保存在文本文件中并进行版本控制。因此,您的同事开发人员可以下载相同版本的库。这真的是一种 Pythonic 的做法,不是吗?

您安装新包的最常见方法是使用pip命令。pip install命令有三个版本,如下所示:

$ pip install PackageName

这是默认设置,并安装包的最新版本:

$ pip install PackageName==1.0.4

使用==参数,您可以安装特定版本的包。在这种情况下,即 1.0.4。使用以下命令安装带有版本号的包:

$ pip install 'PackageName>=1.0.4' # minimum version

当您不确定要安装的包版本但有一个想法需要库的最低版本时,请使用上述命令。

使用pip命令安装库非常容易。您只需在命令行中输入以下内容即可:

$pip install -r requirements.txt

现在我们需要冻结当前项目的库:

$pip freeze > requirements.txt

此命令会冻结项目中当前安装的库以及版本号(如果指定),并将它们存储在名为requirements.txt的文件中。

在我们项目的这个阶段,pip freeze命令将看起来像这样。

Django==1.6.5
argparse==1.2.1
wsgiref==0.1.2

要将这些库与项目一起安装回您的新环境中,我们可以运行以下命令:

$pip install -r requirements.txt

因此,我们可以继续初始化我们的代码目录作为 Git 仓库,并将当前路径更改为$cd mytweets。执行以下命令在项目文件夹中构建 Git 仓库:

$git init

输出将如下所示:

Initialized empty Git repository in /home/ratan/code/mytweets/.git/

如果我们在基于 Linux 的系统上运行所有命令以获取详细的目录列表,我们可以看到以下输出:

...
drwxrwxr-x 7 ratan ratan 4096 Aug 2 16:07 .git/
...

这是.git文件夹,根据其命名约定(以点开头),它在目录的正常列表中是隐藏的,即存储所有 Git 相关文件(如分支、提交、日志等)的目录。删除该特定目录将使您的目录无 Git(无版本控制)并且与您当前系统中的任何其他目录一样正常。

我们可以使用以下命令将当前目录中的所有文件添加到暂存区:

$git add .

使用以下命令进行项目的第一次提交:

$git commit -m "initial commit of the project."

输出将如下所示:

[master (root-commit) 597b6ec] initial commit of the project.
5 files changed, 118 insertions(+)
create mode 100755 manage.py
create mode 100644 mytweets/__init__.py
create mode 100644 mytweets/settings.py
create mode 100644 mytweets/urls.py
create mode 100644 mytweets/wsgi.py

第一行(这里是主分支)表示我们在主分支中,接下来的是被提交的文件。

到目前为止,我们已经设置了基本的 Django 模板并将其添加到了版本控制中。可以使用以下命令验证相同的事情:

$git log

输出将如下所示:

commit 597b6ec86c54584a758f482aa5a0f5781ff4b682
Author: ratan <mail@ratankumar.org>
Date: Sat Aug 2 16:50:37 2014 +0530
initial commit of the project.

有关设置作者和为远程存储库推送生成SSH密钥的说明,请参阅以下链接:

help.github.com/articles/set-up-git

help.github.com/articles/generating-ssh-keys

为应用程序设置基本的 Twitter Bootstrap

如前一章介绍的,bootstrap 是用户界面设计的基本框架。我们将继续使用前面提到的第二种方法,即手动下载 bootstrap 文件并将其链接到静态文件夹中。

我们跳过的方法意味着我们不会执行以下命令:

$pip install django-bootstrap3

有关此实现的详细文档,请参阅django-bootstrap3.readthedocs.org/

我们将要遵循的方法是下载 bootstrap 文件并将其放置在项目的静态文件夹中。

要开始使用 bootstrap,我们必须从以下官方 bootstrap 网址下载静态文件:

getbootstrap.com/

当您访问此链接时,您将找到一个下载按钮。单击下载,然后单击下载 Bootstrap。这将以压缩格式提供 bootstrap 资源文件。下载的文件将具有类似bootstrap-3.2.0-dist.zip的名称。解压此 zip 文件的内容。解压后,文件夹bootstrap-3.2.0-dist的结构如下:

|-- css
| |-- bootstrap.css
| |-- bootstrap.css.map
| |-- bootstrap.min.css
| |-- bootstrap-theme.css
| |-- bootstrap-theme.css.map
| |-- bootstrap-theme.min.css
|-- fonts
| |-- glyphicons-halflings-regular.eot
| |-- glyphicons-halflings-regular.svg
| |-- glyphicons-halflings-regular.ttf
| |-- glyphicons-halflings-regular.woff
|-- js
|-- bootstrap.js
|-- bootstrap.min.js

特定于应用程序的静态文件存储在应用程序的static子目录中。

Django 还会查找STATICFILES_DIRS设置中列出的任何目录。让我们更新项目设置,指定settings.py文件中的静态文件目录。

我们可以更新项目的setting.py文件如下以使用 Twitter bootstrap:

STATICFILES_DIRS = (
os.path.join(
os.path.dirname(__file__),
'static',
),
)

这里,static变量将是我们将保存 bootstrap 文件的文件夹。我们将在当前项目目录内创建static文件夹,并将所有解压缩的 bootstrap 文件复制到该文件夹中。

出于开发目的,我们将保持大多数设置不变,例如默认数据库 SQLite;稍后在部署测试应用程序到 MySQL 或我们选择的任何其他数据库时,我们可以将其移动。

在我们实际在项目中使用 bootstrap 之前,我们必须了解一些基本概念,以理解 bootstrap 作为前端框架。

Bootstrap 基于网格系统设计网页,该网格有三个主要组件,如下:

  • 容器:容器用于为整个网页提供基础,通常,bootstrap 的所有组件将是容器的直接或嵌套子对象。换句话说,容器为响应式宽度提供宽度约束。当屏幕分辨率更改时,容器会在设备屏幕上改变其宽度。行和列是基于百分比的,因此它们会自动修改。

容器还为内容提供了来自浏览器边缘的填充,以便它们不会触及视图区域的边缘。默认填充为 15 像素。您永远不需要在容器内部放置另一个容器。以下图片显示了容器的结构:

为应用程序设置基本的 Twitter Bootstrap

  • :行放置在容器内并包含列。Bootstrap 的基本设计层次结构是容器 | | 。行也像列的包装器一样,因此在列由于默认的左浮动属性而变得奇怪的情况下,保持它们分开分组,以便这个问题不会反映在行外部。

行两侧有 15 像素的负边距,这将它们推出容器的 15 像素填充之上。因此,它们被否定,行与容器的边缘相接触,负边距被填充所覆盖。因此,行不会受到容器填充的推动。永远不要在容器外使用行。

为应用程序设置基本的 Twitter Bootstrap

  • :列有 15 像素的填充。这意味着列实际上与行的边缘相接触,行已经与容器的边缘相接触,因为在前一段讨论的容器的否定属性。

列再次有 15 像素的填充,因此列的内容与容器的视图边缘相距 15 像素。

因此,我们不需要特殊的第一列和最后一列,左右都有填充。现在所有列之间都有一个一致的 15 像素间隙。

列内的内容被推送到列的位置,并且它们之间也被 30 像素的间距分隔。我们可以在列内部使用行进行嵌套布局。

为应用程序设置基本的 Twitter Bootstrap

永远不要在行外使用列。

牢记这些要点,我们可以继续设计我们的第一个布局。

URL 和视图 - 创建主页

在 Django 术语中,视图是一个普通的 Python 函数,通过生成相应的页面来响应页面请求。要为主页编写我们的第一个 Django 视图,我们首先需要在项目内创建一个 Django 应用程序。您可以将应用程序视为视图和数据模型的容器。要创建它,请在我们的django_mytweets文件夹内发出以下命令:

$ python manage.py startapp tweets

应用程序创建的语法与项目创建的语法非常相似。我们使用startapp命令作为python manage.py命令的第一个参数,并提供tweets作为我们应用程序的名称。

运行此命令后,Django 将在项目文件夹内创建一个名为tweets的文件夹,其中包含这三个文件:

  • __init__.py:这个文件告诉 Pythontweets是一个 Python 包

  • views.py:这个文件将包含我们的视图

  • models.py:这个文件将包含我们的数据模型

现在让我们创建主页视图。我们将首先在项目内创建一个template文件夹,以保存所有 HTML 文件:

$mkdir templates

现在在其中创建一个名为base.html的基本 HTML 文件,内容如下:

{% load staticfiles %}
<html>
<head>
<link href="{% static 'bootstrap/css/bootstrap.min.css' %}"
rel="stylesheet" media="screen" />">
</head>

<body>
{% block content %}
<h1 class="text-info">">HELLO DJANGO!</h1>
{% endblock %}

<script src="img/bootstrap.min.js' %}"></script>
</body>
</html>

我们的目录结构现在看起来像这样(如果您使用 Linux 操作系统,请使用tree命令):

mytweets/
|-- manage.py
|-- mytweets
| |-- __init__.py
| |-- __init__.pyc
| |-- settings.py
| |-- settings.pyc
| |-- urls.py
| |-- urls.pyc
| |-- wsgi.py
| `-- wsgi.pyc
|-- static
| |-- css
| | |-- bootstrap.css
| | |-- bootstrap.css.map
| | |-- bootstrap.min.css
| | |-- bootstrap-theme.css
| | |-- bootstrap-theme.css.map
| | `-- bootstrap-theme.min.css
| |-- fonts
| | |-- glyphicons-halflings-regular.eot
| | |-- glyphicons-halflings-regular.svg
| | |-- glyphicons-halflings-regular.ttf
| | `-- glyphicons-halflings-regular.woff
| `-- js
| |-- bootstrap.js
| `-- bootstrap.min.js
|-- templates
| `-- base.html
`-- tweets
|-- admin.py
|-- __init__.py
|-- models.py
|-- tests.py
`-- views.py

介绍基于类的视图

基于类的视图是在 Django 中定义视图的新方法。它们不取代基于函数的视图。它们只是一种以 Python 对象而不是函数实现视图的替代方法。它们有两个优点,优于基于函数的视图。使用基于类的视图,不同的 HTTP 请求可以映射到不同的函数,而不是基于request.method参数进行分支的函数视图。可以使用面向对象的技术来重用代码组件,例如混入(多重继承)。

虽然我们将在项目中使用基于类的视图,但为了了解两者之间的确切区别,我们将在这里呈现两者的代码。

我们将不得不更新我们项目的url.py文件,以便在用户请求网站时提供base.html文件。

基于函数的视图

按照以下方式更新view.py文件:

from django.http import HttpResponse

def index(request):
if request.method == 'GET': 
return HttpResponse('I am called from a get Request')
elif request.method == 'POST':
return HttpResponse('I am called from a post Request')

按照以下方式更新urls.py文件:

from django.conf.urls import patterns, include, url
from django.contrib import admin
from tweets import views
admin.autodiscover()

urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
url(r'^admin/', include(admin.site.urls)),
)

使用以下命令运行开发服务器:

$python manage.py runserver

我们将看到一个响应,显示我是从 get 请求中调用的

基于类的视图

更新views.py文件如下:

from django.http import HttpResponse
from django.views.generic import View

class Index(ViewV iew):
def get(self, request): 
return HttpResponse('I am called from a get Request')
def post(self, request): 
return HttpResponse('I am called from a post Request')

urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from tweets.views import Index
admin.autodiscover()

urlpatterns = patterns('',
url(r'^$', Index.as_view()),
url(r'^admin/', include(admin.site.urls)),
)

在开发服务器被访问后,它也会在浏览器上生成相同的结果。我们将在整个项目中使用基于类的视图。

我们所呈现的只是一个字符串,这有点简单。我们在模板文件夹中创建了一个base.html文件,现在将继续使用我们的基于类的视图并呈现我们的base.html文件。

在 Django 中,有多种方法可以呈现我们的页面。我们可以使用这三个函数中的任何一个来呈现我们的页面:render()render_to_response()direct_to_template()。但是,让我们首先看看它们之间的区别以及我们应该使用哪一个:

  • render_to_response(template[, dictionary][, context_instance][, mimetype])render_to_response命令是标准的呈现函数,要使用RequestContext,我们必须指定context_instance=RequestContext(request)

  • render(request, template[, dictionary][, context_instance][, content_type][, status][, current_app])。这是render_to_response命令的新快捷方式,从 Django 的 1.3 版本开始可用。这将自动使用RequestContext

  • direct_to_template(): 这是一个通用视图。它自动使用RequestContext和所有它的context_processor参数。

但是,应该避免使用direct_to_template命令,因为基于函数的通用视图已被弃用。

我们将选择第二个,render()函数,来呈现我们的base.html模板。

下一步是在我们的 Django 应用程序中包含模板文件夹(我们已经创建的带有名为base.html的基本文件的模板文件夹)。为了包含模板,我们将以以下方式更新settings.py文件:

TEMPLATE_DIRS = (
BASE_DIR + '/templates/'
)
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader', 
)

这定义了模板目录并初始化了基本的TEMPLATE_LOADER参数。

mytweets 项目的 Django 设置

让我们使用我们mytweets项目所需的最小设置更新settings.py文件。在启动我们的 mytweets 应用程序之前,我们将添加许多设置,我们将在以下更改中看到。有关此文件的更多信息,请访问docs.djangoproject.com/en/1.6/topics/settings/

有关设置及其值的完整列表,请访问docs.djangoproject.com/en/1.6/ref/settings/

使用以下内容更新我们项目的settings.py文件:

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

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXX'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []

# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)

MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'mytweets.urls'
WSGI_APPLICATION = 'mytweets.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.6/ref/settings/#databases

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

#static file directory inclusion
STATICFILES_DIRS = ( 
os.path.join(
os.path.dirname(__file__),
'static',
),
)

TEMPLATE_DIRS = (
BASE_DIR + '/templates/'
)

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

# Internationalization
# https://docs.djangoproject.com/en/1.6/topics/i18n/

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.6/howto/static-files/

STATIC_URL = '/static/'

现在,如果我们启动开发服务器,我们的屏幕将如下截图所示:

mytweets 项目的 Django 设置

注意

在我们的base.html文件中,我们写了class="h1"而不是<h1></h1>。这是故意为了在运行时检查是否加载了引导文件,即Header 1属性。

正如您可能已经注意到的,我们没有向模板传递任何变量,这大致区分了静态页面和动态页面。让我们继续做这个。我们只需要对views.pybase.html文件进行一些更改,如下所示:

  • views.py文件中的更改:
from django.views.generic import View
from django.shortcuts import render 
class Index(View):
def get(self, request): 
params = {}
params["name"] = "Django"
return render(request, 'base.html', params)
  • base.html文件中的更改
{% load staticfiles %}
<html>
<head>
<link href="{% static 'bootstrap/css/bootstrap.min.css' %}"
rel="stylesheet" media="screen">
</head>

<body>
{% block content %}
<h1>Hello {{name}}!</h1>
{% endblock %}

<script src="img/bootstrap.min.js' %}"></script>
</body>
</html>

我们可以看到它有多简单。我们所做的只是创建一个映射(在 Python 中称为字典)并将name属性分配给它作为 Django,并将其添加到render()函数中作为新参数。它被呈现到 HTML 的基础上,并且可以轻松地调用{{name}}。当它被呈现时,它会用 Django 替换自己。

我们将提交我们到目前为止所做的所有更改。在这之前,让我们创建一个.gitignore文件。它的作用是,无论这个文件中有什么内容(或者我们在.gitignore文件中写入的文件的通配符),它都会阻止所有这些内容提交,并将它们发送到存储库服务器。

它如何帮助?它在许多重要的用例中都有帮助。假设我们不想将任何本地配置文件放到生产服务器上。在这种情况下,.gitignore文件可以成为救世主,也可以在.py文件生成它们的.pyc文件时使用,这些文件在运行时被编译。我们不需要在服务器上存储这些二进制文件,因为它们每次代码更改时都会单独生成。

在 Linux 命令行中,只需在项目目录的根文件夹中键入$vim .gitignore命令,然后写入*.pyc。然后,以通常的方式保存并退出。

现在,如果我们执行$git status命令,我们将看不到任何扩展名为.pyc的文件,这意味着 Git 已经忽略了以.pyc结尾的文件进行跟踪。

$git status命令的结果如下:

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: mytweets/settings.py
modified: mytweets/urls.py

Untracked files:
(use "git add <file>..." to include in what will be committed)

.gitignore
static/
templates/
tweets/

这是相当清楚的,正如应该的。我们之前已经提交了settings.pyurls.py文件,现在我们对它们进行了一些更改,而提到的未跟踪文件甚至没有被添加到 Git 进行跟踪。

我们可以使用git add .命令将所有更改添加到目录中。但是,为了避免将任何不需要的文件推送到 Git 跟踪,建议在开发的高级阶段逐个添加文件。对于当前的情况,一次性添加文件是可以的。要将所需的文件添加到我们的项目中,请使用以下命令:

$git add .

输出将如下所示:

On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: .gitignore
modified: mytweets/settings.py
modified: mytweets/urls.py
new file: static/css/bootstrap-theme.css
new file: static/css/bootstrap-theme.css.map
new file: static/css/bootstrap-theme.min.css
new file: static/css/bootstrap.css
new file: static/css/bootstrap.css.map
new file: static/css/bootstrap.min.css
new file: static/fonts/glyphicons-halflings-regular.eot
new file: static/fonts/glyphicons-halflings-regular.svg
new file: static/fonts/glyphicons-halflings-regular.ttf
new file: static/fonts/glyphicons-halflings-regular.woff
new file: static/js/bootstrap.js
new file: static/js/bootstrap.min.js
new file: templates/base.html
new file: tweets/__init__.py
new file: tweets/admin.py
new file: tweets/models.py
new file: tweets/tests.py
new file: tweets/views.py

提交更改并附上适当的消息,比如“添加基本的引导模板”:

$git commit -m "basic bootstap template added"

输出将如下所示:

[master 195230b] basic bootstap template added
21 files changed, 9062 insertions(+), 1 deletion(-)
create mode 100644 .gitignore
create mode 100644 static/css/bootstrap-theme.css
create mode 100644 static/css/bootstrap-theme.css.map
create mode 100644 static/css/bootstrap-theme.min.css
create mode 100644 static/css/bootstrap.css
create mode 100644 static/css/bootstrap.css.map
create mode 100644 static/css/bootstrap.min.css
create mode 100644 static/fonts/glyphicons-halflings-regular.eot
create mode 100644 static/fonts/glyphicons-halflings-regular.svg
create mode 100644 static/fonts/glyphicons-halflings-regular.ttf
create mode 100644 static/fonts/glyphicons-halflings-regular.woff
create mode 100644 static/js/bootstrap.js
create mode 100644 static/js/bootstrap.min.js
create mode 100644 templates/base.html
create mode 100644 tweets/__init__.py
create mode 100644 tweets/admin.py
create mode 100644 tweets/models.py
create mode 100644 tweets/tests.py
create mode 100644 tweets/views.py

将所有内容放在一起 - 生成用户页面

到目前为止,我们已经涵盖了很多材料,比如介绍了视图和模板的概念。在最后一节中,我们将编写另一个视图,并利用到目前为止学到的所有信息。这个视图将显示属于某个用户的所有推文的列表。

熟悉 Django 模型

模型是标准的 Python 类,具有一些附加功能。它们是django.db.models.Model的子类。在后台,对象关系映射器ORM)与这些类及其对象绑定在一起。这使它们与底层数据库进行通信。ORM 是 Django 的一个重要特性,没有它,我们将不得不编写自己的查询(如果是 MySQL,则为 SQL)来访问数据库内容。模型的每个属性都由数据库字段表示。没有字段,模型将只是一个空容器,毫无意义。

以下是 Django 的模型属性及其预期用途的解释。完整的字段列表可以在docs.djangoproject.com/en/dev/ref/models/fields/的标准文档中找到。

以下是这些类型的部分表:

字段类型 描述
IntegerField 一个整数
TextField 一个大文本字段
DateTimeField 一个日期和时间字段
EmailField 一个最大长度为 75 个字符的电子邮件字段
URLField 一个最大长度为 200 个字符的 URL 字段
FileField 一个文件上传字段

每个模型字段都带有一组特定于字段的参数。例如,如果我们想要一个字段是CharField字段,我们必须将其max_length参数作为其参数传递,该参数映射到数据库中varchar的字段大小。

以下是可以应用于所有字段类型的参数(它们是可选的):

  • null:默认情况下,它设置为false。当设置为true时,允许将null的关联字段的值存储在数据库中。

  • blank:默认情况下,它设置为false。当设置为true时,允许将blank的关联字段的值存储在数据库中。

注意

nullblank参数之间的区别在于,null参数主要与数据库相关,而blank参数用于验证字段。换句话说,如果属性设置为false,则属性的空值(blank)将不会被保存。

  • choices:这可以是一个列表或元组,并且必须是可迭代的。如果这是一个元组,第一个元素是将存储到数据库中的值,第二个值用于在小部件形式或ModelChoiceField中显示。

例如:

USER_ROLE = ( 
('U', 'USER'), 
('S', 'STAFF'), 
('A', 'ADMIN')
)
user_role = models.CharField(max_length=1, choices=USER_ROLE)
  • default:每次实例化类的对象时分配给属性的值。

  • help_text:以小部件形式显示的帮助文本。

  • primary_key:如果设置为True,则该字段将成为模型的主键。如果模型中没有主键,Django 将创建一个整数字段并将其标记为主键。

模型中的关系

有三种主要类型的关系:多对一,多对多和一对一。

多对一关系

在 Django 中,django.db.models.ForeignKey参数用于将一个模型定义为另一个模型属性的外键,从而产生多对多的关系。

它被用作模型类的任何其他属性,包括它所在的类。例如,如果学生在特定学校学习,那么学校有很多学生,但学生只去一个学校,这是一个多对一的关系。让我们看一下以下代码片段:

from django.db import models
class School(models.Model):
# ...
ass
class Student(models.Model):
school = models.ForeignKey(School)
# …

一对一关系

一对一关系与多对一关系非常相似。唯一的区别是,反向映射在一对一关系的情况下会导致单个对象,而不是多对一关系。

例如:

class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()

在前面的示例中,EntryDetail()类有一个名为entry的属性,它与Entry模型一对一映射。这意味着每个Entry对象都映射到EntryDetail模型。

多对多关系

正如名称本身所示,具有多对多关系的模型属性提供对其指向的两个模型的访问(例如向后的一对多关系)。属性命名是这两种关系之间唯一的重要区别。

如果我们通过以下示例来说明,这将更清楚:

class Product(models.Model):
name = models.CharField(_(u"Name"), max_length=50)
class Category(models.Model):
name = models.CharField(_(u"Name"), max_length=50)
products = models.ManyToManyField("Product", blank=True, null=True)

有了属性和主要关系的想法,我们现在可以直接创建我们的项目模型,在接下来的部分中我们将很快做到这一点。

如果我们要为应用程序设计模型,如果模型太多,我们应该拆分应用程序。如果我们的应用程序中有超过大约 15 个模型,我们应该考虑如何将我们的应用程序拆分成更小的应用程序。这是因为,对于现有的 15 个模型应用程序,我们可能正在做太多事情。这与 Django 的哲学不符,即应用程序应该只做一件事,并且做得正确

模型-设计初始数据库模式

回到我们的项目,我们在初始阶段将需要两个模型:user模型和tweet模型。user模型将用于存储在我们的项目中拥有帐户的用户的基本用户详细信息。

接下来是tweet模型,它将存储与推文相关的数据,例如推文文本,创建该推文的用户,以及推文发布的时间戳等其他重要细节。

要列出用户的推文,最好是创建一个专门针对项目中所有用户的用户应用程序。我们的用户模型将通过扩展 Django 的AbstractBaseUser用户模型类来创建。

注意

永远不建议更改 Django 源树中的实际user类和/或复制和修改auth模块。

这将是使用框架进行 Web 开发的第一个应用程序,而不是自己编写整个身份验证,这对所有 Web 开发场景都是非常普遍的。Django 带有预定义的库,因此我们不必重新发明轮子。它同时提供了认证和授权,并称为认证系统。

Django 的用户对象

Django 1.5 附带了一个可配置的用户模型,这是在应用程序中存储特定于用户的数据的更简单的方法。

我们将创建一个用户应用程序,然后将 Django 的默认用户模型导入其中:

$python manage.py startapp user_profile

我们将根据当前项目的需要扩展 Django 用户模型,通过创建一个继承自AbstractBaseUser类的自定义User()类。因此,我们的models.py文件将如下所示:

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):

Custom user class.

现在我们已经为项目创建了自定义的user类,我们可以向这个user类添加所有我们希望在用户模型中的基本属性。

现在models.py看起来是这样的:

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):

Custom user class.

username = models.CharField('username', max_length=10, unique=True, db_index=True)
email = models.EmailField('email address', unique=True)
joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)

在上述代码片段中,自定义用户模型email字段具有一个设置为Trueunique属性。这意味着用户只能使用给定的电子邮件地址注册一次,验证可以在注册页面上完成。您还将在username属性中看到一个db_index选项,其值为True,这将在username属性上为用户表建立索引。

joineddateTimeField参数,当创建新用户配置文件时会自动填充;当创建新用户帐户时,默认情况下is_active字段设置为True,同时is_admin字段初始化为False

还需要一个字段,使其几乎与默认的 Django 用户模型相同,即username字段。

models.py文件中添加USERNAME_FIELD字段如下:

USERNAME_FIELD = 'username' 
def __unicode__(self):
return self.username

USERNAME_FIELD也作为 Django 中用户模型的唯一标识符。我们已经将我们的username参数映射到 Django 的username字段。这个字段在定义时必须是唯一的(unique=True),而我们的username字段已经是唯一的。

__unicode__()方法也被添加为显示用户模型对象的人类可读表示的定义。

因此,最终的models.py文件将如下所示:

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):
"""
Custom user class.
"""
username = models.CharField( 'username', max_length=10, unique=True, db_index=True)
email = models.EmailField('email address', unique=True)
joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)

USERNAME_FIELD = 'username'
def __unicode__(self):
return self.username

现在,在定义了我们的用户模型之后,我们可以继续设计推文模型。这是我们创建的同一个应用程序,用于查看基本的基于类的视图。我们将向其models.py文件添加内容,如下所示:

from django.db import models
from user_profile import User
class Tweet(models.Model):
"""
Tweet model
"""
user = models.ForeignKey(User)
text = models.CharField(max_length=160)
created_date = models.DateTimeField(auto_now_add=True)
country = models.CharField(max_length=30)
is_active = models.BooleanField(default=True)

推文模型的设计尽可能简单,attribute参数是对我们已经创建的User对象的外键。text属性是推文内容,它将主要由纯文本组成。created_Date属性是在未初始化tweet对象时自动添加到数据库中的,它存储了实际发布推文的国家名称。在大多数情况下,它将与用户的国家相同。is_active标志用于表示推文的当前状态,即它是否处于活动状态并且可以显示,或者已被用户删除。

我们需要在数据库中为我们刚刚创建的两个模型user_profiletweet创建表。我们将不得不更新项目的settings.py文件中的INSTALLED_APPS变量,以告诉 Django 在 Django 项目中包括这两个应用程序。

我们更新后的INSTALLED_APPS变量将如下所示:

INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'user_profile',
'tweet'
)

您可以看到我们添加的最后两个条目以添加我们的模型。

现在,为了为我们的项目创建数据库表,我们将在终端中从根项目文件夹运行以下命令:

$python manage.py syncdb

输出将如下所示:

Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table user_profile_user
Creating table tweet_tweet

您刚刚安装了 Django 的 auth 系统,这意味着您没有定义任何超级用户。您可以在终端上看到以下内容:

Would you like to create one now? (yes/no): yes
Username (leave blank to use 'ratan'):
Email address: mail@ratankumar.org
Password: XXXX
Password (again): XXXX
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

因此,我们的数据库已填充了一个表。我们的项目中将出现一个名为db.sqlite3的数据库文件。

与 Django 1.6 一样,默认情况下会出现管理员面板。我们的模型要在 Django 的管理面板中可用,只需为两个应用程序的模型名称添加admin.site.register参数作为参数。

因此,在admin.py文件中添加admin.site.register(parameter)mytweetsuser_profile文件下将如下所示:

  • tweet应用程序的admin.py文件如下所示:
from django.contrib import admin
from models import Tweet

admin.site.register(Tweet)
  • user_profile应用程序的admin.py文件如下所示:
from django.contrib import admin
from models import User
admin.site.register(User)

使用以下命令启动服务器:

$python manage.py runserver

然后访问 URLhttp://127.0.0.1:8000/admin;它会要求登录信息。您可能还记得,我们在运行$python manage.py syncdb命令时创建了默认用户;使用相同的用户名和密码。

成功登录后,管理面板看起来像以下截图:

Django 的用户对象

让我们在管理面板中玩耍,并创建一个我们将在首页视图中使用的usertweet对象。要向项目添加新用户,只需点击用户模型框前面的添加按钮,如下截图所示:

Django 的用户对象

然后填写详细信息并保存。您将看到如下截图中显示的"用户创建成功"消息:

Django 的用户对象

我们将按照类似的流程创建一条推文。首先返回到http://127.0.0.1:8000/admin/。然后,在推文框前面点击添加按钮。

Django 的用户对象

通过填写框并从下拉菜单中选择用户来撰写新推文。由于我们已将用户映射到用户对象,因此此用户列表已经填充。随着我们不断添加用户,下拉菜单将填充所有用户对象。

Django 的用户对象

最后,在撰写推文后,点击保存按钮。您将看到以下截图中显示的相同屏幕:

Django 的用户对象

如果您仔细观察,管理员列表页面会显示每条推文都是一个tweet对象,这不太友好。实际上,对于 Django 管理视图中或任何其他地方显示的所有模型基础表示,都适用相同的规则。

在我们的项目的admin.py文件中添加以下代码片段:

def __unicode__(self): 
return self.text

我们的管理视图现在将显示确切的文本,而不是写入推文对象。

Django 的用户对象

创建 URL

我们项目中的每个用户都将具有以下格式的唯一 URL 的个人资料:http://127.0.0.1:8000/user/<username>。这里,username变量是我们想要查看推文的所有者。这个 URL 与我们之前添加的第一个 URL 不同,因为它包含一个动态部分,所以我们必须利用正则表达式的能力来表示这个 URL。打开urls.py文件并编辑它,使 URL 表格如下所示:

url(r'^user/(\w+)/$', Profile.as_view()), urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from tweet.views import Index,Profile
admin.autodiscover()

urlpatterns = patterns('',
url(r'^$', Index.as_view()),
url(r'^user/(\w+)/$', Profile.as_view()),
url(r'^admin/', include(admin.site.urls)),
)

这里的模式看起来比第一个更复杂。注释\w表示字母数字字符或下划线。其后的+符号会导致正则表达式匹配前面的内容的一个或多个重复。因此,实际上,\w+表示由字母数字字符和可能的下划线组成的任何字符串。我们用括号括起了正则表达式的这部分。这将导致 Django 捕获与这部分匹配的字符串并将其传递给视图。

在我们看到视图生效之前,还有一件事需要解释。如果您以前没有使用过正则表达式,我们使用的正则表达式看起来可能有点奇怪。这是一个包含两个字符^$的原始字符串。注释r''是 Python 定义原始字符串的语法。如果 Python 遇到这样的原始字符串,反斜杠和其他转义序列将保留在字符串中,而不会以任何方式解释。在这种语法中,反斜杠保留在字符串中而不会改变,转义序列不会被解释。这在处理正则表达式时非常有用,因为它们经常包含反斜杠。

在正则表达式中,^表示字符串的开头,$表示字符串的结尾。因此,^$基本上表示一个不包含任何内容的字符串,即空字符串。鉴于我们正在编写主页的视图,页面的 URL 是根 URL,确实应该是空的。

re模块的 Python 文档详细介绍了正则表达式。如果您想对正则表达式进行彻底的处理,我建议阅读它。您可以在docs.python.org/lib/module-re.html上找到在线文档。以下是一个总结正则表达式语法的表格,供那些想要快速复习的人使用:

符号/表达式 匹配的字符串
. (Dot) 任何字符
^ (Caret) 字符串的开头
` 符号/表达式
--- ---
. (Dot) 任何字符
^ (Caret) 字符串的开头
字符串的结尾
* 0 次或多次重复
+ 1 次或多次重复
? 0 或 1 次重复
&#124; A | B 表示 A 或 B
[a-z] 任何小写字符
\w 任何字母数字字符或 _
\d 任何数字

我们现在将在我们的推文应用程序的view.py文件中创建一个带有GET函数的Profile()类。这里需要学习的重要事情是get()函数如何处理通过 URL 传递的动态参数,即username变量。

我们的推文应用程序的view.py将如下所示:

class Profile(View):
"""User Profile page reachable from /user/<username> URL"""
def get(self, request, username):
params = dict()()()
user = User.objects.get(username=username)
tweets = Tweet.objects.filter(user=user)
params["tweets"] = tweets
params["user"] = user
return render(request, 'profile.html', params)

模板 - 为主页创建模板

我们几乎完成了项目的模型创建。现在我们将继续创建视图页面。

我们要创建的第一个页面是基本页面,它将列出用户发布的所有推文。这可以是一个所谓的公共个人资料页面,可以在没有任何身份验证的情况下访问。

正如你可能已经注意到的,我们在views.py文件的Profile类中使用了profile.html文件,它属于我们的推文应用程序。

我们项目的views.py文件将如下所示:

class Profile(View):
"""User Profile page reachable from /user/<username> URL"""
def get(self, request, username):
params = dict()
user = User.objects.get(username=username)
tweets = Tweet.objects.filter(user=user)
params["tweets"] = tweets
params["user"] = user
return render(request, 'profile.html', params)

我们将使用已经在我们的base.html文件中导入的 Bootstrap 框架来设计Profile.html文件。

我们将首先重构我们为应用程序创建的base.html文件。现在这个base.html文件将被用作我们项目的模板或主题。我们将在整个项目中导入此文件,这将导致项目中的用户界面保持一致。

我们将从我们的base.html文件中删除我们放在块内容中的div标签。

我们还需要 jQuery,这是一个用于完全实现 bootstrap 功能的 JavaScript 库。可以从jquery.com/download/下载。对于我们当前的项目,我们将在生产就绪阶段下载最新版本的 jQuery。我们将在 bootstrap 的 JavaScript 导入之前添加它。

现在base.html文件应该是这样的:

{% load staticfiles %}
<html>
<head>
<link href="{% static 'bootstrap/css/bootstrap.min.css' %}"
rel="stylesheet" media="screen">
</head>

<body>
{% block content %}
{% endblock %}

<script src="img/jquery-2.1.1.min.js' %}"></script>
<script src="img/bootstrap.min.js' %}"></script>
</body>
</html>

在这种情况下,块如下所示:

{% block content %}
{% endblock %}

这意味着,无论我们要扩展哪个模板base.html文件,当前在profile.html文件中,profile.html文件的内容将在这些块引用之间呈现。为了更好地理解这一点,考虑这样做:每个页面都有页眉(在某些情况下是导航栏)和页脚,页面内容根据视图而变化。通过前面的模板,我们通常需要在块内容之前放置页眉代码,并在块内容下方放置页脚内容。

现在使用页眉要容易得多,因为我们有前端框架的优势。我们将首先选择项目的布局。为简单起见,我们将整个页面分为三个部分。第一个将是页眉,随着我们在整个项目中导航,它将是恒定的。同样的情况也适用于页面底部,即我们的页脚。

模板-为主页创建模板

为了实现前面的布局,我们的 bootstrap 代码将以这种方式构建:我们将使用 bootstrap 的navbar作为页眉部分以及页脚部分。然后我们将放置容器div标签。我们的base.html文件的更新代码将更改为以下内容:

{% load staticfiles %}
<html>
<head>
<link href="{% static 'css/bootstrap.min.css' %}"
rel="stylesheet" media="screen">
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<a class="navbar-brand" href="#">MyTweets</a>
<p class="navbar-text navbar-right">User Profile Page</p>
</nav>
<div class="container">
{% block content %}

{% endblock %}
</div>
<nav class="navbar navbar-default navbar-fixed-bottom" role="navigation">
<p class="navbar-text navbar-right">Footer </p>

</nav>
<script src="img/bootstrap.min.js' %}"></script>
</body>
</html>

navbar参数将在主体中启动,但在容器之前,以便它可以包裹整个容器。我们使用 Django 块内容来呈现我们将在扩展模板中定义的行,在这种情况下是profile.html文件。页脚部分最后出现,这是在endblock语句之后。

这将呈现以下页面:

模板-为主页创建模板

提示

请注意,如果您没有包含静态文件,请在您的settings.py文件中用以下内容替换STATICFILES_DIRS变量:

STATICFILES_DIRS = (
BASE_DIR + '/static/',
)

个人资料页面的设计如下:

模板-为主页创建模板

这可以很容易地再次设计,借助名为well的 bootstrap 组件。wellwellbox组件与元素一起使用,以产生内嵌效果。profile.html文件将只扩展base.html文件,并且只包含行和进一步的元素。

我们项目的profile.html文件如下所示:

{% extends "base.html" %}
{% block content %}
<div class="row clearfix">
<div class="col-md-12 column">
{% for tweet in tweets %}
<div class="well">
<span>{{ tweet.text }}</span>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

这将显示我们通过 URL 参数传递的用户的推文。我们采用的示例是用户ratancs,我们在初始设置期间创建的用户。您可以在以下截图中看到他们的推文:

模板-为主页创建模板

总结

我们学习了与我们的 Django 项目相关的基本术语,我们需要设置项目的基本模板结构,以及如何为我们的类似推文的应用程序设置 bootstrap。我们还看到了 MVC 在这里的工作方式以及在创建主页时 URL 和视图的作用。

然后,我们介绍了基于类的视图来生成用户页面。我们看到了模型在 Django 中的工作方式,以及如何为项目设计数据库模式。我们还学会了构建用户注册页面、帐户管理页面和主页模板。

我们将学习设计构建标签模型的算法,以及在接下来的章节中如何在您的帖子中使用标签的机制。

第五章:引入标签

标签是 Web 2.0 应用程序中最突出的功能之一。标签是与信息(如文章、图片或链接)相关联的关键词。标记是将标签分配给内容的过程。通常由作者或用户完成,并允许用户定义的内容分类。

我们还将在我们的项目中使用标签,并将其称为hashtags。标签在 Web 应用程序中变得非常流行,因为它们使用户能够轻松分类、查看和共享内容。如果您不熟悉标签,可以通过访问 Twitter、Facebook 或 Google Plus 等社交网站的示例来了解。在这些网站上,标签被固定到每个状态或对话中,以帮助我们找到热门话题。由于我们将构建一个微型博客网站,标签将帮助我们对用户之间的对话进行分类。

为了将标签引入我们的系统,我们需要一种机制,使用户能够将带有标签的 tweet 提交到数据库中。稍后,我们还需要一种浏览特定标签下分类的 tweet 的方法。

在本章中,您将学习以下内容:

  • 设计标签数据模型

  • 构建一个从 tweet 表单中分离出标签的算法

  • 为特定标签下的 tweet 列表创建页面

  • 构建标签云

标签数据模型

标签需要存储在数据库中并与 tweet 关联。因此,引入标签到我们的项目的第一步是为标签创建一个数据模型。一个标签对象只会保存一个数据,一个代表标签的字符串。此外,我们需要维护与特定 tweet 关联的标签列表。

你可能还记得第四章中,构建类似 Twitter 的应用程序,我们使用外键将 tweet 与用户关联起来,并将其称为一对多关系。然而,标签和 tweet 之间的关系不是一对多,因为一个标签可以与多个 tweet 关联,一个 tweet 也可以与多个标签关联。这被称为多对多关系,并且在 Django 模型中使用models.ManyToManyField参数表示。

到目前为止,您应该很清楚数据模型放在mytweet | models.py文件中。因此,打开文件并将以下HashTag类添加到其中:

class HashTag(models.Model):
  """
  HashTag model
  """
  name = models.CharField(max_length=64, unique=True)
  tweet = models.ManyToManyField(Tweet)
  def __unicode__(self):
    return self.name

相当简单,不是吗?我们只是为标签定义了一个数据模型。该模型在其ManyToManyField参数中保存了标签名称及其 tweet。当您完成输入代码后,不要忘记运行以下命令,以便在数据库中为模型创建一个表:

$ python manage.py syncdb

输出:

 Creating tables ...
 Creating table tweet_hashtag_tweet
 Creating table tweet_hashtag
 Installing custom SQL ...
 Installing indexes ...
 Installed 0 object(s) from 0 fixture(s)

现在,要查看 Django 如何创建和实现所有关系的详细 SQL 查询,以及如何为它们创建表,我们只需使用manage.py中的模型名称发出sql命令。它将显示要运行以创建对象实例的 SQL 查询。熟悉 SQL 的人都知道,多对多关系通常是通过创建连接两个相关表的第三个表来实现的。现在,让我们看看 Django 如何实现这种类型的关系。在终端中,发出以下命令:

$ python manage.py sql tweet

输出:

 BEGIN;
 CREATE TABLE "tweet_tweet" (
 "id" integer NOT NULL PRIMARY KEY,
 "user_id" integer NOT NULL REFERENCES "user_profile_user" ("id"),
 "text" varchar(160) NOT NULL,
 "created_date" datetime NOT NULL,
 "country" varchar(30) NOT NULL,
 "is_active" bool NOT NULL
 )
 ;
 CREATE TABLE "tweet_hashtag_tweet" (
 "id" integer NOT NULL PRIMARY KEY,
 "hashtag_id" integer NOT NULL,
 "tweet_id" integer NOT NULL REFERENCES "tweet_tweet" ("id"),
 UNIQUE ("hashtag_id", "tweet_id")
 )
 ;
 CREATE TABLE "tweet_hashtag" (
 "id" integer NOT NULL PRIMARY KEY,
 "name" varchar(64) NOT NULL UNIQUE
 )
 ;
 COMMIT;

输出可能会因您的数据库引擎而略有不同。事实上,Django 会自动创建一个名为tweet_hashtag_tweet的额外表来维护多对多关系。

在 Django 的模型 API 中定义多对多关系时,值得注意的是,models.ManyToMany字段可以放置在两个相关模型中的任何一个。我们本可以将这个字段放在 tweet 模型中而不是 hashtag;因为我们后来创建了 hashtag 模型,所以我们把models.ManyToMany字段放在了它里面。

为了测试目的,我们将转到管理面板并创建带有标签的推文,就像我们为用户和推文创建一样。但首先,我们需要在admin.py文件中为管理面板注册标签。

修改后的admin.py文件将如下所示:

  from django.contrib import admin
  from models import Tweet,Hashtag
  # Register your models here.
  admin.site.register(Tweet)
  admin.site.register(HashTag)

现在我们可以转到/administration URL 的管理面板。

在为推文创建标签之前,我们需要创建一个带有标签的推文。稍后,我们将编写一个程序,它将解析推文并自动创建与之关联的标签实例。

参考我们在第四章中展示的创建推文的演示图,并创建一条带有以下文本的推文:

Hello, #Django! you are awesome.

使用我们之前使用的相同用户ratancs,然后转到标签模型并创建标签#Django并将其与我们创建的推文关联起来。这将让你了解我们如何将标签分配给推文。

让我们创建一个合适的推文提交表单,它将要求用户将推文写入输入框。它将创建与推文相关的所有标签,并保存推文。

查看我们创建的用户个人资料页面。在页面的顶部中央,将有一个已与用户关联的输入框;因此,当他写一条推文并点击提交按钮时,推文将被保存与他的 ID 关联。

现在,访问这个 URL:http://localhost:8000/user/ratancs/。你会看到我们之前创建的推文。

标签数据模型

我们将返回到profile.html代码,并追加一个文本区域和一个提交按钮,为用户发布一条推文。设计将与我们选择显示推文的方式相同,也就是说,我们将使用Twitter bootstrap的相同的 well box。

我们的profile.html文件模板如下:

  {% extends "base.html" %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-12 column">
      {% for tweet in tweets %}
      <div class="well">
        <span>{{ tweet.text }}</span>
      </div>
      {% endfor %}
    </div>
  </div>
  {% endblock %}

这个{%for ...}块用于表示多条推文,每条推文都在下面,因为它们有div标签。

现在我们将在{% for ...}块的上方创建一个div标签,并添加我们的推文提交表单。

在我们编写表单之前,让我们了解一下 Django 表单以及它们的使用方法。

Django 表单

创建、验证和处理表单是一项非常常见的任务。Web 应用程序通过 Web 表单接收输入并收集用户数据。因此,自然地,Django 自带了处理这些任务的库。你所要做的就是导入这个库并开始编写你的表单:

from django import forms

Django 表单库处理三个常见任务:

  • HTML 表单生成

  • 用户输入的服务器端验证

  • 在输入错误的情况下重新显示 HTML 表单

这个库的工作方式类似于 Django 的数据模型的工作方式。你首先定义一个代表你的表单的类。这个类必须派生自forms.Form基类。这个类中的属性代表表单字段。forms包提供了许多字段类型。

当你从派生自forms.Form基类的类创建对象时,你可以使用各种方法与它交互。有用于生成 HTML 代码的方法,用于访问输入数据的方法,以及用于验证表单的方法。

在下一节中,我们将通过创建一个推文发布表单来学习表单库。

设计推文发布表单

让我们从创建我们的第一个 Django 表单开始。在推文应用程序文件夹中创建一个新文件,命名为forms.py。然后,在代码编辑器中打开文件并输入以下代码:

  from django import forms
  class TweetForm(forms.Form):
    text = forms.CharField(widget=forms.Textarea(attrs={'rows': 1, 'cols': 85}), max_length=160)
    country = forms.CharField(widget=forms.HiddenInput())

在检查了代码之后,你会注意到我们定义这个类的方式类似于我们定义模型类的方式。我们从forms.Form派生了TweetForm类。所有表单类都需要继承自这个类。接下来,我们定义这个表单包含的字段:

  text = forms.CharField(widget=forms.Textarea(attrs={'rows': 1, 'cols': 85}), max_length=160)

表单包含一个文本字段,它将具有文本区域的 HTML 标签,一个用于行和列的附加属性,以及一个输入的最大大小限制,与 tweet 的最大长度相同。

  country = forms.CharField(widget=forms.HiddenInput())

请注意,表单还包含一个名为country的隐藏字段,它将是一个 char 字段。

forms包中有许多字段类型。以下是一些参数,可以传递给任何字段类型的构造函数。一些专门的字段类型除了这些参数之外还可以接受其他参数。

  • label:生成 HTML 代码时字段的标签。

  • required:用户是否必须输入值。默认设置为True。要更改它,将required=False传递给构造函数。

  • widget:这个参数让你控制字段在 HTML 中的呈现方式。我们刚刚用它来使密码的CharField参数成为密码输入字段。

  • help_text:在表单呈现时,字段的描述将被显示。

以下是常用字段类型的表格:

| 字段类型:描述 |
| --- | --- |
| CharField:返回一个字符串。 |
| IntegerField:返回一个整数。 |
| DateField:返回 Python 的datetime.date对象。 |
| DateTimeField:返回 Python 的datetime.datetime对象。 |
| EmailField:返回一个有效的电子邮件地址字符串。 |
| URLField:返回一个有效的 URL 字符串。 |

以下是可用的表单小部件的部分列表:

小部件类型 描述
PasswordInput:密码文本字段。
HiddenInput:隐藏输入字段。
Textarea:允许在多行上输入文本的文本区域。
FileInput:文件上传字段。

现在,我们需要根据form.py文件修改profile.html文件。更新profile.html文件如下:

  {% extends "base.html" %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-12 column">
      <form method="post" action="post/">{% csrf_token %}
        <div class="col-md-8 col-md-offset-2 fieldWrapper">
          {{ form.text.errors }}
          {{ form.text }}
        </div>
        {{ form.country.as_hidden }}
        <div>
          <input type="submit" value="post">
        </div>
      </form>
    </div>
    <h3>&nbsp;</h3>
    <div class="col-md-12 column">
      {% for tweet in tweets %}
      <div class="well">
        <span>{{ tweet.text }}</span>
      </div>
      {% endfor %}
    </div>
  </div>
  {% endblock %}

通过一个简单的表单实现了发布 tweet,即<form method="post" action="post/">{% csrf_token %}。表单将被提交的方法是"post",发布 tweet 表单的相对 URL 将是post/

  {% csrf_token %}

这段代码生成了 CSRF 令牌,实际上解决了一个安全问题;它保护这个post URL 免受另一个服务器的攻击;关于这一点将在本章的后面部分进行解释。

我们在 tweet <div>之前添加了一个div标签,这个div标签包含一个表单,当单击发布按钮时将保存 tweet。

<div class="col-md-8 col-md-offset-2 fieldWrapper">
  {{ form.text.errors }}
  {{ form.text }}
</div>

fieldWrapper类在div标签中被 Django 的表单库使用,用于呈现我们在表单类中提到的文本的 HTML 标签(即文本区域),随后是表单呈现的任何错误情况。

这将呈现如下截图所示的表单:

设计推文发布表单

现在,我们需要做两件事来使这个表单工作:

  1. 我们必须在控制器中定义一个方法,该方法将接受这个表单提交请求,并将 tweet 数据保存到我们的 tweet 模型类对象中。

  2. 我们必须定义一个 URL 模式,以便将该表单提交为 tweet 内容。

为了处理请求,我们将添加一个新的类,该类将接受来自表单的 tweet。我们将把这个类命名为PostTweet。这个类被添加在tweet/view.py中,有一个导入依赖from tweet.forms import TweetForm

  class PostTweet(View):
    """Tweet Post form available on page /user/<username> URL"""
    def post(self, request, username):
      form = TweetForm(self.request.POST)
      if form.is_valid():
        user = User.objects.get(username=username)
        tweet = Tweet(text=form.cleaned_data['text'],
        user=user,
        country=form.cleaned_data['country'])
        tweet.save()
        words = form.cleaned_data['text'].split(" ")
        for word in words:
        if word[0] == "#":
          hashtag, created = HashTag.objects.get_or_create(name=word[1:])
          hashtag.tweet.add(tweet)
        return HttpResponseRedirect('/user/'+username)

我们只需要定义 post 方法,因为我们只需要这个类来接受数据。这里的逻辑非常清楚;如果表单有效,那么数据才会被持久化。重定向总是发生。代码还执行了另一个特殊任务;即从 tweet 中分离出所有的 hashtags。这与分割 tweet 中的所有单词的方式类似,如果单词以#(井号)开头,它将创建该单词的 hashtag(在这里考虑一个正则表达式)。对于第二部分,我们将在我们的urls.py文件中添加一个条目,如下所示:

from django.conf.urls import patterns, include, url
from django.contrib import admin
from tweet.views import Index, Profile, PostTweet

admin.autodiscover()

urlpatterns = patterns('',
  url(r'^$', Index.as_view()),
  url(r'^user/(\w+)/$', Profile.as_view()),
  url(r'^admin/', include(admin.site.urls)),
  url(r'^user/(\w+)/post/$', PostTweet.as_view())
)

如果你仔细看最后一行,我们有:

  url(r'^user/(\w+)/post/$', PostTweet.as_view())

这意味着所有形式为/user/<username>/post的请求将由PostTweet渲染。

通过这个,我们已经制作了一个简单的 Django 表单,用户可以从他的 Twitter 页面发布推文,如下图所示:

设计推文发布表单

一旦推文发布,页面将显示所有推文,如下图所示:

设计推文发布表单

创建标签页面

接下来,我们将创建一个类似于 Twitter 标签列表的页面。对于这个任务,我们几乎会遵循我们为用户个人资料所遵循的相同架构。让我们从为标签页面添加 URL 条目开始。打开urls.py文件并插入以下条目(最好是在用户页面条目下方,以保持表格有序):

  url(r'^hashTag/(\w+)/$', HashTagCloud.as_view()),

这个正则表达式捕获的部分与用户页面的相同。我们只允许标签中包含字母数字字符。

我们将在控制器中定义hashtag类如下:

  class HashTagCloud(View):
    """Hash Tag  page reachable from /hastag/<hashtag> URL"""
    def get(self, request, hashtag):
      params = dict()
      hashtag = HashTag.objects.get(name=hashtag)
      params["tweets"] = hashtag.tweet
      return render(request, 'hashtag.html', params)

我们将使用的 HTML 模板页面几乎与个人资料页面相同,除了我们用于发布推文的表单部分。

我们需要使用以下代码创建hashtag.html文件:

  {% extends "base.html" %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-12 column">
      {% for tweet in tweets.all %}
      <div class="well">
        <span>{{ tweet.text }}</span>
      </div>
      {% endfor %}
    </div>
  </div>
  {% endblock %}

这将列出所有通过 URL 传递的标签的推文。

总结

我们已经学会了如何设计标签数据模型以及从推文中分离标签所需的算法。然后,我们创建了用于列出特定标签下推文的页面。我们看到了如何构建带有标签的推文的代码片段,就像许多博客网站中看到的标签云一样。

在下一章中,我们将看到如何使用 Django 和 AJAX 增强 UI 体验。

第六章:使用 AJAX 增强用户界面

AJAX的到来是 Web 2.0 历史上的一个重要里程碑。AJAX 是一组技术,使开发人员能够构建交互式、功能丰富的 Web 应用程序。在 AJAX 本身出现之前,这些技术多年前就已经存在。然而,AJAX 的出现代表了 Web 从需要在数据交换时刷新的静态页面向动态、响应迅速和交互式用户界面的转变。

由于我们的项目是一个 Web 2.0 应用程序,它应该更加注重用户体验。我们的应用程序的成功取决于用户在上面发布和分享内容。因此,我们的应用程序的用户界面是我们的主要关注点之一。本章将通过引入 AJAX 功能来改进我们的应用程序界面,使其更加用户友好和交互性。

在本章中,您将学习以下主题:

  • AJAX 及其优势

  • 在 Django 中使用 AJAX

  • 如何使用开源 jQuery 框架

  • 实现推文的搜索

  • 在不加载单独页面的情况下编辑推文

  • 提交推文时自动完成标签

AJAX 及其优势

AJAX 代表异步 JavaScript 和 XML,包括以下技术:

  • 用于结构化和样式信息的 HTML 和 CSS

  • JavaScript 用于动态访问和操作信息

  • 一个由现代浏览器提供的对象,用于在不重新加载当前网页的情况下与服务器交换数据

  • 在客户端和服务器之间传输数据的格式

有时会使用 XML,但它可以是 HTML、纯文本或基于 JavaScript 的格式 JSON。

AJAX 技术使您能够在不重新加载整个页面的情况下在客户端和服务器之间交换数据。通过使用 AJAX,Web 开发人员能够增加 Web 页面的交互性和可用性。

在正确的地方实现 AJAX 时,它提供了以下优势:

  • 更好的用户体验:通过 AJAX,用户可以在不刷新页面的情况下完成很多操作,这使得 Web 应用程序更接近常规桌面应用程序

  • 更好的性能:通过与服务器交换所需的数据,AJAX 节省了带宽并提高了应用程序的速度

有许多使用 AJAX 的 Web 应用程序的例子。谷歌地图和 Gmail 可能是最突出的两个例子。事实上,这两个应用程序在推广 AJAX 的使用方面起到了重要作用,因为它们取得了成功。Gmail 与其他网络邮件服务的区别在于其用户界面,它使用户能够在不等待页面在每个操作后重新加载的情况下交互式地管理他们的电子邮件。这创造了更好的用户体验,使 Gmail 感觉更像是一个响应迅速且功能丰富的应用程序,而不是一个简单的网站。

本章将解释如何在 Django 中使用 AJAX,以使我们的应用程序更具响应性和用户友好性。我们将实现当今 Web 应用程序中发现的三种最常见的 AJAX 功能。但在此之前,我们将了解使用 AJAX 框架的好处,而不是使用原始 JavaScript 函数。

在 Django 中使用 AJAX 框架

由于我们已经在项目中使用了 Bootstrap,因此我们无需为 AJAX 和 jQuery 单独配置它。

使用 AJAX 框架的许多优点:

  • JavaScript 的实现因浏览器而异。一些浏览器提供更完整和功能丰富的实现,而其他浏览器包含不完整或不符合标准的实现。

没有 AJAX 框架,开发人员必须跟踪浏览器对他们使用的 JavaScript 功能的支持,并必须解决一些浏览器对 JavaScript 实现的限制。

另一方面,当使用 AJAX 框架时,框架会为我们处理这一点;它抽象了对 JavaScript 实现的访问,并处理了不同浏览器之间的差异和怪癖。这样,我们可以专注于开发功能,而不必担心浏览器的差异和限制。

  • 标准的 JavaScript 函数和类集合对于完整的 Web 应用程序开发有些不足。各种常见任务需要许多行代码,即使它们可以包装在简单的函数中。

因此,即使您决定不使用 AJAX 框架,您也会发现自己在编写一个函数库,该函数库封装了 JavaScript 功能并使其更易于使用。然而,既然已经有许多优秀的开源库可用,为什么要重新发明轮子呢?

今天市场上可用的 AJAX 框架范围从提供服务器端和客户端组件的综合解决方案到简化使用 JavaScript 的轻量级客户端库。鉴于我们已经在服务器端使用 Django,我们只需要一个客户端框架。除此之外,该框架应该易于与 Django 集成,而不需要任何额外的依赖。最后,最好选择一个轻量级和快速的框架。有许多优秀的框架符合我们的要求,例如PrototypeYahoo! UI LibraryjQuery

但是,对于我们的应用程序,我将选择 jQuery,因为它是这三种框架中最轻量级的。它还拥有一个非常活跃的开发社区和广泛的插件范围。如果您已经有其他框架的经验,可以在本章中继续使用它。的确,您将不得不将本章中的 JavaScript 代码适应到您的框架中,但是无论您选择哪种框架,服务器端的 Django 代码都将保持不变。

注意

您还需要导入 Bootstrap 和 jQuery。因此,在我们的 Django 项目中使用 AJAX 功能不需要特定的安装或导入。

使用开源的 jQuery 框架

在我们开始在项目中实现 AJAX 增强功能之前,让我们快速介绍一下 jQuery 框架。

jQuery JavaScript 框架

jQuery 是一个 JavaScript 函数库,它简化了与 HTML 文档的交互并对其进行操作。该库旨在减少编写代码和实现跨浏览器兼容性所需的时间和精力,同时充分利用 JavaScript 提供的功能来构建交互式和响应式的 Web 应用程序。

使用 jQuery 的一般工作流程包括以下两个步骤:

  1. 选择要处理的 HTML 元素或一组元素。

  2. 将 jQuery 方法应用于所选组。

元素选择器

jQuery 提供了一种简单的选择元素的方法:通过将 CSS 选择器字符串传递给名为$()的函数。以下是一些示例,说明了此函数的用法:

  • 如果您想选择页面上的所有锚(<a>)元素,可以使用$("a")函数调用

  • 如果您想选择具有.title CSS 类的锚元素,请使用

$("a.title")
  • 要选择 ID 为#nav的元素,可以使用$("#nav")

  • 要选择#nav内部的所有列表项(<li>)元素,请使用$("#nav li")

$()函数构造并返回一个 jQuery 对象。之后,您可以在此对象上调用方法以与所选的 HTML 元素交互。

jQuery 方法

jQuery 提供了各种方法来操作 HTML 文档。您可以隐藏或显示元素,将事件处理程序附加到事件,修改 CSS 属性,操作页面结构,最重要的是执行 AJAX 请求。

为了调试,我们选择 Chrome 浏览器作为我们的首选浏览器。 Chrome 是最先进的 JavaScript 调试器之一,以其 Chrome 开发者工具的形式。要启动它,请在键盘上按下Ctrl+Shift+J

要尝试本节中概述的方法,请启动开发服务器并导航到用户配置文件页面(http://127.0.0.1:8000/user/ratan/)。通过按下键盘上的Ctrl+Shift+J打开 Chrome 开发者工具(按F12),并尝试选择元素并操作它们。

隐藏和显示元素

让我们从简单的事情开始。要在页面上隐藏一个元素,请在其上调用hide()方法。要再次显示它,请调用show()方法。例如,尝试在您的应用程序的 Bootstrap 中称为navbar的导航菜单上尝试这个:

>>> $(".navbar").hide()
>>> $(".navbar").show() 

您还可以在隐藏和显示元素时对元素进行动画处理。尝试使用fadeOut()fadeIn()slideUp()slideDown()方法来查看这两种动画效果中的两种。

当然,如果一次选择多个元素,这些方法(就像所有其他 jQuery 方法一样)也会起作用。例如,如果打开用户配置文件并在 Chrome 开发人员工具控制台中输入以下方法调用,则所有推文都将消失:

>>> $('.well').slideUp()

访问 CSS 属性和 HTML 属性

接下来,我们将学习如何更改元素的 CSS 属性。jQuery 提供了一个名为css()的方法来执行 CSS 操作。如果您以字符串形式传递 CSS 属性名称调用此方法,它将返回此属性的值:

>>> $(".navbar").css("display")

这样的结果如下:

block

如果向此方法传递第二个参数,它将将所选元素的指定 CSS 属性设置为附加参数:

>>> $(".navbar").css("font-size", "0.8em")

这样的结果如下:

<div id="nav" style="font-size: 0.8em;">

实际上,您可以操纵任何 HTML 属性,而不仅仅是 CSS 属性。要这样做,请使用attr()方法,它的工作方式与css()方法类似。使用属性名称调用它会返回属性值,而使用属性名称或值对调用它会将属性设置为传递的值:

>>> $("input").attr("size", "48")

这将导致以下结果:

<input type="hidden" name="csrfmiddlewaretoken" value="xxx" size="48">
<input id="id_country" name="country" type="hidden" value="Global" size="48">
<input type="submit" value="post" size="48">

这将一次性将页面上所有输入元素的大小更改为48

除此之外,还有一些快捷方法可以获取和设置常用的属性,例如val(),当不带参数调用时返回输入字段的值,并在传递一个参数时将该值设置为参数。还有控制元素内部 HTML 代码的html()方法。

最后,有两种方法可以用来附加或分离 CSS 类到一个元素:它们是addClass()removeClass()方法。还提供了第三种方法来切换 CSS 类,称为toggleClass()方法。所有这些类方法都将要更改的类的名称作为参数。

操作 HTML 文档

现在您已经熟悉了如何操作 HTML 元素,让我们看看如何添加新元素或删除现有元素。要在元素之前插入 HTML 代码,请使用before()方法,要在元素之后插入代码,请使用after()方法。请注意 jQuery 方法的命名方式非常直观,易于记忆!

让我们通过在用户页面上的标签列表周围插入括号来测试这些方法。

打开您的用户页面,并在 Chrome 开发者工具控制台中输入以下内容:

>>> $(".well span").before("<strong>(</strong>")
>>> $(".well span").after("<strong>)</strong>")

您可以向before()after()方法传递任何您想要的字符串。该字符串可以包含纯文本、一个 HTML 元素或更多。这些方法提供了一种非常灵活的方式来动态添加 HTML 元素到 HTML 文档中。

如果要删除一个元素,请使用remove()方法。例如:

$("#navbar").remove()

这种方法不仅隐藏了元素,还将其从文档树中完全删除。如果在使用remove()方法后尝试重新选择元素,您将得到一个空集:

>>> $("#nav")

这样的结果如下:

[]

当然,这只是从当前页面实例中删除元素。如果重新加载页面,元素将再次出现。

遍历文档树

尽管 CSS 选择器提供了一种非常强大的选择元素的方式,但有时您希望从特定元素开始遍历文档树。

对此,jQuery 提供了几种方法。parent()方法返回当前选定元素的父元素。children()方法返回所选元素的所有直接子元素。最后,find()方法返回当前选定元素的所有后代元素。所有这些方法都接受一个可选的 CSS 选择器字符串,以限制结果为与选择器匹配的元素。例如,$(".column").find("span")返回类 column 的所有<span>后代。

如果要访问一组中的单个元素,请使用get()方法,该方法将元素的索引作为参数。例如,$("span").get(0)方法返回所选组中的第一个<span>元素。

处理事件

接下来我们将学习事件处理程序。事件处理程序是在特定事件发生时调用的 JavaScript 函数,例如,当单击按钮或提交表单时。jQuery 提供了一系列方法来将处理程序附加到事件上;在我们的应用程序中特别感兴趣的事件是鼠标点击和表单提交。要处理单击元素的事件,我们选择该元素并在其上调用click()方法。该方法将事件处理程序函数作为参数。让我们在 Chrome 开发者控制台中尝试一下。

打开应用程序的用户个人资料页面,并在推文后插入一个按钮:

>>> $(".well span").after("<button id=\"test-button\">Click me!</button>")

注意

请注意,我们必须转义传递给after()方法的字符串中的引号。

如果您尝试单击此按钮,将不会发生任何事情,因此让我们为其附加一个事件处理程序:

>>> $("#test-button").click(function () { alert("You clicked me!"); })

现在,当您点击按钮时,将出现一个消息框。这是如何工作的?

我们传递给click()方法的参数可能看起来有点复杂,因此让我们再次检查一下:

function () { alert("You clicked me!"); }

这似乎是一个函数声明,但没有函数名。事实上,这个构造在 JavaScript 术语中创建了所谓的匿名函数,当您需要即时创建一个函数并将其作为参数传递给另一个函数时使用。我们本可以避免使用匿名函数,并将事件处理程序声明为常规函数:

>>> function handler() { alert("You clicked me!"); }
>>> $("#test-button").click(handler)

前面的代码实现了相同的效果,但第一个更简洁、紧凑。我强烈建议您熟悉 JavaScript 中的匿名函数(如果您还没有),因为我相信您在使用一段时间后会欣赏这种构造并发现它更易读。

处理表单提交与处理鼠标点击非常相似。首先选择表单,然后在其上调用submit()方法,然后将处理程序作为参数传递。在后面的部分中,我们将在项目中添加 AJAX 功能时多次使用这种方法。

发送 AJAX 请求

在完成本节之前,让我们谈一下 AJAX 请求。jQuery 提供了许多发送 AJAX 请求到服务器的方法。例如,load()方法接受一个 URL,并将该 URL 的页面加载到所选元素中。还有发送 GET 或 POST 请求以及接收结果的方法。在实现项目中的 AJAX 功能时,我们将更深入地研究这些方法。

接下来呢?

这就结束了我们对 jQuery 的快速介绍。本节提供的信息足以继续本章,一旦您完成本章,您将能够自己实现许多有趣的 AJAX 功能。但是,请记住,这个 jQuery 介绍只是冰山一角。如果您想全面了解 jQuery 框架,我强烈建议您阅读 Packt Publishing 的Learning jQuery,因为它更详细地介绍了 jQuery。您可以在www.packtpub.com/jQuery了解更多关于这本书的信息。

实现推文搜索

我们将通过实现实时搜索来引入 AJAX 到我们的应用程序中。这个功能背后的想法很简单:当用户在文本字段中输入一些关键词并点击搜索时,一个脚本在后台工作,获取搜索结果并在同一个页面上呈现它们。搜索页面不会重新加载,从而节省带宽,并提供更好、更具响应性的用户体验。

在我们开始实现这个功能之前,我们需要牢记一个重要的规则,即在使用 AJAX 时编写应用程序,确保它在没有 AJAX 支持的浏览器和没有启用 JavaScript 的用户中也能正常工作。如果你这样做,你就确保每个人都能使用你的应用程序。

实现搜索

因此,在我们使用 AJAX 之前,让我们编写一个简单的视图,通过标题搜索书签。首先,我们需要创建一个搜索表单,所以打开tweets/forms.py文件,并添加以下类:

class SearchForm(forms.Form):
query = forms.CharField(label='Enter a keyword to search for',
widget=forms.TextInput(attrs={'size': 32, 'class':'form-control'}))

正如你所看到的,这是一个非常简单的表单类,只有一个文本字段。用户将使用这个字段输入搜索关键词。接下来,让我们创建一个视图来进行搜索。打开tweets/views.py文件,并输入以下代码:

class Search(View):
  """Search all tweets with query /search/?query=<query> URL"""
  def get(self, request):
    form = SearchForm()
    params = dict()
    params["search"] = form
  return render(request, 'search.html', params)

  def post(self, request):
    form = SearchForm(request.POST)
    if form.is_valid():
    query = form.cleaned_data['query']
    tweets = Tweet.objects.filter(text__icontains=query)
    context = Context({"query": query, "tweets": tweets})
    return_str = render_to_string('partials/_tweet_search.html', context)
  return HttpResponse(json.dumps(return_str), content_type="application/json")
  else:
    HttpResponseRedirect("/search")

除了一些方法调用,这个视图应该非常容易理解。如果你看一下get请求,它非常简单,因为它准备搜索表单,然后呈现它。

post()方法是所有魔法发生的地方。当我们呈现搜索结果时,它只是一个带有搜索表单的布局呈现,也就是说,如果你看一下我们创建的名为search.html的新文件,你会看到以下内容:

{% extends "base.html" %}
{% load staticfiles %}
{% block content %}

<div class="row clearfix">
  <div class="col-md-6 col-md-offset-3 column">
    <form id="search-form" action="" method="post">{% csrf_token %}
      <div class="input-group input-group-sm">
      {{ search.query.errors }}
      {{ search.query }}
        <span class="input-group-btn">
          <button class="btn btn-search" type="submit">search</button>
        </span>
      </div><!-- /input-group -->
    </form>
  </div>
  <div class="col-md-12 column tweets">
  </div>
</div>
{% endblock %}
{% block js %}
  <script src="img/search.js' %}"></script>
{% endblock %}

如果你仔细观察,你会看到一个名为{% block js %}的新部分的包含。这里使用的概念与{% block content %}块相同,也就是说,这里声明的内容将在base.html文件中呈现。进一步看,再看修改后的base.html文件,我们可以看到以下内容:

{% load staticfiles %}
  <html>
    <head>
      <link href="{% static 'css/bootstrap.min.css' %}"
        rel="stylesheet" media="screen">
        {% block css %}
        {% endblock %}
    </head>
    <body>
      <nav class="navbar navbar-default" role="navigation">
        <a class="navbar-brand" href="#">MyTweets</a>
        <p class="navbar-text navbar-right">User Profile Page</p>
      </nav>
      <div class="container">
        {% block content %}
        {% endblock %}
      </div>
      <nav class="navbar navbar-default navbar-fixed-bottom" role="navigation">
        <p class="navbar-text navbar-right">Footer </p>
      </nav>
      <script src="img/jquery-2.1.1.min.js' %}"></script>
      <script src="img/bootstrap.min.js' %}"></script>
      <script src="img/base.js' %}"></script>
        {% block js %}
        {% endblock %}
    </body>
  </html>

上述代码清楚地显示了两个新的内容块,如下所示:

{% block css %}
  {% endblock %}
  {% block js %}
{% endblock %}

它们用于包含相应的文件类型,并将文件类型与基础一起呈现,因此使用简单的规则,每页只声明一个 CSS 和 JavaScript 文件,从而使项目的维护变得更加简单。我们将在本书的后面使用调用assets pipeline的概念来实现这一点。

现在,回到我们的 AJAX 搜索功能,你会发现这个search.html文件与tweet.html文件类似。

对于搜索功能,我们将创建一个新的 URL,需要将其附加到以下的urls.py文件中:

url(r'^search/$', Search.as_view()),
urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from tweet.views import Index, Profile, PostTweet, HashTagCloud, Search

admin.autodiscover()

urlpatterns = patterns('',
url(r'^$', Index.as_view()),
url(r'^user/(\w+)/$', Profile.as_view()),
url(r'^admin/', include(admin.site.urls)),
url(r'^user/(\w+)/post/$', PostTweet.as_view()),
url(r'^hashTag/(\w+)/$', HashTagCloud.as_view()),
url(r'^search/$', Search.as_view()),
)

search.html文件中,我们定义了search.js方法;让我们创建这个 JavaScript 文件,它实际上发出了 AJAX 请求:

search.js

$('#search-form').submit(function(e){
$.post('/search/', $(this).serialize(), function(data){
$('.tweets').html(data);
});
e.preventDefault();
});

当表单提交时,这段 JavaScript 代码会被触发,它会向/search用户发出一个 AJAX post 请求,带有序列化的表单数据,并获得响应。然后,根据获得的响应,它将数据附加到具有类别 tweets 的元素。

如果我们在浏览器中打开用户搜索,它会看起来像下面的截图:

实现搜索

现在,等等!当这个表单提交时会发生什么?

AJAX 请求发送到搜索类的post()方法,如下所示:

def post(self, request):
  form = SearchForm(request.POST)
  if form.is_valid():
    query = form.cleaned_data['query']
    tweets = Tweet.objects.filter(text__icontains=query)
    context = Context({"query": query, "tweets": tweets})
    return_str = render_to_string('partials/_tweet_search.html', context)
  return HttpResponse(json.dumps(return_str), content_type="application/json")
  else:
    HttpResponseRedirect("/search")

我们从request.POST方法中提取表单验证;如果表单有效,就从表单对象中提取查询。

然后,tweets = Tweet.objects.filter(text__icontains===query)方法搜索给定查询项的子字符串匹配。

搜索是使用Tweets.objects模块中的filter方法进行的。你可以把它看作是 Django 模型中SELECT语句的等价物。它接收搜索条件作为参数,并返回搜索结果。每个参数的名称必须遵循以下命名约定:

field__operator

请注意,fieldoperator 变量之间用两个下划线分隔:field 是我们想要搜索的字段的名称,operator 是我们想要使用的查找方法。以下是常用操作符的列表:

  • exact: 参数的值与字段的精确匹配

  • contains: 该字段包含参数的值

  • startswith: 该字段以参数的值开头

  • lt: 该字段小于参数的值

  • gt: 该字段大于参数的值

此外,还有前三个操作符的不区分大小写版本:iexacticontainsistartswith,也可以包括在列表中。

我们现在正在做的一件完全不同的事情是:

context = Context({"query": query, "tweets": tweets})
return_str = render_to_string('partials/_tweet_search.html', context)
return HttpResponse(json.dumps(return_str), content_type="application/json")

我们的目标是在不重新加载或刷新搜索页面的情况下加载搜索结果。如果是这样,我们之前的渲染方法将如何帮助我们?它不能。我们需要一些方法,可以帮助我们在不重新加载页面的情况下将数据发送到浏览器。

我们广泛使用网页开发中称为partials的概念。它们通常是在服务器端生成的小段 HTML 代码片段,以 JSON 格式呈现,然后通过 JavaScript 添加到现有 DOM 中。

为了实现这个方法,我们首先会在现有模板文件夹中创建一个名为 partials 的文件夹,以及一个名为 _tweet_search.html 的文件,内容如下:

{% for tweet in tweets %}
  <div class="well">
    <span>{{ tweet.text }}</span>
  </div>
{% endfor %}
{% if not tweets %}
  <div class="well">
    <span> No Tweet found.</span>
  </div>
{% endif %}

该代码将在一个良好的框中渲染整个推文对象,或者如果找不到推文对象,它将在框中渲染 未找到推文

前面的概念是在视图中将一个 partial 渲染为字符串,如果我们需要为渲染传递任何参数,我们需要在调用从 partials 生成字符串的地方首先传递它们。要为 partials 传递参数,我们需要创建一个上下文对象,然后传递我们的参数:

context = Context({"query": query, "tweets": tweets})
return_str = render_to_string('partials/_tweet_search.html', context)

首先,我们将创建包含 query(稍后将使用)和 tweets 参数的上下文,并使用 render_to_string() 函数。然后,我们可以使用 JSON 将字符串转储到 HttpResponse() 函数,如下所示:

return HttpResponse(json.dumps(return_str), content_type="application/json")

导入列表如下:

from django.views.generic import View
from django.shortcuts import render
from user_profile.models import User
from models import Tweet, HashTag
from tweet.forms import TweetForm, SearchForm
from django.http import HttpResponseRedirect
from django.template.loader import render_to_string
from django.template import Context
from django.http import HttpResponse
import json

就是这样!我们完成了一个基于 AJAX 的推文搜索。搜索 django 列出了我们创建的两条推文,如下截图所示:

实现搜索

继续使用搜索引擎,并且我相信你会更加喜欢 Django。

现在我们有了一个功能性的(尽管非常基本的)搜索页面。搜索功能本身将在后面的章节中得到改进,但对我们来说现在重要的是将 AJAX 引入搜索表单,以便在幕后获取结果并呈现给用户,而无需重新加载页面。由于我们的模块化代码,这个任务将比看起来要简单得多。

实现实时搜索推文

在上一节中进行了简单的搜索,现在我们将实现实时搜索,技术上是相同的,但唯一的区别是搜索表单将随着每次按键而提交,并且结果将实时加载。

要实现实时搜索,我们需要做以下两件事:

  • 我们需要拦截并处理提交搜索表单的事件。这可以使用 jQuery 的 submit() 方法来完成。

  • 我们需要使用 AJAX 在后台加载搜索结果,并将它们插入页面中。

jQuery 提供了一个名为 load() 的方法,用于从服务器检索页面并将其内容插入到所选元素中。在其最简单的形式中,该函数将远程页面的 URL 作为参数。

我们将在标签上实现实时搜索,也就是说,我们将创建一个新页面,与我们刚刚创建的搜索页面相同,但这将用于标签,并且我们将使用实时标签建议(标签自动完成)。在开始之前,我们需要相同的 Twitter typeahead JavaScript 库。

twitter.github.io/typeahead.js/ 下载这个库的最新版本。

在本章中,我们下载了版本为 10.05 的库。下载并保存到当前的 JavaScript 文件夹中。

首先,让我们稍微修改我们的搜索视图,以便在接收到名为 AJAX 的额外 GET 变量时,仅返回搜索结果而不是搜索页面的其余部分。我们这样做是为了使客户端的 JavaScript 代码能够轻松地检索搜索结果,而不需要搜索页面的其余部分的 HTML 格式。这可以通过在请求时简单地使用 bookmark_list.html 模板而不是 search.html 模板来实现。

GET 包含关键的 AJAX 参数。打开 bookmarks/views.py 文件并修改 search_page 参数(朝文件末尾),使其如下所示:

def search_page(request):
  [...]
  variables = RequestContext(request, {
    'form': form,
    'bookmarks': bookmarks,
    'show_results': show_results,
    'show_tags': True,
    'show_user': True
  })
  if request.GET.has_key('AJAX'):):):
    return render_to_response('bookmark_list.html', variables)
  else:
    return render_to_response('search.html', variables)

接下来,在 site_media 目录中创建一个名为 search.js 的文件,并将其链接到 templates/search.html 文件,如下所示:

{% extends "base.html" %}
  {% block external %}
    <script type="text/javascript" src="img/search.js">
    </script>
  {% endblock %}
{% block title %}Search Bookmarks{% endblock %}
{% block head %}Search Bookmarks{% endblock %}
[...]

现在是有趣的部分!让我们创建一个函数,加载搜索结果并将它们插入相应的 div 标签中。在 site_media/search.js 文件中写入以下代码:

function search_submit() {
  var query = $("#id_query").val();
  $("#search-results").load(
    "/search/?AJAX&query=" + encodeURIComponent(query)
  );
return false;
}

让我们逐行浏览这个函数:

  • 该函数首先使用 val() 方法从文本字段中获取查询字符串。

  • 我们使用 load() 方法从 search_page 视图获取搜索结果,并将搜索结果插入到 #search-results div 中。首先对查询调用 encodeURIComponent 参数构造请求 URL,它的工作方式与我们在 Django 模板中使用的 urlencode 过滤器完全相同。调用这个函数很重要,以确保即使用户在文本字段中输入特殊字符,如 &,构造的 URL 仍然有效。在转义查询后,我们将其与 /search/?AJAX&query= 参数连接起来。这个 URL 调用 search_page 视图,并将 GET 变量的 AJAX 参数和查询传递给它。视图返回搜索结果,load() 方法便将结果加载到 #search-results div 中。

  • 我们从函数中返回 False,告诉浏览器在调用处理程序后不要提交表单。如果我们在函数中不返回 False,浏览器将继续像往常一样提交表单,而我们不希望这样。

还有一个小细节:在何处以及何时应该将 search_submit 参数附加到搜索表单的提交事件上?在编写 JavaScript 时的一个经验法则是,在文档完成加载之前,我们不能操作文档树中的元素。因此,我们的函数必须在搜索页面加载完成后立即调用。幸运的是,jQuery 提供了一种在 HTML 文档加载时执行函数的方法。让我们通过将以下代码附加到 site_media/search.js 文件来利用它:

$(document).ready(function () {
  $("#search-form").submit(search_submit);
});

$(document) 函数选择当前页面的文档元素。请注意,document 变量周围没有引号;它是浏览器提供的变量,而不是字符串。

ready() 方法接受一个函数,并在所选元素完成加载后立即执行它。因此,实际上,我们告诉 jQuery 在 HTML 文档加载完成后立即执行传递的函数。我们将一个匿名函数传递给 ready() 方法,这个函数简单地将 search_submit 参数绑定到 #search-form 表单的提交事件上。

就是这样。我们用不到十五行的代码实现了实时搜索。要测试新功能,转到 http://127.0.0.1:8000/search/,提交查询,并注意结果如何在不重新加载页面的情况下显示。

本节涵盖的信息可以应用于任何需要在后台处理而无需重新加载页面的表单。例如,您可以创建一个带有预览按钮的评论表单,该按钮在同一页面上加载预览而无需重新加载。在下一节中,我们将增强用户页面,使用户可以在原地编辑书签而无需离开用户页面。

在不加载单独页面的情况下原地编辑推文

编辑发布的内容是网站上非常常见的任务。通常通过在内容旁边提供一个“编辑”链接来实现。当用户点击链接时,该链接会将用户带到另一个页面上的一个表单,用户可以在那里编辑内容。用户提交表单后,会被重定向回内容页面。

另一方面,想象一下,您可以在不离开内容页面的情况下编辑内容。当您点击“编辑”按钮时,内容会被一个表单替换。当您提交表单时,它会消失,更新后的内容会出现在原来的位置。所有操作都在同一个页面上进行;使用 JavaScript 和 AJAX 来完成表单的渲染和提交。这样的工作流程会更直观和响应更快吗?

上述描述的技术称为原地编辑。它现在在 Web 应用程序中变得更加普遍。我们将通过让用户在用户页面上原地编辑书签来实现此功能。

由于我们的应用程序尚不支持编辑书签,我们将首先实现这一点,然后修改编辑过程以在原地工作。

实现书签编辑

我们已经拥有大部分需要实现书签编辑的部分。如果您回忆一下前一章,我们在 bookmarks/views.py 文件中实现了 bookmark_save_page 视图,以便如果用户尝试多次保存相同的 URL,则更新相同的书签而不是创建副本。这得益于数据模型提供的 get_or_create() 方法,这个小细节极大地简化了书签编辑的实现。我们需要做的是:

  • 我们将要编辑的书签的 URL 作为名为 URL 的 GET 变量传递给 bookmark_save_page 视图。

  • 我们修改 bookmark_save_page 视图,以便在接收到 GET 变量时填充书签表单的字段。该表单将填充与传递的 URL 对应的书签的数据。

当填充的表单被提交时,书签将被更新,就像我们之前解释的那样,因为它看起来好像用户又提交了相同的 URL。

在我们实现上述描述的技术之前,让我们通过将保存书签的部分移动到一个单独的函数中来减少 bookmark_save_page 视图的大小。我们将称此函数为 _bookmark_save。名称开头的下划线告诉 Python 在导入视图模块时不要导入此函数。该函数期望请求和有效的表单对象作为参数;它将根据表单数据保存书签并返回该书签。

打开 bookmarks/views.py 文件并创建以下函数;如果愿意,可以从 bookmark_save_page 视图中复制并粘贴代码,因为我们除了最后的 return 语句外不会对其进行任何更改:

def _bookmark_save(request, form):
  # Create or get link.
  link, dummy = \
  Link.objects.get_or_create(url=form.clean_data['url'])
  # Create or get bookmark.
  bookmark, created = Bookmark.objects.get_or_create(
    user=request.user,
    link=link
  )
  # Update bookmark title.
  bookmark.title = form.clean_data['title']
  # If the bookmark is being updated, clear old tag list.
  if not created:
    bookmark.tag_set.clear()
    # Create new tag list.
    tag_names = form.clean_data['tags'].split()
    for tag_name in tag_names:
      tag, dummy = Tag.objects.get_or_create(name=tag_name)
      bookmark.tag_set.add(tag)
      # Save bookmark to database and return it.
      bookmark.save()
    return bookmark
    Now in the same file, replace the code that you removed from bookmark_save_page
    with a call to _bookmark_save :
      @login_required
      def bookmark_save_page(request):
        if request.method == 'POST':
          form = BookmarkSaveForm(request.POST)
        if form.is_valid():
          bookmark = _bookmark_save(request, form)
          return HttpResponseRedirect(
            '/user/%s/' % request.user.username
          )
        else:
          form = BookmarkSaveForm()
          variables = RequestContext(request, {
            'form': form
          })
        return render_to_response('bookmark_save.html', variables)

bookmark_save_page 视图中的当前逻辑如下:

[伪代码]

if there is POST data:
  Validate and save bookmark.
  Redirect to user page.
else:
  Create an empty form.
Render page.

要实现书签编辑,我们需要稍微修改逻辑,如下所示:

[伪代码]

if there is POST data:
  Validate and save bookmark.
  Redirect to user page.
  else if there is a URL in GET data:
    Create a form an populate it with the URL's bookmark.
  else:
    Create an empty form.
Render page.

让我们将上述伪代码翻译成 Python。修改 bookmarks/views.py 文件中的 bookmark_save_page 视图,使其看起来像以下代码(新代码已突出显示):

from django.core.exceptions import ObjectDoesNotExist
@login_required
def bookmark_save_page(request):
  if request.method == 'POST':
    form = BookmarkSaveForm(request.POST)
      if form.is_valid():
        bookmark = _bookmark_save(request, form)
        return HttpResponseRedirect(
          '/user/%s/' % request.user.username)
        elif request.GET.has_key('url'):):):
          url = request.GET['url']
          title = ''
          tags = ''
        try:
          link = Link.objects.get(url=url)
          bookmark = Bookmark.objects.get(
            link=link,
            user=request.user
          )
        title = bookmark.title
        tags = ' '.join(
          tag.name for tag in bookmark.tag_set.all()
        )
        except ObjectDoesNotExist:
          pass
        form = BookmarkSaveForm({
          'url': url,
          'title': title,
          'tags': tags
        })
        else:
          form = BookmarkSaveForm()
          variables = RequestContext(request, {
            'form': form
          })
        return render_to_response('bookmark_save.html', variables)

代码的这一新部分首先检查是否存在名为 URL 的 GET 变量。如果是这样,它将加载此 URL 的相应LinkBookmark对象,并将所有数据绑定到书签保存表单。您可能会想知道为什么我们在 try-except 结构中加载LinkBookmark对象,并默默地忽略异常。

确实,如果没有找到请求的 URL 的书签,引发 HTTP 404 异常是完全有效的。然而,我们的代码选择在这种情况下只填充 URL 字段,留下标题和标签字段为空。

现在,在用户页面的每个书签旁边添加编辑链接。打开templates/bookmark_list.html文件并插入突出显示的代码:

{% if bookmarks %}
  <ul class="bookmarks">
    {% for bookmark in bookmarks %}
      <li>
        <a href="{{ bookmark.link.url }}" class="title">
        {{ bookmark.title|escape }}</a>
        {% if show_edit %}
          <a href="/save/?url={{ bookmark.link.url|urlencode }}"
          class="edit">[edit]</a>
        {% endif %}
      <br />
      {% if show_tags %}
        Tags:
          {% if bookmark.tag_set.all %}
            <ul class="tags">
              {% for tag in bookmark.tag_set.all %}
                <li><a href="/tag/{{ tag.name|urlencode }}/">
              {{ tag.name|escape }}</a></li>
              {% endfor %}
            </ul>
      {% else %}
        None.
      {% endif %}
      <br />
[...]

注意我们是如何通过将书签的 URL 附加到/save/?url= {{ bookmark.link.url|urlencode }}来构建编辑链接的。

此外,由于我们只想在用户页面上显示编辑链接,模板只在show_edit标志设置为True时呈现这些链接。否则,让用户编辑其他人的链接是没有意义的。现在打开bookmarks/views.py文件,并在user_page标志的模板变量中添加show_edit标志:

def user_page(request, username):
  user = get_object_or_404(User, username=username)
  bookmarks = user.bookmark_set.order_by('-id')
  variables = RequestContext(request, {
    'bookmarks': bookmarks,
    'username': username,
    'show_tags': True,
    'show_edit': username == request.user.username,
  })
return render_to_response('user_page.html', variables)

username == request.user.username表达式仅在用户查看自己的页面时评估为True,这正是我们想要的。

最后,我建议您稍微减小编辑链接的字体大小。打开site_media/style.css文件并将以下内容附加到其末尾:

ul.bookmarks .edit {
  font-size: 70%;
}

我们完成了!在继续之前,随意导航到您的用户页面并尝试编辑书签。

实现书签的原地编辑

现在我们已经实现了书签编辑,让我们转向令人兴奋的部分:使用 AJAX 添加原地编辑!

我们的方法是:

  • 我们将拦截点击编辑链接的事件,并使用 AJAX 从服务器加载书签编辑表单。然后我们将用编辑表单替换页面上的书签。

  • 当用户提交编辑表单时,我们将拦截提交事件,并使用 AJAX 将更新后的书签发送到服务器。

  • 服务器保存书签并返回新书签的 HTML 表示。然后我们将用服务器返回的标记替换页面上的编辑表单。

我们将使用与实时搜索非常相似的方法来实现前面的过程。首先,我们将修改bookmark_save_page视图,以便在 GET 变量称为 AJAX 存在时响应 AJAX 请求。接下来,我们将编写 JavaScript 代码,从视图中检索编辑表单,当用户提交此表单时,将书签数据发送回服务器。

由于我们希望从bookmark_save_page视图返回一个编辑表单的标记给 AJAX 脚本,让我们稍微重构一下我们的模板。在模板中创建一个名为bookmark_save_form.html的文件,并将书签保存表单从bookmark_save.html文件移动到这个新文件中:

<form id="save-form" method="post" action="/save/">
  {{ form.as_p }}
  <input type="submit" value="save" />
</form>

请注意,我们还更改了表单的 action 属性为/save/并为其赋予了一个 ID。这对于表单在用户页面以及书签提交页面上的工作是必要的。

接下来,在bookmark_save.html文件中包含这个新模板:

{%extends "base.html" %}
{%block title %}Save Bookmark{% endblock %}
{%block head %}Save Bookmark{% endblock %}
{%block content %}
{%include 'bookmark_save_form.html' %}
{%endblock %}

,现在我们将表单放在一个单独的模板中。让我们更新bookmark_save_page视图,以处理正常和 AJAX 请求。打开bookmarks/views.py文件并更新视图,使其看起来像下面修改后的样子(用新加粗的行):

def bookmark_save_page(request):
  AJAX = request.GET.has_key('AJAX')))
  if request.method == 'POST':
    form = BookmarkSaveForm(request.POST)
    if form.is_valid():
      bookmark = _bookmark_save(form)
        if AJAX:
          variables = RequestContext(request, {
            'bookmarks': [bookmark],
            'show_edit': True,
            'show_tags': True
        })
      return render_to_response('bookmark_list.html', variables)
      else:
        return HttpResponseRedirect(
          '/user/%s/' % request.user.username
        )
      else:
        if AJAX:
          return HttpResponse('failure')
          elif request.GET.has_key('url'):
            url = request.GET['url']
            title = ''
            tags = ''
        try:
          link = Link.objects.get(url=url)
          bookmark = Bookmark.objects.get(link=link, user=request.user)
          title = bookmark.title
          tags = ' '.join(tag.name for tag in bookmark.tag_set.all())
        except:::
          pass
          form = BookmarkSaveForm({
            'url': url,
            'title': title,
            'tags': tags
          })
        else:
          form = BookmarkSaveForm()
          variables = RequestContext(request, {
            'form': form
          })
          if AJAX:
            return render_to_response(
              'bookmark_save_form.html',
              variables
            )
            else:
              return render_to_response(
                'bookmark_save.html',
                variables
              )

让我们分别检查每个突出显示的部分:

AJAX = request.GET.has_key('AJAX')

在方法的开头,我们将检查是否存在名为 AJAX 的 GET 变量。我们将结果存储在名为 AJAX 的变量中。稍后在方法中,我们可以使用这个变量来检查我们是否正在处理 AJAX 请求:

if condition:
  if form.is_valid():
    bookmark = _bookmark_save(form)
    if AJAX:
      variables = RequestContext(request, {
        'bookmarks': [bookmark],
         'show_edit': True,
         'show_tags': True
      })
    return render_to_response('bookmark_list.html', variables)
    else:
      return HttpResponseRedirect('/user/%s/' % request.user.username)
    else:
      if AJAX:
        return HttpResponse('failure')

如果我们收到 POST 请求,我们检查提交的表单是否有效。如果有效,我们保存书签。接下来,我们检查这是否是一个 AJAX 请求。如果是,我们使用bookmark_list.html模板呈现保存的书签,并将其返回给请求脚本。否则,这是一个正常的表单提交,因此我们将用户重定向到他们的用户页面。另一方面,如果表单无效,我们只会像处理 AJAX 请求一样返回字符串'failure',我们将通过在 JavaScript 中显示错误对话框来响应。如果是正常请求,则无需执行任何操作,因为页面将重新加载,并且表单将显示输入中的任何错误:

if AJAX:
  return render_to_response('bookmark_save_form.html', variables)
  else:
    return render_to_response('bookmark_save.html', variables)

这在方法的末尾进行检查。如果没有 POST 数据,即执行到这一点,这意味着我们应该呈现一个表单并返回它。如果是 AJAX 请求,则使用bookmark_save_form.html模板,否则将其保存为 HTML 文件。

我们的视图现在已准备好为 AJAX 请求提供服务,也可以处理正常的页面请求。让我们编写 JavaScript 代码,以利用更新后的视图。在site_media文件夹中创建一个名为bookmark_edit.js的新文件。但是,在向其中添加任何代码之前,让我们将bookmark_edit.js文件链接到user_page.html模板。打开user_page.html文件,并进行以下修改:

{% extends "base.html" %}
  {% block external %}
    <script type="text/javascript" src="img/bookmark_edit.js">
    </script>
  {% endblock %}
  {% block title %}{{ username }}{% endblock %}
  {% block head %}Bookmarks for {{ username }}{% endblock %}
  {% block content %}
    {% include 'bookmark_list.html' %}
  {% endblock %}

我们需要在bookmark_edit.js文件中编写两个函数:

  • bookmark_edit:此函数处理编辑链接的点击。它从服务器加载编辑表单,并用此表单替换书签。

  • bookmark_save:此函数处理编辑表单的提交。它将表单数据发送到服务器,并用服务器返回的书签 HTML 替换表单。

让我们从第一个函数开始。打开site_media/bookmark_edit.js文件,并在其中编写以下代码:

function bookmark_edit() {
  var item = $(this).parent();
  var url = item.find(".title").attr("href");
  item.load("/save/?AJAX&url=" + escape(url), null, function () {
    $("#save-form").submit(bookmark_save);
  });
  return false;
}

因为这个函数处理编辑链接上的点击事件,所以this变量指的是编辑链接本身。将其包装在 jQuery $()函数中并调用parent()函数返回编辑链接的父元素,即书签的<li>元素(在 Firebug 控制台中尝试一下,看看自己是否能看到相同的结果)。

在获取书签的<li>元素的引用之后,我们获取书签的标题的引用,并使用attr()方法从中提取书签的 URL。

接下来,我们使用load()方法将编辑表单放置在书签的 HTML 文件中。这次,我们在 URL 之外调用load()方法时,还使用了两个额外的参数。load()函数接受两个可选参数,如下所示:

  • 如果我们发送 POST 请求,则它接受键或值对的对象。由于我们使用 GET 请求从服务器端视图获取编辑表单,因此对于此参数,我们传递 null。

  • 它接受一个函数,当 jQuery 完成将 URL 加载到所选元素时调用该函数。我们传递的函数将bookmark_save()方法(接下来我们将要编写的方法)附加到刚刚检索到的表单上。

最后,该函数返回False,告诉浏览器不要跟随编辑链接。现在我们需要使用$(document).ready()bookmark_edit()函数附加到单击编辑链接的事件上:

$(document).ready(function () {
  $("ul.bookmarks .edit").click(bookmark_edit);
});

如果您在编写此函数后尝试在用户页面中编辑书签,则应该会出现编辑表单,但是您还应该在 Firebug 控制台中收到 JavaScript 错误消息,因为bookmark_save()函数未定义,所以让我们来编写它:

function bookmark_save() {
  var item = $(this).parent();
  var data = {
    url: item.find("#id_url").val(),
    title: item.find("#id_title").val(),
    tags: item.find("#id_tags").val()
  };
  $.post("/save/?AJAX", data, function (result) {
    if (result != "failure") {
      item.before($("li", result).get(0));
      item.remove();
      $("ul.bookmarks .edit").click(bookmark_edit);
    }
    else {
      alert("Failed to validate bookmark before saving.");
    }
  });
  return false;
}

在这里,this变量指的是编辑表单,因为我们处理提交表单的事件。该函数首先通过检索对表单的父元素(再次是书签的<li>元素)的引用来开始。接下来,该函数使用每个表单字段的 ID 和val()方法从表单中检索更新的数据。

然后它使用一个名为$.post()的方法将数据发送回服务器。最后,它返回False以防止浏览器提交表单。

您可能已经猜到,$.post()函数是一个发送 POST 请求到服务器的 jQuery 方法。它有三个参数,如下:

  • POST 请求目标的 URL。

  • 表示 POST 数据的键/值对对象。

  • 当请求完成时调用的函数。服务器响应作为字符串参数传递给此函数。

值得一提的是,jQuery 提供了一个名为$.get()的方法,用于向服务器发送 GET 请求。它接受与$.post()函数相同类型的参数。我们使用$.post()方法将更新的书签数据发送到bookmark_save_page视图。正如前面几段讨论的那样,如果视图成功保存书签,则返回更新的书签 HTML。否则,它返回failure字符串。

因此,我们检查服务器返回的结果是否是“失败”。如果请求成功,我们使用before()方法在旧书签之前插入新书签,并使用remove()方法从 HTML 文档中删除旧书签。另一方面,如果请求失败,我们会显示一个显示失败的警报框。

在我们完成本节之前还有一些小事情。为什么我们插入$("li",result).get(0)方法而不是结果本身?如果您检查bookmark_save_page视图,您会看到它使用bookmark_list.html模板来构建书签的 HTML。然而,bookmark_list.html模板返回包装在<ul>标签中的书签<li>元素。基本上,$("li", result).get(0)方法告诉 jQuery 从结果中提取第一个<li>元素,这就是我们想要的元素。正如您从前面的片段中看到的,您可以使用 jQuery $()函数通过将该字符串作为函数的第二个参数传递来选择 HTML 字符串中的元素。

bookmark_submit模板是从bookmark_edit模板中的事件附加的,因此我们不需要在$(document).ready()方法中做任何事情。

最后,在将更新的书签加载到页面后,我们再次调用$("ul.bookmarks.edit").click(bookmark_edit)方法,将bookmark_edit模板附加到新加载的编辑链接上。如果不这样做并尝试两次编辑书签,第二次点击编辑链接将带您到一个单独的表单页面。

当您完成编写 JavaScript 代码后,打开浏览器并转到您的用户页面,尝试使用新功能。编辑书签,保存它们,并注意到如何在页面上立即反映出更改而无需重新加载。

现在您已经完成了这一部分,应该对就地编辑的实现有很好的理解。还有许多其他情况下,这个功能可以很有用,例如,可以用来在同一页上编辑文章或评论,而不必跳转到位于不同 URL 上的表单进行编辑。

在下一节中,我们将实现一个帮助用户在提交书签时输入标签的第三个常见的 AJAX 功能。

在提交推文时自动完成标签

我们将在本章中要实现的最后一个 AJAX 增强功能是标签的自动完成。自动完成的概念是在 Google 发布其 Suggest 搜索界面时进入 Web 应用程序的。Suggest 通过根据用户到目前为止输入的内容,在搜索输入字段下方显示最受欢迎的搜索查询。这也类似于集成开发环境中的代码编辑器根据您的输入提供代码完成建议。这个功能通过让用户输入他们想要的单词的几个字符,然后让他们从列表中选择而不必完全输入来节省时间。

我们将通过在提交书签时提供建议来实现此功能,但我们不打算从头开始编写此功能,而是要使用 jQuery 插件来实现它。jQuery 拥有一个不断增长的大型插件列表,提供各种功能。安装插件与安装 jQuery 本身没有什么不同。您下载一个(或多个)文件并将它们链接到您的模板,然后编写几行 JavaScript 代码来激活插件。

您可以通过导航到docs.jquery.com/Plugins来浏览可用的 jQuery 插件列表。在列表中搜索 autocomplete 插件并下载它,或者您可以直接从bassistance.de/jquery-plugins/jquery-plugin-autocomplete/获取它。

您将收到一个包含许多文件的 zip 存档文件。将以下文件(可以在jquery/autocomplete/scroll目录中找到)提取到site_media目录中:

  • jquery.autocomplete.css

  • dimensions.js

  • jquery.bgiframe.min.js

  • jquery.autocomplete.js

由于我们希望在书签提交页面上提供自动完成功能,请在site_media文件夹中创建一个名为tag_autocomplete.js的空文件。然后打开templates/bookmark_save.html文件,并将所有前述文件链接到它:

{% extends "base.html" %}
  {% block external %}
  <link rel="stylesheet"
  href="/site_media/jquery.autocomplete.css" type="text/css" />
  <script type="text/javascript"
  src="img/dimensions.js"> </script>
  <script type="text/javascript"
  src="img/jquery.bgiframe.min.js"> </script>
  <script type="text/javascript"
  src="img/jquery.autocomplete.js"> </script>
  <script type="text/javascript"
  src="img/tag_autocomplete.js"> </script>
  {% endblock %}
  {% block title %}Save Bookmark{% endblock %}
  {% block head %}Save Bookmark{% endblock %}
[...]

我们现在已经完成了插件的安装。如果你阅读它的文档,你会发现这个插件是通过在选定的输入元素上调用一个名为autocomplete()的方法来激活的。autocomplete()函数接受以下参数:

  • 服务器端 URL:对于这一点,插件向这个 URL 发送一个 GET 请求,其中包含到目前为止已经输入的内容,并期望服务器返回一组建议。

  • 可用于指定各种选项的对象:我们感兴趣的选项有很多。这个选项有一个布尔变量,告诉插件输入字段用于输入多个值(记住我们使用同一个文本字段输入所有标签),以及用于告诉插件哪个字符串分隔多个条目的多个分隔符。在我们的情况下,它是一个单个空格字符。

因此,在激活插件之前,我们需要编写一个视图,接收用户输入并返回一组建议。打开bookmarks/views.py文件,并将以下内容追加到文件末尾:

def AJAX_tag_autocomplete(request):
  if request.GET.has_key('q'):):):
    tags = \
    Tag.objects.filter(name__istartswith=request.GET['q'])[:10]
  return HttpResponse('\n'.join(tag.name for tag in tags))
return HttpResponse()

autocomplete()插件将用户输入发送到名为q的 GET 变量。因此,我们可以验证该变量是否存在,并构建一个以该变量值开头的标签列表。这是使用我们在本章前面学到的filter()方法和istartswith运算符完成的。我们只取前十个结果,以避免给用户带来过多的建议,并减少带宽和性能成本。最后,我们将建议连接成一个由换行符分隔的单个字符串,将字符串包装成一个HttpResponse对象,并返回它。

有了建议视图准备好后,在urls.py文件中为插件添加一个 URL 条目,如下所示:

urlpatterns = patterns('',
  # AJAX
  (r'^AJAX/tag/autocomplete/$', AJAX_tag_autocomplete),
)

现在在site_media/tag_autocomplete.js文件中输入以下代码,激活标签输入字段上的插件:

$(document).ready(function () {
  $("#id_tags").autocomplete(
    '/AJAX/tag/autocomplete/',
    {multiple: true, multipleSeparator: ' '}
  );
});

该代码将一个匿名函数传递给$(document).ready()方法。这个函数在标签输入字段上调用autocomplete()函数,并传递了我们之前讨论过的参数。

这几行代码就是我们实现标签自动完成所需要的全部内容。要测试新功能,请导航到http://127.0.0.1:8000/save/的书签提交表单,并尝试在标签字段中输入一个或两个字符。根据数据库中可用的标签,应该会出现建议。

有了这个功能,我们完成了这一章。我们涵盖了很多材料,并学习了许多令人兴奋的技术和技巧。阅读完本章后,你应该能够想到并实现许多其他用户界面的增强功能,比如在用户页面上删除书签的能力,或者通过标签实时浏览书签等等。

下一章将转向一个不同的主题:我们将让用户对他们最喜欢的书签进行投票和评论,我们的应用程序的首页将不再像现在这样空荡荡了!

总结

呼,这是一个很长的章节,但希望你从中学到了很多!我们从学习 jQuery 框架和如何将其整合到我们的 Django 项目开始了这一章。之后,我们在我们的书签应用程序中实现了三个令人兴奋的功能:实时搜索、就地编辑和自动完成。

下一章将是另一个令人兴奋的章节。我们将让用户提交书签到首页并为他们最喜欢的书签投票。我们还将让用户对书签进行评论。所以,请继续阅读!

第七章:关注和评论

我们应用程序的主要思想是为用户提供一个通过推文分享他们的想法的平台。让用户创建新推文只是其中的一部分,如果用户无法与现有推文进行交互,则应用程序将被认为是不完整的。在本章中,我们将完成另一部分,即使用户能够关注特定用户并评论现有推文。在此过程中,您还将学习到几个新的 Django 功能。

在本章中,您将学习以下内容:

  • 让用户关注另一个用户

  • 显示最受关注的用户

让用户关注另一个用户

到目前为止,我们的用户可以通过浏览标签和用户页面发现新的推文。让我们为用户提供一种方法来关注另一个用户,这样他们就可以在他们各自的主页上看到来自他们关注的所有用户的聚合推文。让我们还使用户能够评论新的推文。

我们还将创建一个页面,用户可以按关注者数量列出受欢迎的用户。这个功能对我们的应用程序很重要,因为它将把主页从一个基本的欢迎页面变成一个经常更新的用户列表,用户可以在其中找到热门用户和他们有趣的推文。

我们实现此功能的策略如下:

  • 创建一个数据模型来存储用户及其关注者。这个模型将跟踪与用户相关的各种信息。

  • 在他们的标题旁边给每个用户一个关注按钮。我们还将创建一个视图,显示计数,比如用户发表的推文数量和他们的关注者数量。这需要大量的工作,但结果将是值得的,我们将在这个过程中学到很多有用的信息。

让我们开始吧!

首先,我们要做的是为每个推文添加一个转发计数,并跟踪用户投票赞成的所有推文。为了实现这一点,我们需要创建一个新的UserFollowers数据模型。

UserFollowers 数据模型

当一个用户被另一个用户关注时,我们需要在数据库中存储以下信息:

  • 用户被关注的日期。我们需要这个信息来显示在一段时间内拥有最多关注者的用户。

  • 用户拥有的关注者数量。

  • 关注我们用户的用户列表。

这是为了防止用户两次关注同一个用户。

为此,我们将创建一个名为UserFollowers的新数据模型。打开user_profile/model.py并将以下类添加到其中:

class UserFollowers(models.Model):
  user = models.ForeignKey(User, unique=True))
  date = models.DateTimeField(auto_now_add=True)
  count = models.IntegerField(default=1))
  followers = models.ManyToManyField(User, related_name='followers')
  def __str__(self):
    return '%s, %s' % self.user, self.count

这个数据模型利用了一些重要的特性,所以我们将逐个介绍它的字段。用户字段是一个外键,指回被关注的用户。我们希望它是唯一的,这样同一个用户就不能被多次关注。

日期字段的类型是models.DateTimeField。顾名思义,您可以使用此字段存储日期/时间值。参数auto_now_add告诉 Django 在首次创建此数据模型的对象时自动将此字段设置为当前日期/时间。

计数字段的类型是models.IntegerField。该字段保存一个整数值。通过在该字段使用default=1参数,我们告诉 Django 在首次创建此数据模型的对象时将字段的值设置为 1。

以下的ManyToManyField参数包含了关注此用户的用户列表。

注意

这里,related_name='followers'参数必须作为第二个参数给出。用户和关注者都指向相同的类user,如果通过相关名称进行区分,可能会出现错误,例如,访问字段user的访问器与相关的 m2m 字段User.userfollowers_set冲突。

将数据模型代码输入到user_profile/models.py文件后,运行以下命令在数据库中创建相应的表:

$ python manage.py syncdb

有了这个,我们就可以存储所有我们需要维护关注者的信息。

接下来,我们将创建一个视图,用户可以通过单击其个人资料名称旁边的关注按钮来关注其他用户。

如果访问的用户不是已经关注你的用户,那么应该有一个按钮来关注该用户。如果用户已经被关注,同样的按钮应该允许取消关注。

让我们编辑现有的用户个人资料,profile.html

对用户名添加用户图标,我们可以使用以下 Bootstrap 图标。这是默认 Bootstrap 附带的图标集。

  {% block navbar %}
  <p class="navbar-text navbar-right">
    <span class="glyphicon glyphicon-user"></span> {{ user.username }}
  </p>
  {% endblock %}

我们还将在个人资料页面上设计一个新的推文发布文本框。更新后的user_profile.html文件如下:

  {% extends "base.html" %}
  {% block navbar %}
  <p class="navbar-text navbar-right">
    <span class="glyphicon glyphicon-user"></span> {{ user.username }}
  </p>
  {% endblock %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-6 col-md-offset-3 column">
      <form id="search-form" action="post/" method="POST">{% csrf_token %}
        <div class="input-group">
          {{ form.text.errors }}
          {{ form.text }}
          {{ form.country.as_hidden }}
          <span class="input-group-btn">
            <button class="btn btn-default" type="submit">Post</button>
          </span>
        </div><!-- /input-group -->
      </form>
    </div>
    <h1>&nbsp;</h1>
    <div class="col-md-12 column">
      {% for tweet in tweets %}
      <div class="well">
        <span>{{ tweet.text }}</span>
      </div>
      {% endfor %}
    </div>
  </div>
  {% endblock %}

更新forms.py文件以呈现一个新表单:

class TweetForm(forms.Form):
  text = forms.CharField(widget=forms.Textarea(attrs={'rows': 1, 'cols': 85, 'class':'form-control', 'placeholder': 'Post a new Tweet'}), max_length=160)
  country = forms.CharField(widget=forms.HiddenInput())

表单的更新 UI 将如下所示:

用户关注者数据模型

要添加关注用户的功能,我们首先需要创建另一个用户。我们将遵循之前使用的相同方法,即通过 Django 管理员。

我们一直在推迟的一个非常重要的事情是用户登录和注册。没有它,关注功能无法使用。我们将首先实现 Django 登录,然后再转向关注功能。

用户登录模型

为了实现用户登录,我们需要为登录和注册添加默认 URL。我们将在urls.py文件中添加以下 URL 模式:

  url(r'^login/$', 'django.contrib.auth.views.login'),
  url(r'^logout/$', 'django.contrib.auth.views.logout')

现在,我们的urls.py文件将如下所示:

  from django.conf.urls import patterns, include, url
  from django.contrib import admin
  from tweet.views import Index, Profile, PostTweet, HashTagCloud, Search, SearchHashTag, HashTagJson
  admin.autodiscover()

  urlpatterns = patterns('',
    url(r'^$', Index.as_view()),
    url(r'^user/(\w+)/$', Profile.as_view()),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^user/(\w+)/post/$', PostTweet.as_view()),
    url(r'^hashTag/(\w+)/$', HashTagCloud.as_view()),
    url(r'^search/$', Search.as_view()),
    url(r'^search/hashTag$', SearchHashTag.as_view()),
    url(r'^hashtag.json$', HashTagJson.as_view()),
    url(r'^login/$', 'django.contrib.auth.views.login'),
    url(r'^logout/$', 'django.contrib.auth.views.logout')   
  )

登录和注销视图都有默认模板名称,分别为registration/login.htmlregistration/logged_out.html。因为这些视图是特定于用户而不是我们可重用的应用程序,我们将使用以下命令在mytweets项目内创建一个新的模板/registration 目录:

 $  mkdir -p mytweets/templates/registration

然后,创建一个简单的登录和注销页面。在login.html文件中使用以下代码片段:

  {% extends "base.html" %}
  {% block content %}
  {% if form.errors %}
  <p>Your username and password didn't match. Please try again.</p>
  {% endif %}
  <form method="post" action="{% url 'django.contrib.auth.views.login' %}">
    {% csrf_token %}
    <table>
      <tr>
        <td>{{ form.username.label_tag }}</td>
        <td>{{ form.username }}</td>
      </tr>
      <tr>
        <td>{{ form.password.label_tag }}</td>
        <td>{{ form.password }}</td>
      </tr>
    </table>
    <input type="submit" value="login"/>
    <input type="hidden" name="next" value="{{ next }}"/>
  </form>
  {% endblock %}

logout.html文件中使用以下代码片段:

  {% extends "base.html" %}
  {% block content %}
    You have been Logged out!
  {% endblock %}

我们刚刚启用了 Django 的默认身份验证系统。由于这是基本授权,它具有特定重定向的预定义 URL。例如,我们已经知道/login将把用户带到/registration/login.html页面。同样,一旦用户经过身份验证,他们将被重定向到 URLaccounts/profile。在我们的项目中,每个用户都有一个自定义的 URL。我们将在settings.py文件中更新这些条目

LOGIN_REDIRECT_URL = '/profile'
LOGIN_URL = 'django.contrib.auth.views.login'

为了保持简单,我们将只创建一个视图,该视图将带着经过身份验证的用户到个人资料,然后将用户重定向到他们的个人资料页面。基本上,我们将在有效身份验证后构造用户名的参数;换句话说,将在单独的类视图中生成/profile | /profile/<username>。为此,我们还需要创建以下 URL 条目:

  url(r'^profile/$', UserRedirect.as_view()),

以及Profile重定向类和get()方法如下:

class UserRedirect(View):
  def get(self, request):
  return HttpResponseRedirect('/user/'+request.user.username)

就是这样。现在每个已登录用户都将被重定向到他的个人资料页面。

现在,回到最初的问题,当用户访问另一个用户的个人资料时,他们将有选择关注该用户的个人资料;这意味着关注者将在他们的主页上获取所有发布的推文的更新。

一旦关注了用户,关注者将有选项取消关注该用户,如果用户访问自己的个人资料,他们应该根本看不到任何东西。

用户个人资料的更新代码如下:

  {% extends "base.html" %}
  {% block navbar %}
  <p class="navbar-text navbar-left">
    <span class="glyphicon glyphicon-user"> </span> {{ profile.username }}'s Profile Page
    {% if profile.username != user.username %}
    <span class="btn btn-xs btn-default follow-btn" title="Click to follow {{ profile.username }}">
    <input id="follow" type="hidden" name="follow" value="{{ profile.username }}">
    <span class="glyphicon glyphicon-plus"> </span> {% if following %} Unfollow {% else %} Follow {% endif %}</span>
    {% endif %}
  </p>
  <p class="navbar-text navbar-right">
    <span class="glyphicon glyphicon-user"></span> {{ user.username }}
  </p>
  {% endblock %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-6 col-md-offset-3 column">
      <form id="search-form" action="post/" method="POST">{% csrf_token %}
        <div class="input-group">
          {{ form.text.errors }}
          {{ form.text }}
          {{ form.country.as_hidden }}
          <span class="input-group-btn">
            <button class="btn btn-default" type="submit">Post</button>
          </span>
        </div>
        <!-- /input-group -->
      </form>
    </div>
    <h1>&nbsp;</h1>
    <div class="col-md-12 column">
      {% for tweet in tweets %}
      <div class="well">
        <span>{{ tweet.text }}</span>
      </div>
      {% endfor %}
    </div>
  </div>
  {% endblock %}

以下代码检查用户是否正在查看自己的个人资料;如果是,他们将不会看到关注按钮。它还检查已登录的用户是否正在关注他们访问的个人资料;如果是,将显示取消关注按钮,如果不是,将显示关注按钮。

  {% if profile.username != user.username %}
  <span class="btn btn-xs btn-default follow-btn" title="Click to follow {{ profile.username }}">
    <input id="follow" type="hidden" name="follow" value="{{ profile.username }}">
  <span class="glyphicon glyphicon-plus"> </span> {% if following %} Unfollow {% else %} Follow {% endif %}</span>
  {% endif %}

为了呈现更新后的视图,class Profile()也已更新如下:

class Profile(LoginRequiredMixin, View):
  """User Profile page reachable from /user/<username> URL"""
  def get(self, request, username):
    params = dict()
    userProfile = User.objects.get(username=username))
    userFollower = UserFollower.objects.get(user=userProfile)
    if userFollower.followers.filter(username=request.user.username).exists():
      params["following"] = True
    else:
      params["following"] = False
      form = TweetForm(initial={'country': 'Global'})
      search_form = SearchForm()
      tweets = Tweet.objects.filter(user=userProfile).order_by('-created_date')
      params["tweets"] = tweets
      params["profile"] = userProfile
      params["form"] = form
      params["search"] = search_form
      return render(request, 'profile.html', params)

以下代码检查已登录用户是否是正在访问的用户的关注者:

  if userFollower.followers.filter(username=request.user.username).exists():

添加或删除关注者

让我们为个人资料创建一个post()方法,根据参数添加或删除关注者:

  def post(self, request, username):
    follow = request.POST['follow']
    user = User.objects.get(username= request.user.username)))
    userProfile === User.objects.get(username=username)
    userFollower, status = UserFollower.objects.get_or_create(user=userProfile)
    if follow=='true':
      #follow user
      userFollower.followers.add(user)
    else:
      #unfollow user
      userFollower.followers.remove(user)
    return HttpResponse(json.dumps(""), content_type="application/json")

这是一个简单的函数,用于检查参数以将用户添加到或从关注者列表中删除。

profile.html文件中的关注按钮部分应更新为类名,以便我们可以触发 JavaScript 事件功能,如下所示:

<p class="navbar-text navbar-left">
  <span class="glyphicon glyphicon-user"> </span> {{ profile.username }}'s Profile Page
    {% if profile.username != user.username %}
    <span class="btn btn-xs btn-default follow-btn" title="Click to follow {{ profile.username }}" value="{{ following }}" username="{{ profile.username }}">
      <span class="glyphicon glyphicon-plus"></span><span class="follow-text">
      {{ following|yesno:"Unfollow,Follow" }}
    </span>
  </span>
  {% endif %}
</p>

最后,让我们创建profile.js文件,其中包含post()方法,每当单击关注/取消关注按钮时都会调用该方法:

创建一个名为profile.js的 JavaScript 文件,并添加以下代码:

$(".follow-btn").click(function () {
  var username = $(this).attr('username');
  var follow = $(this).attr('value') != "True";
  $.ajax({
    type: "POST",
    url:  "/user/"+username+"/",
    data: { username: username , follow : follow  },
    success: function () {
      window.location.reload();
    },
    error: function () {
      alert("ERROR !!");
    }
  })
});

不要忘记在页面底部的profile.html文件中添加此 JavaScript 文件,如下面的代码所示:

  {% block js %}
  <script src="img/profile.js' %}"></script>
  {% endblock %}

显示最受关注的用户

在我们实现了关注用户的功能之后,我们可以继续进行新页面设计,我们将在其中列出最受关注的用户。这个页面的逻辑可以被重用来设计具有最多评论数量的页面。

这个页面设计的基本组件包括:

  • 视图users.html文件

  • 控制器:最受关注的用户

  • URL 映射

view.html文件中添加以下内容:

  {% extends "base.html" %}
  {% load staticfiles %}
  {% block navbar %}
  <p class="navbar-text navbar-right">
    <span class="glyphicon glyphicon-user"></span> {{ user.username }}
  </p>
  {% endblock %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-12 column">
      {% for userFollower in userFollowers %}
      <div class="well">
        <span class="username">{{ userFollower.user.username }}</span>
        <span class="count text-muted"> ({{ userFollower.count }} followers)</span>
      </div>
      {% endfor %}
    </div>
  </div>
  {% endblock %}

在控制器中添加以下类:

class MostFollowedUsers(View):
  def get(self, request):
    userFollowers = UserFollower.objects.order_by('-count')
    params = dict()
    params['userFollowers'] = userFollowers
    return render(request, 'users.html', params)

以下一行按照拥有最多关注者的顺序对关注者进行排序:

  userFollowers = UserFollower.objects.order_by('-count')

我们还需要更新 URL 映射,如下所示:

  url(r'^mostFollowed/$', MostFollowedUsers.as_view()),

就这些了!我们已经完成了一个页面,其中所有用户都按关注者数量列出。如果数量太高,您还可以使用这种基本的 Python 列表语法进行限制:

  userFollowers = UserFollower.objects.order_by('-count')[:10]

这将只列出前 10 名用户。

摘要

在本章中,我们学习了如何创建登录、注销和注册页面模板。我们还学会了如何允许关注另一个用户并显示最受关注的用户。

下一章将转到新的主题。迟早,您将需要一个管理界面来管理应用程序的数据模型。幸运的是,Django 带有一个成熟的管理界面,可以立即使用。我们将在下一章中学习如何启用和自定义此界面,所以请继续阅读!

第八章:创建管理界面

在本章中,我们将学习使用 Django 的内置功能的管理员界面的特性。我们还将介绍如何以自定义方式显示推文,包括侧边栏或启用分页。本章将涉及以下主题:

  • 自定义管理界面

  • 自定义列表页面

  • 覆盖管理模板

  • 用户、组和权限

  • 用户权限

  • 组权限

  • 在视图中使用权限

  • 将内容组织成页面(分页)

自定义管理界面

Django 提供的管理界面非常强大和灵活,从 1.6 版本开始,默认情况下就已激活。这将为您的站点提供一个功能齐全的管理工具包。尽管管理应用程序对大多数需求应该足够了,但 Django 提供了几种自定义和增强它的方法。除了指定哪些模型可用于管理界面外,您还可以指定如何呈现列表页面,甚至覆盖用于呈现管理页面的模板。因此,让我们了解这些功能。

自定义列表页面

正如我们在上一章中看到的,我们使用以下方法将我们的模型类注册到管理界面:

  • admin.site.register (Tweet)

  • admin.site.register (Hashtag)

  • admin.site.register (UserFollower)

我们还可以自定义管理页面的几个方面。让我们通过示例来了解这一点。推文列表页面显示每条推文的字符串表示,如下面的屏幕截图所示:

自定义列表页面

如果此页面能够显示发布推文的用户的名称以及发布时间,那不是更有用吗?事实证明,实现这个功能只需要添加几行代码。

编辑tweet/admin.py文件中的推文模型如下:

  from django.contrib import admin
  from models import Tweet, HashTag
  from user_profile.models import UserFollower
  # Register your models here.
  admin.site.register(Tweet)
  admin.site.register(HashTag)
  admin.site.register(UserFollower)

在“#在此注册您的模型”上方添加新的代码行,更新后的代码将如下所示:

  from django.contrib import admin
  from models import Tweet, HashTag
  from user_profile.models import UserFollower
 class TweetAdmin(admin.ModelAdmin):
 list_display = ('user', 'text', 'created_date')
  # Register your models here.
  admin.site.register(Tweet, TweetAdmin)))
  admin.site.register(HashTag)
  admin.site.register(UserFollower)

此代码为TweetAdmin()类的管理员视图添加了额外的列:

  class TweetAdmin(admin.ModelAdmin):
    list_display = ('user', 'text', 'created_date')

此外,我们为管理员推文传递了一个额外的参数;即admin.site.register(Tweet)现在变成了admin.site.register(Tweet, TweetAdmin)。刷新同一页面,注意变化,如下面的屏幕截图所示:

自定义列表页面

表现得更有条理了!我们只需在Tweet模型的TweetAdmin()类中定义一个名为list_display的元组属性。该元组包含要在列表页面中使用的字段的名称。

在 Admin 类中还有其他属性可以定义;每个属性应该定义为一个或多个字段名称的元组。

  • list_filter:如果定义了,这将创建一个侧边栏,其中包含可以根据模型中一个或多个字段来过滤对象的链接。

  • ordering:用于在列表页面中对对象进行排序的字段。

  • search_fields:如果定义了,它将创建一个可用于搜索的搜索字段。字段名称前面加上减号,并且根据一个或多个字段的数据模型中的可用对象,使用降序而不是升序。

让我们在推文列表页面中利用前述每个属性。再次编辑tweet/admin.py文件中的推文模型,并追加以下突出显示的行:

  from django.contrib import admin
  from models import Tweet, HashTag
  from user_profile.models import UserFollower

  class TweetAdmin(admin.ModelAdmin):
    list_display = ('user', 'text', 'created_date')
 list_filter = ('user', )
 ordering = ('-created_date', )
 search_fields = ('text', )

  # Register your models here.
  admin.site.register(Tweet, TweetAdmin)
  admin.site.register(HashTag)
  admin.site.register(UserFollower)

使用这些属性后的效果如下:

自定义列表页面

正如您所看到的,我们能够只用几行代码来自定义和增强推文列表页面。接下来,我们将学习如何自定义用于呈现管理页面的模板,这将使我们对管理界面有更大的控制权。

覆盖管理模板

有时您想要更改管理界面的外观或移动各种管理页面上的元素并重新排列它们。幸运的是,管理界面足够灵活,可以通过允许我们覆盖其模板来执行所有这些操作及更多操作。自定义管理模板的过程很简单。首先,您将模板从管理应用程序文件夹复制到项目的模板文件夹中,然后编辑此模板并根据您的喜好进行自定义。管理模板的位置取决于 Django 的安装位置。以下是 Django 在主要操作系统下的默认安装路径列表:

  • Windows:C:\PythonXX\Lib\site-packages\django

  • UNIX 和 Linux:/usr/lib/pythonX.X/site-packages/django

  • Mac OS X:/Library/Python/X.X/site-packages/django

(这里,X.X是您系统上 Python 的版本。site-packages文件夹也可以被称为dist-packages。)

如果您在操作系统的默认安装路径中找不到 Django,请执行文件系统搜索django-admin.py。您会得到多个结果,但您想要的结果将在 Django 安装路径下,位于名为bin的文件夹内。

找到 Django 安装路径后,打开django/contrib/admin/templates/,您将找到管理应用程序使用的模板。

此目录中有许多文件,但最重要的文件是这些:

  • admin/base_site.html:这是管理的基本模板。此模板生成界面。所有页面都继承自以下模板。

  • admin/change_list.html:此模板生成可用对象的列表。

  • admin/change_form.html:此模板生成用于添加或编辑对象的表单。

  • admin/delete_confirmation.html:此模板在删除对象时生成确认页面。

让我们尝试自定义其中一个模板。假设我们想要更改所有管理页面顶部的字符串Django administration。为此,在我们项目的templates文件夹内创建一个名为admin的文件夹,并将admin/base_site.html文件复制到其中。然后,编辑文件以将所有Django的实例更改为Django Tweet

  {% extends "admin/base.html" %}
  {% load i18n %}
  {% block title %}{{ title|escape }} |
  {% trans 'Django Tweet site admin' %}{% endblock %}
  {% block branding %}
  <h1 id="site-name">{% trans 'Django Tweet administration' %}</h1>
  {% endblock %}
  {% block nav-global %}{% endblock %}

结果将如下所示:

覆盖管理模板

由于管理模板的模块化设计,通常不需要也不建议替换整个模板。通常最好只覆盖您需要更改的模板部分。

这个过程非常简单,不是吗?随意尝试其他模板。例如,您可能想要向列表或编辑页面添加帮助消息。

管理模板利用了 Django 模板系统的许多高级功能,因此如果您看到一个您不熟悉的模板标签,可以参考 Django 文档。

用户、组和权限

到目前为止,我们一直使用manage.py syncdb命令创建的超级用户帐户登录到管理界面。但实际上,您可能有其他受信任的用户需要访问管理页面。在本节中,我们将看到如何允许其他用户使用管理界面,并在此过程中了解更多关于 Django 权限系统的信息。

但在我们继续之前,我想强调一点:只有受信任的用户应该被授予对管理页面的访问权限。管理界面是一个非常强大的工具,所以只有你熟悉的人才应该被授予访问权限。

用户权限

如果数据库中除了超级用户之外没有其他用户,请使用我们在第七章中构建的注册表单创建一个新用户帐户,关注和评论。或者,您可以通过单击用户,然后单击添加用户来使用管理界面本身。

接下来,返回用户列表,然后单击新创建的用户的名称。您将获得一个表单,可用于编辑用户帐户的各个方面,例如姓名和电子邮件信息。在编辑表单的权限部分下,您将找到一个名为员工状态的复选框。启用此复选框将允许新用户进入管理界面。但是,他们登录后将无法做太多事情,因为此复选框仅授予对管理区域的访问权限;它不会赋予查看或更改数据的能力。

为了给新用户足够的权限来更改数据模型,您可以启用超级用户状态复选框,这将授予新用户执行任何所需功能的完全权限。此选项使帐户与manage.py syncdb命令创建的超级用户帐户一样强大。

然而,总的来说,不希望将用户对所有内容都授予完全访问权限。因此,Django 允许您通过权限系统对用户的操作进行精细控制。在超级用户状态复选框下方,您将找到可以授予用户的权限列表。如果您查看此列表,您将发现每个数据模型都有三种类型的权限:

  • 向数据模型添加对象

  • 更改数据模型中的对象

  • 从数据模型中删除对象

这些权限是由 Django 自动生成的,用于包含 Admin 类的数据模型。使用箭头按钮向我们正在编辑的帐户授予一些权限。例如,给予帐户添加、编辑和删除推文和主题标签的能力。接下来,注销然后使用新帐户再次登录到管理界面。您会注意到您只能管理推文和主题标签数据模型。

用户编辑页面的权限部分还包含一个名为活跃的复选框。此复选框可用作全局开关,用于启用或禁用帐户。取消选中时,用户将无法登录到主站点或管理区域。

组权限

如果您有大量共享相同权限的用户,编辑每个用户帐户并为他们分配相同权限将是一项繁琐且容易出错的任务。因此,Django 提供了另一个用户管理设施:组。简单来说,组是对共享相同权限的用户进行分类的一种方式。您可以创建一个组并为其分配权限。当您将用户添加到组时,该用户将被授予组的所有权限。

创建组与创建其他数据模型并没有太大的不同。在管理界面的主页上点击,然后点击添加组。接下来,输入组名并为组分配一些权限;最后,点击保存

要将用户添加到组中,请编辑用户帐户,滚动到编辑表单中的部分,然后选择要将用户添加到的任何组。

在视图中使用权限

尽管到目前为止我们只在管理界面中使用了权限,但 Django 还允许我们在编写视图时利用权限系统。在编写视图时,可以使用权限来授予一组用户对特定功能或页面的访问权限,例如私人内容。我们将在本节中了解可以用来实现此目的的方法。我们不会实际更改应用程序的代码,但如果您想尝试解释的方法,请随意这样做。

如果您想要检查用户是否具有特定权限,可以在User对象上使用has_perm()方法。该方法采用表示权限的字符串,格式如下:

app.operation_model

app参数指定了模型所在的应用程序的名称;operation参数可以是addchangedeletemodel参数指定了模型的名称。

例如,要检查用户是否可以添加推文,使用以下代码:

  user.has_perm('tweets.add_tweet')

要检查用户是否可以更改推文,使用以下代码:

  user.has_perm('tweets.change_tweet')

此外,Django 提供了一个名为decorator的函数,可以用来限制只有特定权限的用户才能访问视图。这个装饰器叫做permission_required,位于django.contrib.auth.decorators包中。

使用这个装饰器类似于我们使用login_required函数的方式。这个装饰器函数是为了限制页面只对已登录用户开放。假设我们想要将tweet_save_page视图(在tweets/views.py文件中)限制为具有tweet.add_tweet权限的用户。为此,我们可以使用以下代码:

from django.contrib.auth.decorators import permission_required
@permission_required('tweets.add_tweet', login_url="/login/")
def tweet_save_page(request):
  # [...]

这个装饰器接受两个参数:要检查的权限以及如果用户没有所需权限时要重定向用户的位置。

使用has_perm方法还是permission_required装饰器取决于您想要的控制级别。如果您需要控制对整个视图的访问权限,请使用permission_required装饰器。但是,如果您需要对视图内的权限进行更精细的控制,请使用has_perm方法。这两种方法应该足够满足任何权限相关的需求。

将内容组织成页面 - 分页

在之前的章节中,我们已经涵盖了列出用户的推文和列出最多关注的用户等内容,但是考虑到当这些小数字扩大并且我们开始获得大量结果时的使用情况。为了应对这种情况,我们应该调整我们的代码以支持分页。

页面的大小会增加,而在页面中找到项目将变得困难。幸运的是,这有一个简单直观的解决方案:分页。分页是将内容分成页面的过程。而且,正如以往一样,Django 已经有一个实现这个功能的组件,可以供我们使用!

如果我们有一大堆推文,我们将这些推文分成每页十个(左右)项目的页面,并向用户呈现第一页,并提供链接以浏览其他页面。

Django 分页功能封装在一个名为Paginator的类中,该类位于django.core.paginator包中。让我们使用交互式控制台来学习这个类的接口:

  from tweet.models import *
  from django.core.paginator import Paginator
  query_set = Tweet.objects.all()
  paginator = Paginator(query_set, 10)

注意

使用python manage.py shell命令打开 Django shell。

在这里,我们导入一些类,构建一个包含所有书签的查询集,并实例化一个名为Paginator的对象。这个类的构造函数接受要分页的查询集,以及每页的项目数。

让我们看看如何从Paginator对象中检索信息(当然,结果会根据您拥有的书签数量而有所不同):

>>> paginator.num_pages # Number of pages
1
>>> paginator.count # Total number of items
5
# Items in first page (index is zero-based)
>>> paginator.object_list
[<Tweet: #django is awesome.>, <Tweet: I love Django too.>, <Tweet: Django makes my day.>, <Tweet: #Django is fun.>, <Tweet: #Django is fun.>]

# Does the first page have a previous page?
>>> page1 = paginator.page(1)
# Stores the first page object to page1
>>> page1.has_previous()
False
# Does the first page have a next page?
>>> page1.has_next()
True

正如您所看到的,Paginator为我们做了大部分的工作。它接受一个查询集,将其分成页面,并使我们能够将查询集呈现为多个页面。

让我们将分页功能实现到我们的一个视图中,例如推文页面。打开tweet/views.py并修改user_page视图如下:

我们有我们的用户个人资料页面列表,其中包含以下类:

  class Profile(LoginRequiredMixin, View):
    """User Profile page reachable from /user/<username> URL"""
    def get(self, request, username):
      params = dict()
      userProfile = User.objects.get(username=username)
      userFollower = UserFollower.objects.get(user=userProfile)
      if userFollower.followers.filter(username=request.user.username).exists():
        params["following"] = True
      else:
        params["following"] = False
        form = TweetForm(initial={'country': 'Global'})
        search_form = SearchForm()
        tweets = Tweet.objects.filter(user=userProfile).order_by('-created_date')
        params["tweets"] = tweets
        params["profile"] = userProfile
        params["form"] = form
        params["search"] = search_form
        return render(request, 'profile.html', params)

我们需要修改前面的代码以使用分页:

  class Profile(LoginRequiredMixin, View):
    """User Profile page reachable from /user/<username> URL"""
    def get(self, request, username):
      params = dict()
      userProfile = User.objects.get(username=username)
      userFollower = UserFollower.objects.get(user=userProfile)
      if userFollower.followers.filter(username=request.user.username).exists():
        params["following"] = True
      else:
        params["following"] = False
        form = TweetForm(initial={'country': 'Global'})
        search_form = SearchForm()
        tweets = Tweet.objects.filter(user=userProfile).order_by('-created_date')
        paginator = Paginator(tweets, TWEET_PER_PAGE)
        page = request.GET.get('page')
      try:
        tweets = paginator.page(page)
        except PageNotAnInteger:
          # If page is not an integer, deliver first page.
          tweets = paginator.page(1)
      except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        tweets = paginator.page(paginator.num_pages)
        params["tweets"] = tweets
        params["profile"] = userProfile
        params["form"] = form
        params["search"] = search_form
        return render(request, 'profile.html', params)

以下代码片段主要在前面的代码中实现了分页的魔法:

        tweets = Tweet.objects.filter(user=userProfile).order_by('-created_date')
        paginator = Paginator(tweets, TWEET_PER_PAGE)
        page = request.GET.get('page')
        try:
          tweets = paginator.page(page)
        except PageNotAnInteger:
          # If page is not an integer, deliver first page.
          tweets = paginator.page(1)
        except EmptyPage:
          # If page is out of range (e.g. 9999), deliver last page of results.
          tweets = paginator.page(paginator.num_pages)

为了使这段代码工作,需要在settings.py文件中添加TWEET_PER_PAGE = 5参数,并在前面的代码中,只需在代码顶部添加import settings.py语句。

我们从请求中读取了一个名为pageget变量,告诉 Django 请求了哪个页面。我们还在settings.py文件中设置了TWEET_PER_PAGE参数,以显示单个页面上的推文数量。对于这种特定情况,我们选择了5

paginator = Paginator(tweets, TWEET_PER_PAGE)方法创建一个分页对象,其中包含有关查询的所有信息。

现在,只需使用 URL user/<username>/?page=<page_numer>,页面将如下截图所示。第一张图片显示了带有 URL 中页面编号的用户推文。

将内容组织成页面-分页

以下截图显示了用户主页上的推文列表:

将内容组织成页面-分页

总结

尽管本章相对较短,但我们学会了如何实现许多事情。这强调了 Django 让您只需几行代码就能做很多事情的事实。您学会了如何利用 Django 强大的管理界面,如何自定义它,以及如何利用 Django 提供的全面权限系统。

在下一章中,您将了解到几乎每个 Web 2.0 应用程序中都有的一些令人兴奋的功能。

第九章:扩展和部署

在本章中,我们将通过利用各种 Django 框架功能来准备我们的应用程序以在生产中部署。我们将添加对多种语言的支持,通过缓存和自动化测试来提高性能,并为生产环境配置项目。本章中有很多有趣和有用的信息,因此在将应用程序发布到网上之前,请确保您仔细阅读!

在本章中,您将学习以下主题:

  • 向朋友发送邀请电子邮件

  • 国际化(i18n)-提供多种语言的站点

  • 缓存-在高流量期间提高站点性能

  • 单元测试-自动化测试应用程序的过程

向朋友发送邀请电子邮件

使我们的用户邀请他们的朋友具有许多好处。如果他们的朋友已经使用我们的网站,那么他们更有可能加入我们的网站。加入后,他们还会邀请他们的朋友,依此类推,这意味着我们的应用程序会有越来越多的用户。因此,在我们的应用程序中包含“邀请朋友”的功能是一个好主意。

构建此功能需要以下组件:

  • 一个邀请数据模型,用于在数据库中存储邀请

  • 用户可以在其中输入他们朋友的电子邮件 ID 并发送邀请的表单

  • 带有激活链接的邀请电子邮件

  • 处理电子邮件中发送的激活链接的机制

在本节中,我们将实现这些组件中的每一个。但是,因为本节涉及发送电子邮件,我们首先需要通过向settings.py文件添加一些选项来配置 Django 发送电子邮件。因此,打开settings.py文件并添加以下行:

  SITE_HOST = '127.0.0.1:8000'
  DEFAULT_FROM_EMAIL = 'MyTwitter <noreply@mytwitter.com>'
  EMAIL_HOST = 'mail.yourisp.com'
  EMAIL_PORT = ''
  EMAIL_HOST_USER = 'username+mail.yourisp.com'
  EMAIL_HOST_PASSWORD = ''

让我们看看前面代码中的每个变量都做了什么:

  • SITE_HOST:这是您服务器的主机名。现在将其保留为127.0.0.1:8000。在下一章中部署服务器时,我们将更改此设置。

  • DEFAULT_FROM_EMAIL:这是出站电子邮件服务器From字段中显示的电子邮件地址。对于主机用户名,请输入您的用户名加上您的电子邮件服务器,如前面的代码片段所示。如果您的 ISP 不需要这些字段,请将其留空。

  • EMAIL_HOST:这是您的电子邮件服务器的主机名。

  • EMAIL_PORT:这是出站电子邮件服务器的端口号。如果将其留空,则将使用默认值(25)。您还需要从 ISP 那里获取此信息。

  • EMAIL_HOST_USEREMAIL_HOST_PASSWORD:这是 Django 发送的电子邮件的用户名和密码。

如果您的开发计算机没有运行邮件服务器,很可能是这种情况,那么您需要输入 ISP 的出站电子邮件服务器。联系您的 ISP 以获取更多信息。

要验证您的设置是否正确,请启动交互式 shell 并输入以下内容:

>>> from django.core.mail import EmailMessage
>>> email = EmailMessage('Hello', 'World', to=['your_email@example.com'])
>>> email.send()

your_email@example.com参数替换为您的实际电子邮件地址。如果前面的发送邮件调用没有引发异常并且您收到了邮件,那么一切都设置好了。否则,您需要与 ISP 验证您的设置并重试。

但是,如果您没有从 ISP 那里获得任何信息怎么办?然后我们尝试另一种方式:使用 Gmail 发送邮件(当然,不是作为noreply@mytweet.com,而是从您的真实电子邮件 ID)。让我们看看您需要对MyTweeets项目的settings.py文件进行哪些更改。

完全删除以前的settings.py文件条目,并添加以下内容:

  EMAIL_USE_TLS = True
  EMAIL_HOST = 'smtp.gmail.com'
  EMAIL_HOST_USER = 'your-gmail-email-id'
  EMAIL_HOST_PASSWORD = 'your-gmail-application-password'
  EMAIL_PORT = 587
  SITE_HOST = '127.0.0.1:8000'

如果您遇到错误,例如:

 (534, '5.7.9 Application-specific password required. Learn more at\n5.7.9 http://support.google.com/accounts/bin/answer.py?answer=185833 zr2sm8629305pbb.83 - gsmtp')

这意味着EMAIL_HOST_PASSWORD参数需要一个应用程序授权密码,而不是您的电子邮件密码。请按照主机部分中提到的链接获取有关如何创建的更多详细信息。

设置好这些东西后,尝试使用以下命令从 shell 再次发送邮件:

>>> from django.core.mail import EmailMessage
>>> email = EmailMessage('Hello', 'World', to=['your_email@example.com'])
>>> email.send()

在这里,your_email@example.com参数是您想发送邮件的任何电子邮件地址。邮件的发件人地址将是我们传递给以下变量的 Gmail 电子邮件地址:

 EMAIL_HOST_USER = 'your-gmail-email-id'

现在,一旦设置正确,使用 Django 发送邮件就像小菜一碟!我们将使用EmailMessage函数发送邀请邮件,但首先,让我们创建一个数据模型来存储邀请。

邀请数据模型

邀请包括以下信息:

  • 收件人姓名

  • 收件人邮箱

  • 发件人的用户对象

我们还需要为邀请存储一个激活码。该代码将在邀请邮件中发送。该代码将有两个目的:

  • 在接受邀请之前,我们可以使用该代码验证邀请是否实际存在于数据库中

  • 接受邀请后,我们可以使用该代码从数据库中检索邀请信息,并跟踪发件人和收件人之间的关系

考虑到上述信息,让我们创建邀请数据模型。打开user_profile/models.py文件,并将以下代码追加到其中:

  class Invitation(models.Model):
    name = models.CharField(maxlength=50)
    email = models.EmailField()
    code = models.CharField(maxlength=20)
    sender = models.ForeignKey(User)
    def __unicode__(self):
        return u'%s, %s' % (self.sender.username, self.email)

在这个模型中没有什么新的或难以理解的。我们只是为收件人姓名、收件人电子邮件、激活码和邀请发件人定义了字段。我们还为调试创建了一个__unicode__方法,并在管理界面中启用了该模型。不要忘记运行python manage.py syncdb命令来在数据库中创建新模型的表。

我们还将为此创建邀请表单。在user_profile目录中创建一个名为forms.py的文件,并使用以下代码进行更新:

from django import forms

class InvitationForm(forms.Form):
  email = forms.CharField(widget=forms.TextInput(attrs={'size': 32, 'placeholder': 'Email Address of Friend to invite.', 'class':'form-control search-query'}))

创建发送邀请的视图页面类似于创建我们为搜索和推文表单创建的其他页面,通过创建一个名为template/invite.html的新文件:

  {% extends "base.html" %}
  {% load staticfiles %}
  {% block content %}
  <div class="row clearfix">
    <div class="col-md-6 col-md-offset-3 column">
      {% if success == "1" %}
        <div class="alert alert-success" role="alert">Invitation Email was successfully sent to {{ email }}</div>
      {% endif %}
      {% if success == "0" %}
        <div class="alert alert-danger" role="alert">Failed to send Invitation Email to {{ email }}</div>
      {% endif %}
      <form id="search-form" action="" method="post">{% csrf_token %}
        <div class="input-group input-group-sm">
        {{ invite.email.errors }}
        {{ invite.email }}
          <span class="input-group-btn">
            <button class="btn btn-search" type="submit">Invite</button>
          </span>
        </div>
      </form>
    </div>
  </div>
  {% endblock %}

此方法的 URL 输入如下:

  url(r'^invite/$', Invite.as_view()),

现在,我们需要创建getpost方法来使用此表单发送邀请邮件。

由于发送邮件比推文更具体于用户,我们将在user_profile视图中创建此方法,而不是之前使用的推文视图。

使用以下代码更新user_profile/views.py文件:

from django.views.generic import View
from django.conf import settings
from django.shortcuts import render
from django.template import Context
from django.template.loader import render_to_string
from user_profile.forms import InvitationForm
from django.core.mail import EmailMultiAlternatives
from user_profile.models import Invitation, User
from django.http import HttpResponseRedirect
import hashlib

class Invite(View):
  def get(self, request):
    params = dict()
    success = request.GET.get('success')
    email = request.GET.get('email')
    invite = InvitationForm()
    params["invite"] = invite
    params["success"] = success
    params["email"] = email
    return render(request, 'invite.html', params)

  def post(self, request):
    form = InvitationForm(self.request.POST)
    if form.is_valid():
      email = form.cleaned_data['email']
      subject = 'Invitation to join MyTweet App'
      sender_name = request.user.username
      sender_email = request.user.email
      invite_code = Invite.generate_invite_code(email)
      link = 'http://%s/invite/accept/%s/' % (settings.SITE_HOST, invite_code)
      context = Context({"sender_name": sender_name, "sender_email": sender_email, "email": email, "link": link})
      invite_email_template = render_to_string('partials/_invite_email_template.html', context)
      msg = EmailMultiAlternatives(subject, invite_email_template, settings.EMAIL_HOST_USER, [email], cc=[settings.EMAIL_HOST_USER])
      user = User.objects.get(username=request.user.username)
      invitation = Invitation()
      invitation.email = email
      invitation.code = invite_code
      invitation.sender = user
      invitation.save()
      success = msg.send()
      return HttpResponseRedirect('/invite?success='+str(success)+'&email='+email)

  @staticmethod
  def generate_invite_code(email):
    secret = settings.SECRET_KEY
    if isinstance(email, unicode):
      email = email.encode('utf-8')
      activation_key = hashlib.sha1(secret+email).hexdigest()
      return activation_key

在这里,get()方法就像使用invite.html文件渲染邀请表单一样简单,并且初始未设置successemail变量。

post()方法使用通常的表单检查和变量提取概念;您将首次看到的代码如下:

  invite_code = Invite.generate_invite_code(email)

这实际上是一个静态函数调用,为每个受邀用户生成具有唯一密钥的激活令牌。当您加载名为_invite_email_template.html的模板并将以下变量传递给它时,render_to_string()方法将起作用:

  • sender_name:这是邀请或发件人的姓名

  • sender_email:这是发件人的电子邮件地址

  • email:这是被邀请人的电子邮件地址

  • link:这是邀请接受链接

然后使用该模板来渲染邀请邮件的正文。之后,我们使用EmailMultiAlternatives()方法发送邮件,就像我们在上一节的交互式会话中所做的那样。

这里有几点需要注意:

  • 激活链接的格式为http://SITE_HOST/invite/accept/CODE/。我们将在本节后面编写一个视图来处理此类 URL。

  • 这是我们第一次使用模板来渲染除网页以外的其他内容。正如您所见,模板系统非常灵活,允许我们构建电子邮件,以及网页或任何其他文本。

  • 我们使用render_to_string()render()方法构建消息正文,而不是通常的render_to_response调用。如果你还记得,这就是我们在本书早期渲染模板的方式。我们这样做是因为我们不是在渲染网页。

由于send方法加载名为_invite_email_template.html的模板,请在模板文件夹中创建一个同名文件并插入以下内容:

  Hi,
    {{ sender_name }}({{ sender_email }}) has invited you to join Mytweet.
    Please click {{ link }} to join.
This email was sent to {{ email }}. If you think this is a mistake Please ignore.

我们已经完成了“邀请朋友”功能的一半实现。目前,点击激活链接会产生 404 页面未找到错误,因此,接下来,我们将编写一个视图来处理它。

处理激活链接

我们取得了良好的进展;用户现在能够通过电子邮件邀请他们的朋友。下一步是构建一个处理邀请中激活链接的机制。以下是我们将要做的概述。

我们将构建一个视图来处理激活链接。此视图验证邀请码实际上是否存在于数据库中,并且注册的用户自动关注发送链接的用户并被重定向到注册页面。

让我们从为视图编写 URL 条目开始。打开urls.py文件并添加以下突出显示的行:

 url(r'^invite/accept/(\w+)/$', InviteAccept.as_view()),

user_profile/view.py文件中创建一个名为InviteAccept()的类。

从逻辑上讲,邀请接受将起作用,因为用户将被要求注册应用程序,如果他们已经注册,他们将被要求关注邀请他们的用户。

为了简单起见,我们将用户重定向到带有激活码的注册页面,这样当他们注册时,他们将自动成为关注者。让我们看一下以下代码:

class InviteAccept(View):
  def get(self, request, code):
    return HttpResponseRedirect('/register?code='+code)

然后,我们将用以下代码编写注册页面:

class Register(View):
  def get(self, request):
    params = dict()
    registration_form = RegisterForm()
    code = request.GET.get('code')
    params['code'] = code
    params['register'] = registration_form
    return render(request, 'registration/register.html', params)

  def post(self, request):
    form = RegisterForm(request.POST)
    if form.is_valid():
      username = form.cleaned_data['username']
      email = form.cleaned_data['email']
      password = form.cleaned_data['password']
      try:
        user = User.objects.get(username=username)                
      except:
        user = User()
        user.username = username
        user.email = email
        commit = True
        user = super(user, self).save(commit=False)
        user.set_password(password)
        if commit:
          user.save()
        return HttpResponseRedirect('/login')

如你所见,视图遵循邀请电子邮件中发送的 URL 格式。激活码是使用正则表达式从 URL 中捕获的,然后作为参数传递给视图。

这有点耗时,但我们能够充分利用我们的 Django 知识来实现它。您现在可以点击通过电子邮件收到的邀请链接,看看会发生什么。您将被重定向到注册页面;您可以在那里创建一个新账户,登录,并注意新账户和您的原始账户如何成为发送者的关注者。

国际化(i18n)-提供多种语言的网站

如果人们无法阅读我们应用的页面,他们就不会使用我们的应用。到目前为止,我们只关注说英语的用户。然而,全世界有许多人不懂英语或更喜欢使用他们的母语。为了吸引这些人,将我们应用的界面提供多种语言是个好主意。这将克服语言障碍,并为我们的应用打开新的前沿,特别是在英语不常用的地区。

正如你可能已经猜到的那样,Django 提供了将项目翻译成多种语言所需的所有组件。负责提供此功能的系统称为国际化系统i18n)。翻译 Django 项目的过程非常简单。

按照以下三个步骤进行:

  1. 指定应用程序中应翻译的字符串,例如,状态和错误消息是可翻译的,而用户名则不是。

  2. 为要支持的每种语言创建一个翻译文件。

  3. 启用和配置 i18n 系统。

我们将在以下各小节中详细介绍每个步骤。在本章节的最后,我们的应用将支持多种语言,您将能够轻松翻译任何其他 Django 项目。

将字符串标记为可翻译的

翻译应用程序的第一步是告诉 Django 哪些字符串应该被翻译。一般来说,视图和模板中的字符串需要被翻译,而用户输入的字符串则不需要。将字符串标记为可翻译是通过函数调用完成的。函数的名称以及调用方式取决于字符串的位置:在视图、模板、模型或表单中。

这一步比起一开始看起来要容易得多。让我们通过一个例子来了解它。我们将翻译应用程序中的“邀请关注者”功能。翻译应用程序的其余部分的过程将完全相同。打开user_profile/views.py文件,并对邀请视图进行突出显示的更改:

from django.utils.translation import ugettext as _
from django.views.generic import View
from django.conf import settings
from django.shortcuts import render
from django.template import Context
from django.template.loader import render_to_string
from user_profile.forms import InvitationForm
from django.core.mail import EmailMultiAlternatives
from user_profile.models import Invitation, User
from django.http import HttpResponseRedirect
import hashlib

class Invite(View):
  def get(self, request):
    params = dict()
    success = request.GET.get('success')
    email = request.GET.get('email')
    invite = InvitationForm()
    params["invite"] = invite
    params["success"] = success
    params["email"] = email
    return render(request, 'invite.html', params)

  def post(self, request):
    form = InvitationForm(self.request.POST)
    if form.is_valid():
      email = form.cleaned_data['email']
      subject = _('Invitation to join MyTweet App')
      sender_name = request.user.username
      sender_email = request.user.email
      invite_code = Invite.generate_invite_code(email)
      link = 'http://%s/invite/accept/%s/' % (settings.SITE_HOST, invite_code)
      context = Context({"sender_name": sender_name, "sender_email": sender_email, "email": email, "link": link})
      invite_email_template = render_to_string('partials/_invite_email_template.html', context)
      msg = EmailMultiAlternatives(subject, invite_email_template, settings.EMAIL_HOST_USER, [email], cc=[settings.EMAIL_HOST_USER])
      user = User.objects.get(username=request.user.username)
      invitation = Invitation()
      invitation.email = email
      invitation.code = invite_code
      invitation.sender = user
      invitation.save()
      success = msg.send()
    return HttpResponseRedirect('/invite?success='+str(success)+'&email='+email)

  @staticmethod
  def generate_invite_code(email):
    secret = settings.SECRET_KEY
    if isinstance(email, unicode):
      email = email.encode('utf-8')
      activation_key = hashlib.sha1(secret+email).hexdigest()
    return activation_key

请注意,主题字符串以“_”开头;或者,您也可以这样写:

from django.utils.translation import ugettext
  subject = ugettext('Invitation to join MyTweet App')

无论哪种方式,它都运行良好。

正如您所看到的,更改是微不足道的:

  • 我们从django.utils.translation中导入了一个名为ugettext的函数。

  • 我们使用了as关键字为函数(下划线字符)分配了一个更短的名称。我们这样做是因为这个函数将用于在视图中标记字符串为可翻译的,而且由于这是一个非常常见的任务,给函数一个更短的名称是个好主意。

  • 我们只需将一个字符串传递给_函数即可将其标记为可翻译。

这很简单,不是吗?然而,这里有一个小观察需要做。第一条消息使用了字符串格式化,并且在调用_()函数后应用了%运算符。这是为了避免翻译电子邮件地址。最好使用命名格式,这样在实际翻译时可以更好地控制。因此,您可能想要定义以下代码:

message= \
_('An invitation was sent to %(email)s.') % {
'email': invitation.email}

既然我们知道如何在视图中标记字符串为可翻译的,让我们转到模板。在模板文件夹中打开invite.html文件,并修改如下:

{% extends "base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block content %}
<div class="row clearfix">
  <div class="col-md-6 col-md-offset-3 column">
    {% if success == "1" %}
    <div class="alert alert-success" role="alert">
      {% trans Invitation Email was successfully sent to  %}{{ email }}
    </div>
    {% endif %}
    {% if success == "0" %}
    <div class="alert alert-danger" role="alert">Failed to send Invitation Email to {{ email }}</div>
    {% endif %}
      <form id="search-form" action="" method="post">{% csrf_token %}
        <div class="input-group input-group-sm">
        {{ invite.email.errors }}
        {{ invite.email }}
          <span class="input-group-btn">
            <button class="btn btn-search" type="submit">Invite</button>
          </span>
        </div>
      </form>
    </div>
  </div>
  {% endblock %}

在这里,我们在模板的开头放置了{% load i18n %}参数,以便让它可以访问翻译标签。<load>标签通常用于启用默认情况下不可用的额外模板标签。您需要在使用翻译标签的每个模板的顶部放置它。i18n 是国际化的缩写,这是 Django 框架的名称,它提供了翻译功能。

接下来,我们使用了一个名为trans的模板标签来标记字符串为可翻译的。这个模板标签与视图中的gettext函数完全相同。值得注意的是,如果字符串包含模板变量,trans标签将不起作用。在这种情况下,您需要使用blocktrans标签,如下所示:

{% blocktrans %} 

您可以在{% endblocktrans %}块中传递一个变量块,即{{ variable }},以使其对读者更有意义。

现在您知道如何在模板中处理可翻译的字符串了。那么,让我们转到表单和模型。在表单或模型中标记字符串为可翻译与在视图中略有不同。要了解如何完成这一点,请打开user_profile/forms.py文件,并修改邀请表单如下:

from django.utils.translation import gettext_lazy as _
class InvitationForm(forms.Form):
  email = forms.CharField(widget=forms.TextInput(attrs={'size': 32, 'placeholder': _('Email Address of Friend to invite.'), 'class':'form-control'}))

唯一的区别是我们导入了gettext_lazy函数而不是gettextgettext_lazy会延迟直到访问其返回值时才翻译字符串。这在这里是必要的,因为表单的属性只在应用程序启动时创建一次。如果我们使用普通的gettext函数,翻译后的标签将以默认语言(通常是英语)存储在表单属性中,并且永远不会再次翻译。但是,如果我们使用gettext_lazy函数,该函数将返回一个特殊对象,每次访问时都会翻译字符串,因此翻译将正确进行。这使得gettext_lazy函数非常适合表单和模型属性。

有了这个,我们完成了为“邀请朋友”视图标记字符串以进行翻译。为了帮助您记住本小节涵盖的内容,这里是标记可翻译字符串所使用的技术的快速总结:

  • 在视图中,使用gettext函数标记可翻译的字符串(通常导入为_

  • 在模板中,使用trans模板标记标记不包含变量的可翻译字符串,使用blocktrans标记标记包含变量的字符串。

  • 在表单和模型中,使用gettext_lazy函数标记可翻译的字符串(通常导入为_

当然,也有一些特殊情况可能需要单独处理。例如,您可能希望使用gettext_lazy函数而不是gettext函数来翻译视图中的默认参数值。只要您理解这两个函数之间的区别,您就应该能够决定何时需要这样做。

创建翻译文件

现在我们已经完成了标记要翻译的字符串,下一步是为我们想要支持的每种语言创建一个翻译文件。这个文件包含所有可翻译的字符串及其翻译,并使用 Django 提供的实用程序创建。

让我们创建一个翻译文件。首先,您需要在 Django 安装文件夹内的bin目录中找到一个名为make-messages.py的文件。找到它的最简单方法是使用操作系统中的搜索功能。找到它后,将其复制到系统路径(在 Linux 和 Mac OS X 中为/usr/bin/,在 Windows 中为c:\windows\)。

此外,确保在 Linux 和 Mac OS X 中运行以下命令使其可执行(对 Windows 用户来说,这一步是不需要的):

$ sudo chmod +x /usr/bin/make-messages.py

make-messages.py实用程序使用一个名为 GNU gettext 的软件包从源代码中提取可翻译的字符串。因此,您需要安装这个软件包。对于 Linux,搜索您的软件包管理器中的软件包并安装它。Windows 用户可以在gnuwin32.sourceforge.net/packages/gettext.htm找到该软件包的安装程序。

最后,Mac OS X 用户将在gettext.darwinports.com/找到适用于其操作系统的软件包版本以及安装说明。

安装 GNU gettext 软件包后,打开终端,转到您的项目文件夹,在那里创建一个名为locale的文件夹,然后运行以下命令:

$ make-messages.py -l de

这个命令为德语语言创建了一个翻译文件。de变量是德语的语言代码。如果您想要翻译其他语言,将其语言代码放在de的位置,并继续为本章的其余部分执行相同的操作。除此之外,如果您想要支持多种语言,为每种语言运行上一个命令,并将说明应用到本节的所有语言。

一旦您运行了上述命令,它将在locale/de/LC_MESSAGES/下创建一个名为django.po的文件。这是德语语言的翻译文件。在文本编辑器中打开它,看看它是什么样子的。文件以一些元数据开头,比如创建日期和字符集。之后,您会发现每个可翻译字符串的条目。每个条目包括字符串的文件名和行号,字符串本身,以及下面的空字符串,用于放置翻译。以下是文件中的一个示例条目:

#: user_profile/forms.py
msgid "Friend's Name"
msgstr ""

要翻译字符串,只需使用文本编辑器在第三行的空字符串中输入翻译。您也可以使用专门的翻译编辑器,比如Poedit(在www.poedit.net/上提供所有主要操作系统的版本),但对于我们的简单文件,普通文本编辑器就足够了。确保在文件的元数据部分设置一个有效的字符。我建议您使用UTF-8

"Content-Type: text/plain; charset=UTF-8\n"

您可能会注意到翻译文件包含一些来自管理界面的字符串。这是因为admin/base_site.html管理模板使用trans模板标记将其字符串标记为可翻译的。无需翻译这些字符串;Django 已经为它们提供了翻译文件。

翻译完成后,您需要将翻译文件编译为 Django 可以使用的格式。这是使用 Django 提供的另一个实用程序compile-messages.py命令完成的。找到并将此文件移动到系统路径,并确保它是可执行的,方法与我们使用make-messages.py命令相同。

接下来,在项目文件夹中运行以下命令:

$ compile-messages.py

如果实用程序报告文件中的错误(例如缺少引号),请更正错误并重试。一旦成功,实用程序将在同一文件夹中创建一个名为django.mo的已编译翻译文件,并为本节的下一步做好一切准备。

启用和配置 i18n 系统

Django 默认启用了 i18n 系统。您可以通过在settings.py文件中搜索以下行来验证这一点:

USE_I18N = True

有两种配置 i18n 系统的方法。您可以为所有用户全局设置语言,也可以让用户单独指定其首选语言。我们将在本小节中看到如何同时进行这两种配置。

要全局设置活动语言,请在settings.py文件中找到名为LANGUAGE_CODE的变量,并将您喜欢的语言代码分配给它。例如,如果您想将德语设置为项目的默认语言,请将语言代码更改如下:

LANGUAGE_CODE = 'de'

现在,如果开发服务器尚未运行,请启动它,并转到“邀请朋友”页面。在那里,您会发现字符串已根据您在德语翻译文件中输入的内容进行了更改。现在,将LANGUAGE_CODE变量的值更改为'en',并注意页面如何恢复为英语。

第二种配置方法是让用户选择语言。为此,我们应该启用一个名为LocaleMiddleware的类。简而言之,中间件是处理请求或响应对象的类。Django 的许多组件都使用中间件类来实现功能。要查看这一点,请打开settings.py文件并搜索MIDDLEWARE_CLASSES变量。您会在那里找到一个字符串列表,其中一个是django.contrib.sessions.middleware.SessionMiddleware,它将会话数据附加到请求对象上。在使用中间件之前,我们不需要了解中间件类是如何实现的。要启用LocaleMiddleware,只需将其类路径添加到MIDDLEWARE_CLASSES列表中。确保将LocaleMiddleware放在SessionMiddleware之后,因为区域设置中间件利用会话 API,我们将在下面看到。打开settings.py文件并按照以下代码片段中的突出显示的内容修改文件:

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.doc.XViewMiddleware',
'django.middleware.locale.LocaleMiddleware',
)

区域设置中间件通过以下步骤确定用户的活动语言:

  1. 它在会话数据中查找名为django_language的键。

  2. 如果键不存在,则查找名为django_language的 cookie。

  3. 如果 cookie 不存在,则查看 Accept-Language HTTP 标头中的语言代码。此标头由浏览器发送到 Web 服务器,指示您希望以哪种语言接收内容。

  4. 如果一切都失败了,将使用settings.py文件中的LANGUAGE_CODE变量。

在所有前面的步骤中,Django 会寻找与可用翻译文件匹配的语言代码。为了有效地利用区域设置中间件,我们需要一个视图,使用户能够选择语言并相应地更新会话数据。幸运的是,Django 已经为我们提供了这样的视图。该视图称为setlanguage,并且它期望在名为 language 的 GET 变量中包含语言代码。它使用此变量更新会话数据,并将用户重定向到原始页面。要启用此视图,请编辑urls.py文件,并向其中添加以下突出显示的行:

urlpatterns = patterns('',
# i18n
(r'^i18n/', include('django.conf.urls.i18n')),
)

添加上述行类似于我们为管理界面添加 URL 条目的方式。如果您还记得之前的章节,include()函数可以用于在特定路径下包含来自另一个应用程序的 URL 条目。现在,我们可以通过提供链接(例如/i18n/setlang/language=de)让用户将语言更改为德语。我们将修改基本模板以在所有页面上添加此类链接。打开templates/base.html文件,并向其中添加以下突出显示的行:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    [...]
  </head>
  <body>
    [...]
    <div id="footer">
    Django Mytweets <br />
    Languages:
      <a href="/i18n/setlang/?language=en">en</a>
      <a href="/i18n/setlang/?language=de">de</a>
      [ 218 ]Chapter 11
    </div>
  </body>
</html>

此外,我们将通过将以下 CSS 代码附加到site_media/style.css文件来为新的页脚设置样式:

#footer {
margin-top: 2em;
text-align: center;
}

现在,我们的应用程序的 i18n 功能已经准备就绪。将浏览器指向“邀请朋友”页面,并尝试页面底部的新语言链接。语言应该根据点击的链接而改变。

在我们结束本节之前,这里有一些观察结果:

  • 您可以在视图中使用请求LANGUAGE_CODE属性访问当前活动的语言。

  • Django 本身被翻译成多种语言。您可以通过在激活英语以外的语言时触发表单错误来查看这一点。错误消息将以所选语言显示,即使您自己没有进行翻译。

  • 在模板中,当使用RequestContext变量时,可以使用LANGUAGE_CODE模板变量访问当前活动的语言。

这一部分有点长,但您从中学到了一个非常重要的功能。通过以多种语言提供我们的应用程序,我们使其能够吸引更广泛的受众,从而具有吸引更多用户的潜力。这实际上适用于任何 Web 应用程序,现在,我们将能够轻松地将任何 Django 项目翻译成多种语言。

在下一节中,我们将转移到另一个主题。当您的应用程序用户基数增长时,服务器的负载将增加,您将开始寻找改进应用程序性能的方法。这就是缓存发挥作用的地方。

因此,请继续阅读以了解这个非常有用的技术!

缓存-在高流量期间提高站点性能

Web 应用程序的页面是动态生成的。每次请求页面时,都会执行代码来处理用户输入并生成输出。生成动态页面涉及许多开销,特别是与提供静态 HTML 文件相比。代码可能会连接到数据库,执行昂贵的计算,处理文件等等。同时,能够使用代码生成页面正是使网站动态和交互的原因。

如果我们能同时获得两全其美岂不是太好了?这就是缓存所做的,这是大多数中高流量网站上实现的功能。当请求页面时,缓存会存储页面的生成 HTML,并在以后再次请求相同页面时重用它。这样可以通过避免一遍又一遍地生成相同页面来减少很多开销。当然,缓存页面并不是永久存储的。当页面被缓存时,会为缓存设置一个过期时间。当缓存页面过期时,它会被删除,页面会被重新生成并缓存。过期时间通常在几秒到几分钟之间,取决于网站的流量。过期时间确保缓存定期更新,并且用户接收内容更新的同时,减少生成页面的开销。

尽管缓存对于中高流量网站特别有用,低流量网站也可以从中受益。如果网站突然接收到大量高流量,可能是因为它被主要新闻网站报道,您可以启用缓存以减少服务器负载,并帮助您的网站度过高流量的冲击。稍后,当流量平息时,您可以关闭缓存。因此,缓存对小型网站也很有用。您永远不知道何时会需要它,所以最好提前准备好这些信息。

启用缓存

我们将从启用缓存系统开始这一部分。要使用缓存,您首先需要选择一个缓存后端,并在 settings.py 文件中的一个名为 CACHE_BACKEND 的变量中指定您的选择。此变量的内容取决于您选择的缓存后端。一些可用的选项包括:

  • 简单缓存:对于这种情况,缓存数据存储在进程内存中。这只对开发过程中测试缓存系统有用,不应在生产中使用。要启用它,请在 settings.py 文件中添加以下内容:
CACHE_BACKEND = 'simple:///'
  • 数据库缓存:对于这种情况,缓存数据存储在数据库表中。要创建缓存表,请运行以下命令:
$ python manage.py createcachetable cache_table

然后,在 settings.py 文件中添加以下内容:

CACHE_BACKEND = 'db://cache_table'

在这里,缓存表被称为 cache_table。只要不与现有表冲突,您可以随意命名它。

  • 文件系统缓存:在这里,缓存数据存储在本地文件系统中。要使用它,请在 settings.py 文件中添加以下内容:
CACHE_BACKEND = 'file:///tmp/django_cache'

在这里,/tmp/django_cache 变量用于存储缓存文件。如果需要,您可以指定另一个路径。

  • Memcached:Memcached 是一个先进、高效和快速的缓存框架。安装和配置它超出了本书的范围,但如果您已经有一个可用的 Memcached 服务器,可以在 settings.py 文件中指定其 IP 和端口,如下所示:
CACHE_BACKEND = 'memcached://ip:port/'

如果您不确定在本节中选择哪个后端,请选择简单缓存。然而,实际上,如果您突然遇到高流量并希望提高服务器性能,可以选择 Memcached 或数据库缓存,具体取决于服务器上可用的选项。另一方面,如果您有一个中高流量的网站,我强烈建议您使用 Memcached,因为它绝对是 Django 可用的最快的缓存解决方案。本节中提供的信息无论您选择哪种缓存后端都是一样的。

因此,决定一个缓存后端,并在 settings.py 文件中插入相应的 CACHE_BACKEND 变量。接下来,您应该指定缓存页面的过期持续时间(以秒为单位)。在 settings.py 文件中添加以下内容,以便将页面缓存五分钟:

CACHE_MIDDLEWARE_SECONDS = 60 * 5

现在,我们已经完成了启用缓存系统。继续阅读,了解如何利用缓存来提高应用程序的性能。

配置缓存

您可以配置 Django 缓存整个站点或特定视图。我们将在本小节中学习如何做到这两点。

缓存整个站点

要缓存整个网站,请将CacheMiddleware类添加到settings.py文件中的MIDDLEWARE_CLASSES类中:

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.cache.CacheMiddleware',
'django.middleware.doc.XViewMiddleware',
'django.middleware.locale.LocaleMiddleware',
)

在这里顺序很重要,就像我们添加区域设置中间件时一样。缓存中间件类应该在会话和身份验证中间件类之后添加,在区域设置中间件类之前添加。

这就是您需要缓存 Django 网站的全部内容。从现在开始,每当请求页面时,Django 都会存储生成的 HTML 并在以后重复使用。重要的是要意识到,缓存系统只缓存没有GETPOST变量的页面。因此,我们的用户仍然可以发布推文和关注朋友,因为这些页面的视图期望 GET 或 POST 变量。另一方面,推文和标签列表等页面将被缓存。

缓存特定视图

有时,您可能只想缓存网站的特定页面-可能是一个与您的页面链接的高流量网站,因此大部分流量将被引导到这个特定页面。在这种情况下,只缓存此页面是有意义的。另一个适合缓存的好候选者是生成成本高昂的页面,因此您只希望每五分钟生成一次。我们应用程序中的标签云页面符合后一种情况。每次请求页面时,Django 都会遍历数据库中的所有标签,并计算每个标签的推文数量。这是一个昂贵的操作,因为它需要大量的数据库查询。因此,缓存这个视图是一个好主意。

要根据标签类缓存视图,只需应用一个名为cache_page的方法和与之相关的缓存参数。通过编辑mytweets/urls.py文件中的以下代码来尝试这一点:

from django.views.decorators.cache import cache_page
...
...
url(r'^search/hashTag$',  cache_page(60 * 15)(SearchHashTag.as_view())),
...
...

使用cache_page()方法很简单。它允许您指定要缓存的视图。站点缓存中提到的规则也适用于视图缓存。如果视图接收 GET 或 POST 参数,Django 将不会对其进行缓存。

有了这些信息,我们完成了本节。当您首次将网站发布到公众时,缓存是不必要的。然而,当您的网站增长,或者突然接收到大量高流量时,缓存系统肯定会派上用场。因此,在监视应用程序性能时要牢记这一点。

接下来,我们将学习 Django 测试框架。测试有时可能是一项乏味的任务。如果您可以运行一个命令来处理测试您的网站,那不是很好吗?Django 允许您这样做,我们将在下一节中学习。

模板片段可以以以下方式进行缓存:

 % load cache %}
 {% cache 500 sidebar %}
 .. sidebar ..
 {% endcache %}

单元测试-自动化测试应用程序的过程

在本书的过程中,我们有时修改了先前编写的视图。这在软件开发过程中经常发生。一个人可能会修改甚至重写一个函数来改变实现细节,因为需求已经改变,或者只是为了重构代码,使其更易读。

当您修改一个函数时,您必须再次测试它,以确保您的更改没有引入错误。然而,如果您不断重复相同的测试,测试将变得乏味。如果函数的各个方面没有很好地记录,您可能会忘记测试所有方面。显然,这不是一个理想的情况;我们绝对需要一个更好的机制来处理测试。

幸运的是,已经有了一个解决方案。它被称为单元测试。其思想是编写代码来测试您的代码。测试代码调用您的函数并验证它们的行为是否符合预期,然后打印出结果报告。您只需要编写一次测试代码。以后,每当您想要测试时,只需运行测试代码并检查生成的报告即可。

Python 自带了一个用于单元测试的框架。它位于单元测试模块中。Django 扩展了这个框架,以添加对视图测试的支持。我们将在本节中学习如何使用 Django 单元测试框架。

测试客户端

为了与视图交互,Django 提供了一个模拟浏览器功能的类。您可以使用它向应用程序发送请求并接收响应。让我们使用交互式控制台来学习。使用以下命令启动控制台:

$ python manage.py shell

导入Client()类,创建一个Client对象,并使用 GET 请求检索应用程序的主页:

>>>from django.test.client import Client
client = Client()
>>> response = client.get('/')
>>> print response

X-Frame-Options: SAMEORIGIN
Content-Type: text/html; charset=utf-8

<html>
 <head>
 <link href="/static/css/bootstrap.min.css"
 rel="stylesheet" media="screen">
 </head>
 <body>
 <nav class="navbar navbar-default" role="navigation">
 <a class="navbar-brand" href="#">MyTweets</a>
 </nav>
 <div class="container">
 </div>
 <nav class="navbar navbar-default navbar-fixed-bottom" role="navigation">
 <p class="navbar-text navbar-right">Footer </p>
 </nav>
 <script src="img/jquery-2.1.1.min.js"></script>
 <script src="img/bootstrap.min.js"></script>
 <script src="img/base.js"></script>
 </body>
</html>
>>> 

尝试向登录视图发送 POST 请求。输出将根据您是否提供正确的凭据而有所不同:

>>> print client.post('/login/',{'username': 'your_username', 'password': 'your_password'})

最后,如果有一个只允许已登录用户访问的视图,您可以像这样发送一个请求:

>>> print client.login('/friend/invite/', 'your_username', 'your_password')

如您从交互式会话中看到的,Client()类提供了三种方法:

  • get:这个方法向视图发送一个 GET 请求。它将视图的 URL 作为参数。您可以向该方法传递一个可选的 GET 变量字典。

  • post:这个方法向视图发送一个 POST 请求。它将视图的 URL 和一个 POST 变量字典作为参数。

  • login:这个方法向一个只允许已登录用户访问的视图发送一个 GET 请求。它将视图的 URL、用户名和密码作为参数。

Client()类是有状态的,这意味着它在请求之间保留其状态。一旦您登录,后续的请求将在您登录的状态下处理。Client()类的方法返回的响应对象包含以下属性:

  • status_code:这是响应的 HTTP 状态

  • content:这是响应页面的主体

  • template:这是用于渲染页面的Template实例;如果使用了多个模板,这个属性将是一个Template对象的列表

  • context:这是用于渲染模板的Context对象

这些字段对于检查测试是否成功或失败非常有用,接下来我们将看到。请随意尝试更多Client()类的用法。在继续下一小节之前,了解它的工作原理是很重要的,我们将在下一小节中创建第一个单元测试。

测试注册视图

现在您对Client()类感到满意了,让我们编写我们的第一个测试。单元测试应该位于应用程序文件夹内名为tests.py的模块中。每个测试应该是从django.test.TestCase模块派生的类中的一个方法。方法的名称必须以单词 test 开头。有了这个想法,我们将编写一个测试方法,试图注册一个新的用户帐户。因此,在bookmarks文件夹内创建一个名为tests.py的文件,并在其中输入以下内容:

from django.test import TestCase
from django.test.client import Client
class ViewTest(TestCase):
def setUp(self):
self.client = Client()
def test_register_page(self):
data = {
'username': 'test_user',
'email': 'test_user@example.com',
'password1': 'pass123',
'password2': 'pass123'
}
response = self.client.post('/register/', data)
self.assertEqual(response.status_code, 302)

让我们逐行查看代码:

  • 首先,我们导入了TestCaseClient类。

  • 接下来,我们定义了一个名为ViewTest()的类,它是从TestCase类派生的。正如我之前所说,所有测试类都必须从这个基类派生。

  • 之后,我们定义了一个名为setUp()的方法。当测试过程开始时,将调用这个方法。在这里,我们创建了一个Client对象。

  • 最后,我们定义了一个名为test_register_page的方法。方法的名称以单词 test 开头,表示它是一个测试方法。该方法向注册视图发送一个 POST 请求,并检查状态码是否等于数字302。这个数字是重定向的 HTTP 状态。

如果您回忆一下前面的章节,注册视图在请求成功时会重定向用户。

我们使用一个名为assertEqual()的方法来检查响应对象。这个方法是从TestCase类继承的。如果两个传递的参数不相等,它会引发一个异常。如果引发了异常,测试框架就知道测试失败了;否则,如果没有引发异常,它就认为测试成功了。

TestCase类提供了一组方法供测试使用。以下是一些重要的方法列表:

  • assertEqual:这期望两个值相等

  • assertNotEquals:这期望两个值不相等

  • assertTrue:这期望一个值为True

  • assertFalse:这期望一个值为False

现在您了解了测试类,让我们通过发出命令来运行实际测试:

$ python manage.py test

输出将类似于以下内容:

Creating test database...
Creating table auth_message
Creating table auth_group
Creating table auth_user
Creating table auth_permission
[...]
Loading 'initial_data' fixtures...
No fixtures found.
.
-------------------------------------------------------------
Ran 1 test in 0.170s
OK
Destroying test database...

那么,这里发生了什么?测试框架首先通过创建一个类似于真实数据库中的表的测试数据库来开始。接下来,它运行在测试模块中找到的测试。最后,它打印出结果的报告并销毁测试数据库。

在这里,我们的单个测试成功了。如果测试失败,输出会是什么样子,请修改tests.py文件中的test_register_page视图,删除一个必需的表单字段:

def test_register_page(self):
data = {
'username': 'test_user',
'email': 'test_user@example.com',
'password1': '1',
# 'password2': '1'
}
response = self.client.post('/register/', data)
self.assertEqual(response.status_code, 302)

现在,再次运行python manage.py test命令以查看结果:

=============================================================
FAIL: test_register_page (mytweets.user_profile.tests.ViewTest)
-------------------------------------------------------------
Traceback (most recent call last):
File "mytweets/user_profile/tests.py", line 19, in test_
register_page
self.assertEqual(response.status_code, 302)
AssertionError: 200 != 302
-------------------------------------------------------------
Ran 1 test in 0.170s
FAILED (failures=1)

我们的测试有效!Django 检测到错误并给了我们发生的确切细节。完成后不要忘记将测试恢复到原始形式。现在,让我们编写另一个测试,一个稍微更高级的测试,以更好地了解测试框架。

还有许多其他情景可以编写单元测试:

  • 检查注册是否失败,如果两个密码字段不匹配

  • 测试“添加朋友”和“邀请朋友”视图

  • 测试“编辑书签”功能

  • 测试搜索返回正确结果

上面的列表只是一些例子。编写单元测试以覆盖尽可能多的用例对于保持应用程序的健康和减少错误和回归非常重要。你编写的单元测试越多,当你的应用程序通过所有测试时,你就越有信心。Django 使单元测试变得非常容易,所以要充分利用这一点。

在应用程序的生命周期中的某个时刻,它将从开发模式转移到生产模式。下一节将解释如何为生产环境准备您的 Django 项目。

部署 Django

所以,你在你的 Web 应用程序上做了很多工作,现在是时候上线了。为了确保从开发到生产的过渡顺利进行,必须在应用程序上线之前进行一些更改。本节涵盖了这些更改,以帮助您成功上线您的 Web 应用程序。

生产 Web 服务器

在本书中,我们一直在使用 Django 自带的开发 Web 服务器。虽然这个服务器非常适合开发过程,但绝对不适合作为生产 Web 服务器,因为它并没有考虑安全性或性能。因此,它绝对不适合生产环境。

在选择 Web 服务器时,有几个选项可供选择,但Apache是迄今为止最受欢迎的选择,Django 开发团队实际上也推荐使用它。如何在 Apache 上设置 Django 的详细信息取决于您的托管解决方案。一些托管计划提供预配置的 Django 托管,您只需将项目文件复制到服务器上,而其他托管计划则允许您自己配置一切。

设置 Apache 的详细信息可能会因多种因素而有所不同,超出了本书的范围。如果最终需要自己配置 Apache,请参考 Django 文档www.djangoproject.com/documentation/apache_auth/以获取详细说明。

总结

本章涵盖了各种有趣的主题。在本章中,我们为项目开发了一组重要的功能。追随者的网络对于帮助用户社交和共享兴趣非常重要。我们了解了几个在部署 Django 时有用的 Django 框架。我们还学会了如何将 Django 项目从开发环境迁移到生产环境。值得注意的是,我们学到的这些框架都非常易于使用,因此您将能够在未来的项目中有效地利用它们。这些功能在 Web 2.0 应用程序中很常见,现在,您将能够将它们整合到任何 Django 网站中。

在下一章中,我们将学习如何改进应用程序的各个方面,主要是性能和本地化。我们还将学习如何在生产服务器上部署我们的项目。下一章将提供大量有用的信息,所以请继续阅读!

第十章:扩展 Django

到目前为止,我们已经走了很长的路,涉及了大量与 Django 功能相关的代码和基本概念。在本章中,我们将更多地讨论 Django,但我们将简要讨论不同的参数,例如自定义标签、过滤器、子框架、消息系统等。以下是本章将涉及的主题:

  • 自定义模板标签和过滤器

  • 基于类的通用视图

  • 贡献的子框架

  • 消息系统

  • 订阅系统

  • 用户分数

自定义模板标签和过滤器

Django 模板系统配备了许多模板标签和过滤器,使编写模板变得简单灵活。但是,有时您可能希望使用自己的标签和过滤器扩展模板系统。当您发现自己多次重复相同的标签结构时,希望将结构包装在单个标签中,甚至希望添加到模板系统中的过滤器时,通常会发生这种情况。

猜猜?Django 已经允许您这样做,而且这也很容易!您基本上只需向应用程序添加一个名为templatetags的新包,并将包含标签和过滤器的模块放入其中。让我们通过添加一个将字符串大写的过滤器来学习这一点。在mytweets父文件夹中添加一个templatetags文件夹,并在其中放置一个名为__init__.py的空文件,以便 Python 将该文件夹视为包。现在,在其中创建一个名为mytweet_filters的模块。我们将在此模块中编写我们的过滤器。以下是目录结构的示例:

templatetags/
  |-- __init__.py
  -- mytweet_filters.py

现在,将以下代码添加到mytweet_filters.py文件中:

  from django import template
  register = template.Library()

  @register.filter
  def capitalize(value):
    return value.capitalize()

register变量是一个对象,可用于向模板系统引入新的标签和过滤器。在这里,我们使用register.filter装饰器将 capitalize 函数添加为过滤器。

要在模板中使用新的过滤器,请在模板文件的开头放入以下行:

{% load mytweet_filters %}

然后,您可以像使用 Django 提供的任何其他过滤器一样使用新的过滤器:

Hi {{ name|capitalize }}!

添加自定义模板标签的工作方式与过滤器类似。基本上,您定义方法来处理标签,然后注册标签以使其可用于模板。这个过程稍微复杂一些,因为标签可能比过滤器更复杂。有关自定义模板标签的更多信息,请参阅 Django 在线文档。

在编写自定义过滤器时,您必须注意 Django 的自动转义行为。可以传递给过滤器的字符串有三种类型:

  • 原始字符串:此字符串是通过str命令准备的,或者是用 unicode 形成的。如果启用了自动转义,它们将自动转义。

  • 安全字符串:这些字符串是标记为免受进一步转义的字符串。它们不需要进一步转义。要将输出标记为安全字符串,请使用django.utils.safestring.mark_safe()模块。

  • 标记为“需要转义”的字符串:顾名思义,它们始终需要转义。

基于类的通用视图

在使用 Django 时,您会注意到无论您正在处理哪个项目,总是需要某些类型的视图。因此,Django 配备了一组可在任何项目中使用的视图。这些视图称为通用视图

Django 为以下目的提供了通用视图:

  • 为任务创建简单的视图,例如重定向到另一个 URL 或呈现模板

  • 列出和形成详细视图以显示数据模型中的对象-这些视图类似于管理页面显示数据模型的列表和详细页面

  • 生成基于日期的存档页面;这对博客特别有用

  • 创建,编辑和删除数据模型中的对象

Django 的基于类的视图可以通过定义子类或直接在 URL 配置中传递参数来配置。

子类充满了消除重写常见情况模板的约定。当您使用子类时,实际上可以通过提供新值来覆盖主类的属性或方法:

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

class ContactView(TemplateView):
  template_name = "contact.html"

我们还将在urls.py文件中添加其条目以进行重定向:

# project/urls.py
from django.conf.urls.defaults import *
from some_app.views import ContactView

urlpatterns = patterns('',
  (r'^connect/', ContactView.as_view()),
)

有趣的是,我们可以通过文件更改来实现相同的效果,并且只需在urls.py文件中添加以下内容即可:

from django.conf.urls.defaults import *
from django.views.generic import TemplateView

urlpatterns = patterns('',
  (r'^contact/', TemplateView.as_view(template_name="contact.html")),
)

贡献的子框架

django.contrib包含 Django 的标准库。我们在本书的前几章中使用了该软件包中的以下子框架:

  • admin: 这是 Django 管理界面

  • auth: 这是用户认证系统

  • sessions: 这是 Django 会话框架

  • syndication: 这是提要生成框架

这些子框架极大地简化了我们的工作,无论我们是创建注册和认证功能,构建管理页面,还是为我们的内容提供提要。django.contrib包是 Django 的一个非常重要的部分。了解其子包及如何使用它们将为您节省大量时间和精力。

本节将为您提供有关此软件包中其他框架的简要介绍。您不会深入了解如何使用每个框架,但您将学到足够的知识以了解何时使用框架。一旦您想在项目中使用框架,您可以阅读在线文档以了解更多信息。

Flatpages

Web 应用程序可能包含静态页面。例如,您的网站可能包括一组很少更改的帮助页面。Django 提供了一个名为flatpages的应用程序来提供静态页面。该应用程序非常简单;它为您提供了一个数据模型,用于存储有关每个页面的各种信息,包括以下内容:

  • URL

  • 标题

  • 内容

  • 模板名称

  • 查看页面是否需要注册

要使用该应用程序,您只需在settings.py文件中的INSTALLED_APPS变量中启用它,并将其中间件添加到MIDDLEWARE_CLASSES变量中。之后,您可以使用 flatpages 应用程序提供的数据模型存储和管理静态页面。

人性化

humanize应用程序提供了一组过滤器,以为您的页面增添人性化的触感。

以下是可用过滤器的列表:

  • apnumber: 对于 1-9 的数字,它返回拼写的数字。否则,它返回数字。换句话说,1 变成'one',9 变成'nine',以此类推,而 10 保持为 10。

  • intcomma: 这接受一个整数并将其转换为带有逗号的字符串,例如:

4500 becomes 4,500.
45000 becomes 45,000.
450000 becomes 450,000.
4500000 becomes 4,500,000.
  • intword: 这将整数转换为易于阅读的形式,例如:

1000000 变成 1.0 百万。

1200000 becomes 1.2 million.
1200000000 becomes 1.2 billion.
  • naturalday: 基于日期所在的范围,如果给定日期在(+1,0,-1)范围内,则分别显示日期为"明天","今天"和"昨天",例如,(如果今天是 2007 年 1 月 26 日):
25 Jan 2007 becomes yesterday.
26 Jan 2007 becomes today.
27 Jan 2007 becomes tomorrow.
  • naturaltime: 这返回一个表示事件日期发生多少秒、分钟或小时前的字符串,例如,(如果现在是 2007 年 1 月 26 日 16:30:00):
26 Jan 2007 16:30:00 becomes now.
26 Jan 2007 16:29:31 becomes 29 seconds ago.
26 Jan 2007 16:29:00 becomes a minute ago.
26 Jan 2007 16:25:35 becomes 4 minutes ago.
26 Jan 2007 15:30:29 becomes 59 minutes ago.
26 Jan 2007 15:30:01 becomes 59 minutes ago.
26 Jan 2007 15:30:00 becomes an hour ago.
26 Jan 2007 13:31:29 becomes 2 hours ago.
25 Jan 2007 13:31:29 becomes 1 day, 2 hours ago.
25 Jan 2007 13:30:01 becomes 1 day, 2 hours ago.
25 Jan 2007 13:30:00 becomes 1 day, 3 hours ago.
26 Jan 2007 16:30:30 becomes 30 seconds from now.
26 Jan 2007 16:30:29 becomes 29 seconds from now.
26 Jan 2007 16:31:00 becomes a minute from now.
26 Jan 2007 16:34:35 becomes 4 minutes from now.
26 Jan 2007 17:30:29 becomes an hour from now.
26 Jan 2007 18:31:29 becomes 2 hours from now.
27 Jan 2007 16:31:29 becomes 1 day from now.
  • ordinal: 这将整数转换为序数形式。例如,1 变成'1st',以此类推,每三个数字之间。

Sitemap

Sitemap是一个生成站点地图的框架,这些站点地图是帮助搜索引擎索引器在您的站点上找到动态页面的 XML 文件。它告诉索引器页面的重要性以及更改频率。这些信息使索引过程更准确和高效。

站点地图框架允许您用 Python 代码表示上述信息,然后生成代表您网站站点地图的 XML 文档。这涵盖了django.contrib包中最常用的子框架。该包包含一些不像前面那些重要的附加应用程序,并且会不时地更新新的应用程序。要了解django.contrib包中的任何应用程序,您可以随时阅读其在线文档。

跨站点请求伪造保护

我们在第五章中讨论了如何防止两种类型的 Web 攻击,即 SQL 注入和跨站点脚本。Django 提供了对抗另一种称为跨站点请求伪造的攻击的保护。在这种攻击中,恶意站点试图通过欺骗在您网站上登录的用户来操纵您的应用程序,使其打开一个特制的页面。该页面通常包含 JavaScript 代码,试图向您的网站提交表单。CSRF 保护通过将一个令牌(即秘密代码)嵌入到所有表单中,并在提交表单时验证该令牌来工作。这有效地使 CSRF 攻击变得不可行。

要激活 CSRF 保护,您只需要将'django.contrib.csrf.middleware.CsrfMiddleware'参数添加到MIDDLEWARE_CLASSES变量中,这将透明地工作,以防止 CSRF 攻击。

消息系统

我们的应用允许用户将彼此添加为好友并监视好友的书签。虽然这两种形式的通信与我们的书签应用程序的性质有关,但有时用户希望灵活地向彼此发送私人消息。这个功能对于增强我们网站的社交方面特别有用。

消息系统可以以多种方式实现。它可以简单到为每个用户提供一个联系表单,当提交时,通过发送其内容到用户的电子邮件来工作。您已经拥有构建此功能组件所需的所有信息:

  • 一个消息表单,其中包含主题的文本字段和消息正文的文本区域

  • 显示用户消息表单的视图,并通过send_mail()函数将表单内容发送给用户

当允许用户通过您的网站发送电子邮件时,您需要小心以防止滥用该功能。在这里,您可以将联系表单限制为仅限已登录的用户或仅限好友。

实现消息系统的另一种方法是在数据库中存储和管理消息。这样,用户可以使用我们的应用程序发送和查看消息,而不是使用电子邮件。虽然这种方法更加与我们的应用程序绑定,因此可以使用户留在我们的网站上,但需要更多的工作来实现。然而,与之前的方法一样,您已经拥有实现这种方法所需的所有信息。这里需要的组件如下:

  • 存储消息的数据模型。它应该包含发送者、接收者、主题和正文的字段。您还可以添加日期、阅读状态等字段。

  • 创建消息的表单。需要主题和正文的字段。

  • 列出可用消息的视图。

  • 显示消息的视图。

上述列表只是实现消息系统的一种方式。例如,您可以将列表和消息视图合并为一个视图,或者提供一个视图来显示已发送的消息以及已接收的消息。可能性很多,取决于您希望该功能有多高级。

订阅系统

我们提供了几种 Web 订阅,使用户能够监视我们网站的更新。然而,一些用户可能仍然更喜欢通过电子邮件监视更新的旧方式。对于这些用户,您可能希望将电子邮件订阅系统实施到应用程序中。例如,您可以让用户在朋友发布书签时收到通知,或者在特定标签下发布书签时收到通知。

此外,您可以将这些通知分组并批量发送,以避免发送大量的电子邮件。此功能的实现细节在很大程度上取决于您希望它如何工作。它可以是一个简单的数据模型,用于存储每个用户订阅的标签。它将循环遍历所有订阅特定标签的用户,并在此标签下发布书签时向他们发送通知。然而,这种方法太基础,会产生大量的电子邮件。更复杂的方法可能涉及将通知存储在数据模型中,并在每天发送一封电子邮件。

用户评分

一些网站(如Slashdot.orgreddit.com)通过为每个用户分配一个分数来跟踪用户的活动。每当用户以某种方式为网站做出贡献时,该分数就会增加。用户的分数可以以各种方式利用。例如,您可以首先向最活跃的用户发布新功能,或者为活跃用户提供其他优势,这将激励其他用户更多地为您的网站做出贡献。

实施用户评分非常简单。您需要一个数据模型来在数据库中维护评分。之后,您可以使用 Django 模型 API 从视图中访问和操作评分。

总结

本章的目的是为您准备本书未涵盖的任务。它向您介绍了许多主题。当需要某种功能时,您现在知道在哪里寻找框架,以帮助您快速而干净地实施该功能。

本章还为您提供了一些想法,您可能希望将其实施到我们的书签应用程序中。致力于这些功能将为您提供更多的机会来尝试 Django 并扩展您对其框架和内部工作原理的了解。

在下一章中,我们将介绍各种数据库连接的方式,如 MySQL、NoSQL、PostgreSQL 等,这对于任何基于数据库的应用程序都是必需的。

第十一章:数据库连接

Django 是一个数据库无关的框架,这意味着 Django 提供的数据库字段被设计为在不同的数据库中工作,比如 SQLite、Oracle、MySQL 和 PostgreSQL。事实上,它们也可以在几个第三方数据库后端上工作。PostgreSQL 是 Django 在生产中的一个很好的数据库,而 SQLite 用于开发环境,如果你不想为项目使用关系数据库管理系统(RDBMS),你将需要做很多工作。本章将详细介绍这两种类型的区别,并向您展示哪种更适合 Django,以及我们如何在 Django 项目中实际实现它们。

以下是本章将涉及的主题:

  • SQL 与 NoSQL

  • Django 与关系数据库

  • Django 与 NoSQL

  • 建立数据库系统

  • 单页应用项目 - URL 缩短器

首先,让我们看看 SQL 和 NoSQL 之间的区别。

SQL 与 NoSQL

SQL 数据库或关系数据库已经存在很长时间;事实上,直到新术语被创造出来之前,数据库大致被假定为 SQL 数据库,这个新术语就是 NoSQL。

好吧,我们正在谈论 SQL 和 NoSQL 之间的高级区别。以下是它们之间的区别:

SQL 数据库(RDBMS) NoSQL 数据库
SQL 数据库是关系数据库(RDBMS) NoSQL 数据库是非关系或分布式数据库
SQL 数据库基于表及其与其他表的关系 NoSQL 基于文档、键值对、图数据库或宽列存储
SQL 数据库将数据存储在表的行中 NoSQL 是一组键值对、文档、图数据库或宽列存储
SQL 数据库有预定义的模式 NoSQL 有动态模式
SQL 数据库是纵向可扩展的 NoSQL 数据库是横向可扩展的
SQL 数据库的例子有 MySQL、Oracle、SQLite、PostgreSQL 和 MS SQL NoSQL 数据库的例子有 MongoDB、BigTable、Redis、RavenDB、Cassandra、HBase、Neo4j 和 CouchDB

让我们试着了解一些著名的 SQL 和 NoSQL 数据库的基本特性。

SQL 数据库

以下部分涉及不同的 SQL 数据库及其用法。

MySQL - 开源

作为世界上最流行的数据库之一,MySQL 具有一些优点,使其适用于各种业务问题。以下是 MySQL 的一些重要优点:

  • 复制:MySQL 支持复制,通过复制 MySQL 数据库,可以显著减少一台机器的工作负载,并且可以轻松扩展应用程序

  • 分片:当写操作数量非常高时,分片通过将应用服务器分区来将数据库分成小块,有助于减轻负载

PostgreSQL

如前所述,PostgreSQL 是 Django 社区中最受欢迎的数据库。它也拥有核心支持的数据库中最广泛的功能集。

进化的 PostgresSQL 的高级查询和功能使得将复杂的传统 SQL 查询转换为更简单的查询变得可能。然而,使用传统的 SQL 数据库实现数组、hstore、JSON 等功能有点棘手。

NoSQL 数据库

这个概念是在水平扩展困难且基于 RDBMS 的数据库无法像预期的那样扩展时引入的。它通常被称为 Not only SQL。它提供了一种存储和检索数据的机制,而不是传统的 SQL 方法。

MongoDB

MongoDB 是最受欢迎的基于文档的 NoSQL 数据库之一,它以类似 JSON 的文档存储数据。它是一个非关系数据库,具有动态模式。它是由DoubleClick的创始人开发的。它是用C++编写的,目前被一些大公司使用,如纽约时报、Craigslist 和 MTV Networks。以下是 MongoDB 的一些优点和优势:

  • 速度:对于简单的查询,它具有良好的性能,因为所有相关数据都在一个单个文档中,消除了连接操作

  • 可扩展性:它是水平可扩展的,也就是说,您可以通过增加资源池中服务器的数量来减少工作负载,而不是依赖独立的资源

  • 易管理:对开发人员和管理员都很容易使用。这也使得 MondoDB 具有共享数据库的能力

  • 动态模式:它为您提供了在不修改现有数据的情况下演变数据模式的灵活性

CouchDB

CouchDB 也是一种基于文档的 NoSQL 数据库。它以 JSON 文档的形式存储数据。以下是 CouchDB 的一些优点和优势:

  • 无模式:作为 NoSQL 家族的一员,它也具有无模式的特性,使其更加灵活,因为它具有存储数据的 JSON 文档形式

  • HTTP 查询:您可以使用 Web 浏览器访问数据库文档

  • 冲突解决:它具有自动冲突,当您要使用分布式数据库时非常有用

  • 易复制:复制相当简单

Redis

Redis 是另一个开源的 NoSQL 数据库,主要因其闪电般的速度而被广泛使用。它是用 ANSI C 语言编写的。以下是 Redis 的一些优点和优势:

  • 数据结构:Redis 提供了高效的数据结构,有时被称为数据结构服务器。存储在数据库中的键可以是哈希、列表和字符串,并且可以是排序或无序集合。

  • Redis 作为缓存:您可以使用 Redis 作为缓存,通过实现具有有限时间的键来提高性能。

  • 非常快:它被认为是最快的 NoSQL 服务器之一,因为它使用内存数据集。

设置数据库系统

Django 支持多种数据库引擎。然而有趣的是,您只需要学习一个 API 就可以使用任何这些数据库系统。

这可能是因为 Django 的数据库层抽象了对数据库系统的访问。

稍后您将了解这一点,但是现在,您只需要知道无论您选择哪种数据库系统,都可以在不修改的情况下运行本书(或其他地方)开发的 Django 应用程序。

与客户端-服务器数据库系统不同,SQLite 不需要在内存中保留进程,并且将数据库存储在单个文件中,使其非常适合我们的开发环境。这就是为什么我们在整个项目中一直使用这个数据库,直到现在。当然,您可以自由选择使用您喜欢的数据库管理系统。我们可以通过编辑配置文件告诉 Django 使用哪个数据库系统。值得注意的是,如果您想使用 MySQL,您需要安装 MySQL,这是 Python 的 MySQL 驱动程序。

在 Django 中安装数据库系统非常简单;您只需要先安装要配置的数据库,然后在settings.py文件中添加几行配置,数据库设置就完成了。

设置 MySQL

我们将在接下来的几节中逐步安装和配置 MySQL 及其相关插件。

在 Linux - Debian 中安装 MySQL

在 Linux 中执行以下命令安装 MySQL(这里是 Debian):

sudo apt-get install mysql-server 

执行此命令后,将要求您设置 MySQL 并使用用户名和密码配置数据库。

安装 Python 的 MySQL 插件

要安装所需的与 MySQL 相关的插件,请使用以下命令:

pip install MySQL-python 

现在,打开settings.py文件,并添加以下行以使 Django 连接到 MySQL:

DATABASES = {
  'default': {
  'ENGINE': 'django.db.backends.mysql',
  'NAME': 'django_db',
  'USER': 'your_username',
  'PASSWORD': 'your_password',
  }
}

就是这样,现在你需要做的就是在新配置的数据库中重新创建所有表,并运行以下命令:

python manage.py syncdb 

注意

如果您尝试访问未定义的数据库,将会收到django.db.utils.ConnectionDoesNotExist异常。

Django 的优势在于您可以同时在多个数据库中使用它。

然而,您可能会想,为什么在同一个项目中需要多个数据库?

直到 NoSQL 数据库出现之前,在大多数情况下,通常使用同一个数据库来保存所有类型的数据记录,从关键数据(如用户详细信息)到转储数据(如日志);所有这些都保存在同一个数据库中,系统在扩展系统时面临挑战。

对于多数据库系统,一个理想的解决方案可能是将关系信息(例如用户、角色和其他帐户信息)存储在 SQL 数据库(如 MySQL)中。独立的应用程序数据可以存储在 NoSQL 数据库(如 MongoDB)中。

我们需要通过配置文件定义多个数据库。当您想要使用多个数据库与您使用的数据库服务器时,Django 需要告诉您。因此,在settings.py文件中,您需要使用数据库别名映射更改DATABASES设置。

多数据库配置的一个适当示例可以写成如下形式:

DATABASES = {
  'default': {
    'NAME': 'app_data',
    'ENGINE': 'django.db.backends.postgresql_psycopg2',
    'USER': 'postgres_user',
    'PASSWORD': 's3krit'
  },
  'users': {
    'NAME': 'user_data',
    'ENGINE': 'django.db.backends.mysql',
    'USER': 'mysql_user',
    'PASSWORD': 'priv4te'
  }
}

上述示例使用了两个数据库,分别是 PostgreSQL 和 MySQL,具有所需的凭据。

迁移和迁移的需求

迁移允许您通过创建代表模型更改的迁移文件来更新、更改和删除模型,并且可以在任何开发、暂存或生产数据库上运行。

Django 的模式迁移经历了漫长而复杂的历史;在过去的几年里,第三方应用South是唯一的选择。如果您考虑迁移的重要性,Django 1.7 发布时内置了迁移支持。

我们还需要了解 South 与 Django 迁移的区别。对于熟悉 South 的人来说,这应该感觉相当熟悉,可能会更清晰一些。为了方便参考,以下表格比较了旧的 South 工作流程和新的 Django 迁移工作流程:

步骤 South Django 迁移
初始迁移 运行 syncdb 然后 ./manage.py schemamigration <appname> --initial ./manage.py makemigrations <appname>
应用迁移 ./manage.py migrate <appname> ./manage.py migrate <appname>
非首次迁移 ./manage.py schemamigration <appname> --auto ./manage.py makemigration <appname>

因此,从表中我们可以看出,Django 迁移基本上遵循与 South 相同的流程,至少对于标准迁移流程来说,这只是简化了一些事情。

Django 迁移中的新功能

新的迁移代码将是 South 的改进版本,但将基于相同的概念,如下所示:

  • 每个应用程序的迁移

  • 自动检测变化

  • 数据迁移与模式迁移同时进行

让我们看一下以下术语列表,以了解 Django 迁移的优势:

  • 改进的迁移格式:改进的迁移格式可读性更强,因此可以在不实际执行的情况下进行优化或检查

  • 重置基线:在这种情况下,不需要每次保留或执行整个迁移历史,因为现在可以随着项目的增长创建新的第一次迁移

  • 改进的自动检测:新的和自定义字段更改将更容易被检测到,因为迁移将与改进的字段 API 一起构建

  • 更好的合并检测:新的迁移格式将自动解决不同版本控制系统分支之间的合并,如果我们能够合并这些更改,就不再需要任何工作

一旦您设置好项目并启动应用程序,也就是说,您的应用程序已经在数据库中生成了必要的表,您不应该对 Django 模型进行复杂的更改,也就是说,您不应该从一个类中删除属性。然而,在实际情况下,这是不可能的,因为您可能需要相应地更改您的模型类。在这种情况下,我们有一个解决这些问题的方法。这个过程被称为迁移,在 Django 中,这些迁移是通过一个叫做 South 的模块来完成的。

直到 Django 的 1.7 版本,即最新版本,您必须单独安装 south 模块。然而,自 Django 的 1.7 迁移以来,south 模块是一个内置模块。您可能一直在这样做,例如,当您使用以下命令更改(例如添加新属性)您的模型类时:

$python manage.py syncdb 

使用更新版本,manage.py syncdb已经被弃用以进行迁移,但如果您仍然喜欢旧的方式,现在可以使用。

后端支持

这对于任何用于生产的 Django 应用程序来说都是非常重要的,以获得迁移支持。因此,选择一个主要受迁移模块支持的数据库总是一个更好的决定。

一些最兼容的数据库如下:

  • PostgreSQL:在迁移或模式支持方面,PostgresSQL 是最兼容的数据库。

注意

您可以使用null=True初始化新列,因为这样可以更快地添加。

  • MySQL:MySQL 是一个广泛使用的数据库,因为 Django 无缝支持它。这里的问题是,在进行模式更改操作时,没有事务支持,也就是说,如果一个操作失败,您将不得不手动回滚更改。此外,对于每个模式更新,所有表都将被重写,这可能需要很长时间,重新启动应用程序可能需要很长时间。

  • SQLite:这是 Django 默认的数据库,主要用于开发目的。因此,它对以下情况的模式更改支持有限:

  • 创建新表

  • 数据复制

  • 删除旧表

  • 重命名表

如何进行迁移?

迁移主要是通过以下三个命令完成的,如下所示:

  • makemigrations:这是基于您对准备迁移查询的模型所做的更改

  • migrate:这将应用makemigrations查询准备的更改并列出它们的状态。

  • sqlmigrate:这显示了makemigrations查询准备的 SQL 查询

因此,Django 的模式迁移流程可以如下所述:

$python manage.py makemigrations 'app_name'

这将准备迁移文件,其外观类似于以下内容:

Migrations for 'app_name':
  0003_auto.py:
    - Alter field name on app_name

然后,在文件创建后,您可以检查目录结构。您将在migration文件夹下看到一个名为0003_auto.py的文件;您可以使用以下命令应用更改:

$ python manage.py migrate app_name

以下是您需要执行的操作:

Synchronize non migrated apps: sessions, admin, messages, auth, staticfiles, contenttypes
Apply all migrations: app_name
Synchronizing apps without migrations:
Creating tables...
Installing custom SQL...
Installing indexes...
Installed 0 object(s) from 0 fixture(s)
Running migrations:
Applying app_name.0003_auto... OK

OK消息表示迁移已成功应用。

为了使它更容易理解,迁移可以用以下图表来解释:

如何进行迁移?

有三个独立的实体:

  • 源代码

  • 迁移文件

  • 数据库

开发人员在源代码中进行更改,主要是在models.py文件中,并修改先前定义的模式。例如,当他们根据业务需求创建一个新字段,或者将 max_length 从 50 更新为 100。

我们将完成项目的适当迁移,以查看这个迁移实际上是如何工作的。

首先,我们必须创建应用程序的初始迁移:

$ python manage.py makemigrations tweet

其输出如下:

Migrations for 'tweet': 
0001_initial.py: 
- Create model HashTag 
- Create model Tweet 
- Add field tweet to hashtag 

这表明初始迁移已经创建。

现在,让我们改变我们的推文模态,现在如下所示:

text = models.CharField(max_length=160, null=False, blank=False)

我们将更改之前的推文模态为:

text = models.CharField(max_length=140, null=False, blank=False)

由于我们已经更改了我们的模式,现在我们必须进行迁移以正确运行应用程序。

从迁移流程中,我们了解到,现在我们必须运行makemigrations命令,如下所示:

$python manage.py makemigrations tweet

其输出如下:

Migrations for 'tweet': 
0002_auto_20141215_0808.py: 
- Alter field text on tweet 

正如你所看到的,它已经检测到了我们字段的更改。

为了验证,我们将打开我们的 SQL 数据库并检查 tweet 表的当前模式。

登录到 MySQL:

$mysql -u mysql_username -pmysql_password mytweets 

在 MySQL 控制台中,写入:

$mysql> desc tweet_tweet;

这将显示 tweet 表的模式,如下所示:

+-------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| text | varchar(160) | NO | | NULL | |
| created_date | datetime | NO | | NULL | |
| country | varchar(30) | NO | | NULL | |
| is_active | tinyint(1) | NO | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

由于我们还没有应用我们的迁移,数据库中明显显示字符字段中的文本为 160:

text | varchar(160) | NO | | NULL

我们在应用我们的迁移后将做完全相同的事情:

$python manage.py migrate tweet

以下是我们需要执行的操作:

Apply all migrations: tweet
Running migrations:
Applying tweet.0002_auto_20141215_0808... OK

我们的迁移已成功应用;让我们从数据库中验证一下。

要在tweet_tweet表上运行相同的 MySQL desc命令,请使用以下命令:

mysql> desc tweet_tweet;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| text | varchar(140) | YES | | NULL | |
| created_date | datetime | NO | | NULL | |
| country | varchar(30) | NO | | NULL | |
| is_active | tinyint(1) | NO | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

确实!我们的迁移已成功应用:

| text | varchar(140) | YES | | NULL | |

迁移如何知道要迁移什么

Django 永远不会在同一个数据库上运行两次迁移,这意味着它会保留这些信息。这些信息由一个名为django_migrations的表管理,它是在第一次启动 Django 应用程序时创建的,之后每次迁移都会插入一行新数据。

例如,运行我们的迁移后,表格可能会看起来像这样:

mysql> select * from django_migrations;
+----+-------+-------------------------+---------------------+
| id | app | name | applied |
+----+-------+-------------------------+---------------------+
| 1 | tweet | 0001_initial | 2014-12-15 08:02:34 |
| 2 | tweet | 0002_auto_20141215_0808 | 2014-12-15 08:13:19 |
+----+-------+-------------------------+---------------------+

前面的表格显示了有两个带有标记信息的迁移,并且每次迁移时,它都会跳过这些更改,因为这个表中已经有了对应于该迁移文件的条目。

这意味着即使你手动更改迁移文件,它也会被跳过。

这是有道理的,因为通常你不希望运行两次迁移。

然而,如果出于某种原因你真的想要应用两次迁移,你可以简单地删除表格条目中的"THIS IS NOT A OFFICIALLY RECOMMENDED WAY",它将正常工作。

相反,如果你想要撤消特定应用的所有迁移,你可以迁移到一个名为 zero 的特殊迁移。

例如,如果你键入,tweet 应用的所有迁移将被撤销:

$python manage.py migrate tweet zero

除了使用 zero,你还可以使用任意的迁移,如果那个迁移在过去,那么数据库将回滚到那个迁移的状态,或者如果还没有运行该迁移,那么数据库将向前滚动。

迁移文件

那么,迁移文件包含什么,当我们运行以下命令时到底发生了什么?

$python manage.py migrate tweet 

运行完这个命令后,你会看到一个名为migrations的目录,里面存储着所有的迁移文件。让我们来看看它们。由于它们是 Python 文件,可能很容易理解。

打开tweet/migrations/0001_initial.py文件,因为这是初始迁移代码创建的文件。它应该看起来类似于以下内容:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

class Migration(migrations.Migration):
dependencies = [
  ('user_profile', '__first__'),
]

operations = [
  migrations.CreateModel(
  name='HashTag',
  fields=[
    ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
    ('name', models.CharField(unique=True, max_length=64)),
  ],
  options = {
  },
  bases=(models.Model,),
  ),
  migrations.CreateModel(
  name='Tweet',
  fields=[
    ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
    ('text', models.CharField(max_length=160)),
    ('created_date', models.DateTimeField(auto_now_add=True)),
    ('country', models.CharField(default=b'Global', max_length=30)),
    ('is_active', models.BooleanField(default=True)),
    ('user', models.ForeignKey(to='user_profile.User')),
  ],
  options = {
  },
  bases=(models.Model,),
  ),
  migrations.AddField(
    model_name='hashtag',
    name='tweet',
    field=models.ManyToManyField(to='tweet.Tweet'),
    preserve_default=True,
  ),
]

要使迁移实际工作,必须有一个名为Migration()的类,它继承自django.db.migrations.Migration模块。这是用于迁移框架的主要类,这个迁移类包含两个主要列表,如下所示:

  • 依赖项:这是必须在迁移开始之前运行的其他迁移的列表。在存在依赖关系的情况下,比如外键关系的情况下,外键模型必须在其键被添加到这里之前存在。在前面的情况下,我们对user_profile参数有这样的依赖。

  • 操作:这个列表包含要应用的迁移列表,整个迁移操作可以属于以下类别:

  • CreateModel:从名称本身,很明显这将创建一个新模型。从前面的模型文件中,你可以看到这样的行:

migrations.CreateModel(
name='HashTag',....
migrations.CreateModel(
name='Tweet',..

这些迁移行创建了具有定义属性的新模型。

  • DeleteModel:这将包含从数据库中删除模型的语句。这些与CreateModel方法相反。

  • RenameModel:这将使用给定的新名称从旧名称重命名模型。

  • AlterModelTable:这将更改与模型关联的表的名称。

  • AlterUniqueTogether:这是更改的表的唯一约束。

  • AlteIndexTogether:这将更改模型的自定义索引集。

  • AddField:这只是向现有模型添加新字段。

  • RemoveField:这将从模型中删除字段。

  • RenameField:这将为模型将字段名称从旧名称重命名为新名称。

在更新应用程序时,模式的迁移不是唯一需要迁移的事情;还有另一件重要的事情叫做数据迁移。这是由先前操作已经存储在数据库中的数据,因此也需要迁移。

数据迁移可以在许多情况下使用。其中,最合乎逻辑的情况是:

  • 将外部数据加载到应用程序中

  • 当模型架构发生变化并且数据集也需要更新时

让我们通过从username.txt文件中加载推文来玩耍我们的项目。使用以下命令为我们的项目创建一个空迁移:

$python manage.py makemigrations --empty tweet

这将生成一个名为mytweets/migrations/003_auto<date_time_stamp>.py的迁移文件。

打开这个文件;它看起来像下面这样:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

dependencies = [
  ('tweet', '0002_auto_20141215_0808'),
]

operations = [
]

这只是 Django 迁移工具的基本结构,要进行数据迁移,我们必须在操作中添加RunPython()函数,如下所示:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

def load_data(apps, schema_editor):
  Tweet(text='This is sample Tweet',
    created_date=date(2013,11,29),
    country='India',
    is_active=True,
  ).save()

class Migration(migrations.Migration):

dependencies = [
  ('tweet', '0002_auto_20141215_0808'),
]

operations = [
  migrations.RunPython(load_data)
]

就这些了。现在,运行迁移命令:

$python manage.py migrate

这些是您需要执行的操作:

Synchronize unmigrated apps: user_profile
Apply all migrations: admin, contenttypes, tweet, auth, sessions
Synchronizing apps without migrations:
Creating tables...
Installing custom SQL...
Installing indexes...
Running migrations:
Applying contenttypes.0001_initial... FAKED
Applying auth.0001_initial... FAKED
Applying admin.0001_initial... FAKED
Applying sessions.0001_initial... FAKED
Applying tweet.0003_auto_20141215_1349... OK

执行上述命令后,该命令迁移了所有应用程序,并最终应用了我们创建新推文的迁移,从加载的数据中创建了新推文:

mysql> select * from tweet_tweet;
+----+---------+---------------------------------------------+---------------------+---------+-----------+
| id | user_id | text | created_date | country | is_active |
+----+---------+---------------------------------------------+---------------------+---------+-----------+
| 1 | 1 | This Tweet was uploaded from the file. | 2014-12-15 14:17:42 | India | 1 |
+----+---------+---------------------------------------------+---------------------+---------+-----------+
2 rows in set (0.00 sec)

很棒,对吧?

当您有以 JSON 或 XML 文件形式的外部数据时,这种解决方案非常必要。

理想的解决方案是使用命令行参数来获取文件路径并加载数据,如下所示:

$python load data tweet/initial_data.json

不要忘记将迁移文件夹添加到 Git 中,因为它们与源代码一样重要。

Django 与 NoSQL

Django 并不正式支持 NoSQL 数据库,但是在有这么多开发者的伟大社区的支持下,Django 有一个支持MongoDB作为后端数据库的分支。

为了说明问题,我们将使用 Django-Norel 项目来配置 Django 与 MongoDB 数据库。

您可以在django-nonrel.org/找到关于此的详细信息。

可以按照docs.mongodb.org/manual/installation/中提到的步骤安装 MongoDB,根据您的配置。

在这里,我们将为 Linux 的 Debian 版本(具体来说是 Ubuntu)设置 MongoDB。

导入 MongoDB 公共 GPG 密钥:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10

为 MongoDB 创建一个列表文件:

echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list

重新加载本地软件包数据库:

sudo apt-get update

安装 MongoDB 软件包:

sudo apt-get install -y mongodb-org

启动 MongoDB:

sudo service mongod start

单页面应用项目 - URL 缩短器

MongoDB 可以与 Django 一起使用的两种方式如下:

  • MongoEngine:这是一个文档对象映射器(类似于 ORM,但用于文档数据库),用于从 Python 与 MongoDB 一起使用。

  • Django non-rel:这是一个支持 Django 在非关系型(NoSQL)数据库上的项目;目前支持 MongoDB。

MongoEngine

在我们继续展示如何配置 MongoEngine 与 Django 之前,需要安装 MongoEngine。通过输入以下命令来安装 MongoEngine:

sudo pip install mongoengine 

为了保护我们之前创建的项目,并更好地理解,我们将创建一个单独的新项目来配置 MongoDB,并且我们将使用现有项目来配置 MySQL:

$django-admin.py startproject url_shortner
$cd url_shortner
$python manage.py startapp url

这将创建项目的基本结构,我们非常了解。

将 MongoDB 连接到 Django

我们将不得不修改settings.py文件,如果我们只在项目中使用 MognoDB,这在这种情况下是正确的,那么我们可以忽略标准数据库设置。我们所要做的就是在settings.py文件上调用connect()方法。

我们将为 MongoDB 放置一个虚拟后端。只需在settings.py文件中替换以下代码,如下所示:

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

用以下内容替换上述代码:

DATABASES = {
  'default': {
  'ENGINE': 'django.db.backends.dummy'
  }
}

Django 中的身份验证

MongoEngine 的优势在于它包括了 Django 身份验证后端。

用户模型成为 MongoDB 文档,并实现了大部分普通 Django 用户模型的方法和属性,这使得 MongoEngine 与 Django 兼容。我们还可以使用身份验证基础设施和装饰器,例如login_required()authentication()方法。auth模块还包含get_user()方法,它接受用户 ID 作为参数并返回用户对象。

要为 MognoEngine 启用此后端,请在settings.py文件中添加以下内容:

AUTHENTICATION_BACKENDS = (
  'mongoengine.django.auth.MongoEngineBackend',
)

存储会话

在 Django 中,您可以使用不同的数据库来存储应用程序的会话。要启用存储在 MongoDB 中的 MongoEngine 会话,settings.py文件中的MIDDLEWARE_CLASSES必须有django.contrib.sessions.middleware.SessionMiddleware参数的条目。还必须在INSTALLED_APPS中有django.contrib.sessions的条目,因为我们是从 Django 的基本结构开始的。

现在,您只需要在settings.py文件中添加以下行:

SESSION_ENGINE = 'mongoengine.django.sessions'
SESSION_SERIALIZER = 'mongoengine.django.sessions.BSONSerializer'

我们现在已经准备好开始一个小型演示项目,在其中我们将在 MongoDB 中实现 URL 缩短项目。

让我们首先创建一个 URL 模型,我们将在其中存储所有长 URL 及其对应的短 URL。

转到以下url/models.py文件:

from django.db import models
from mongoengine import *
connect('urlShortener')

您已经熟悉了上述代码的前两行,它们导入了模块。

第三行,即connect('urlShortener'),将 Django 连接到名为urlShortener的 MongoDB 数据库。

MongoDB 提供了许多连接机制供您选择,它们如下:

from mongoengine import connect
connect('project1')

我们正在使用的方法将 MongoDB 从其默认端口(27017)中获取;如果您在其他端口上运行 MongoDB,请使用connect()方法进行连接:

connect('project1', host='192.168.1.35', port=12345)

如果您为 MongoDB 配置了密码,可以传递参数如下:

connect('project1', username='webapp', password='pwd123')

像 Django 的默认模型字段一样,MongoDB 也为您提供了不同的字段,它们是:

  • BinaryField:此字段用于存储原始二进制数据。

  • BooleanField:这是一个布尔字段类型。

  • DateTimeField:这是一个日期时间字段。

  • ComplexDateTimeField:这样处理微秒,而不是像DateTimeField那样将它们四舍五入。

  • DecimalField:这是一个固定小数点十进制数字段。

  • DictField:这是一个包装了标准 Python 字典的字典字段。这类似于嵌入式文档,但结构未定义。

  • DynamicField:这是一种真正动态的字段类型,能够处理不同和多样化的数据类型。

  • EmailField:这是一个验证输入为电子邮件地址的字段。

  • FileField:这是一个 GridFS 存储字段。

  • FloatField:这是一个浮点数字段。

  • GeoPointField:这是一个存储经度和纬度坐标的列表。

  • ImageField:这是图像文件存储字段。

  • IntField:这是一个 32 位整数字段。

  • ListField:这是一个列表字段,它包装了一个标准字段,允许在数据库中使用字段的多个实例作为列表。

  • MapField:这是一个将名称映射到指定字段类型的字段。这类似于DictField,只是每个项目的“值”必须与指定的字段类型匹配。

  • ObjectIdField:这是 MongoDB 对象 ID 的字段包装器。

  • StringField:这是一个 Unicode 字符串字段。

  • URLField:这是一个验证输入为 URL 等的字段。

注意

默认情况下,字段不是必需的。要使字段成为必需字段,请将字段的 required 关键字参数设置为True。字段还可以具有可用的验证约束(例如,前面示例中的 max_length)。字段还可以采用默认值,如果未提供值,则将使用默认值。默认值可以选择是可调用的,将调用以检索值(如前面的示例)。

可以在docs.mongoengine.org/en/latest/apireference.html上看到完整的不同字段列表。

现在,我们将创建我们的Url()类,它将类似于我们迄今为止创建的其他模型,比如推文等等:

class Url(Document):
full_url = URLField(required=True)
short_url = StringField(max_length=50, primary_key=True, unique=True)
date = models.DateTimeField(auto_now_add=True)

让我们来看一下以下术语列表:

  • full_url:这是一个 URL 字段,将存储完整的 URL,以及触发其短 URL 时请求将重定向的相同 URL

  • short_url:这是相应长 URL 的短 URL

  • date:这将存储Url对象创建的日期。

现在,我们将转到视图并创建两个类:

  • 索引:在这里,用户可以生成短链接。这也将有一个post()方法,保存每个长 URL。

  • 链接:这是短 URL 重定向控制器。当查询短 URL 时,此控制器将请求重定向到长 URL,如下面的代码片段所示:

class Index(View):
def get(self, request):
return render(request, 'base.html')

def post(self, request):
long_url = request.POST['longurl']
short_id = str(Url.objects.count() + 1)
url = Url()
url.full_url = long_url
url.short_url = short_id
url.save()
params = dict()
params["short_url"] = short_id
params['path'] = request.META['HTTP_REFERER']
return render(request, 'base.html', params)

让我们来看一下以下术语列表:

  • get()方法很简单:它将请求转发到base.html文件(我们将很快创建)

  • post()方法从请求的 POST 变量中获取长 URL 并设置对象计数,就像短 URL 保存Url对象到数据库一样:

params['path'] = request.META['HTTP_REFERER'] 

这用于将当前路径传递给视图,以便可以使用锚标记使短 URL 可点击。

这就是这个 URL 对象在数据库中保存的方式:

{ "_id" : ObjectId("548d6ec8e389a24f5ea44258"), "full_url" : "http://sample_long_url", "short_url" : "short_url" } 

现在,我们将继续创建Link()类,它将接受短 URL 请求并重定向到长 URL:

class Link(View):
def get(self, request, short_url):
url = Url.objects(short_url=short_url)
result = url[0]
return HttpResponseRedirect(result.full_url)

short_url参数是来自请求 URL 的short_url代码:

url = Url.objects(short_url=short_url)

前一行查询数据库,检查给定短 URL 的匹配长 URL 是否存在:

return HttpResponseRedirect(result.full_url) 

这将重定向请求以从数据库中查找长 URL。

对于视图,我们需要创建的只是base.html文件。

由于这个项目的目的不是教你用户界面,我们不会包含任何库,并且会尽可能少地使用 HTML 来制作页面。

base.html文件的代码如下:

<!DOCTYPE html>
  <html>
    <head lang="en">
      <meta charset="UTF-8">
      <title>URL Shortner</title>
    </head>
    <body>
      <form action="" method="post">
        {% csrf_token %}
        Long Url:<br>
        <textarea rows="3" cols="80" name="longurl"></textarea>
        <br>
        <input type="submit" value="Get short Url">
      </form>

      <div id="short_url">
      {% if short_url %}
        <span>
          <a href="{{ path }}link/{{ short_url }}" target="_blank">{{ path }}link/{{ short_url }}</a>
        </span>
        {% endif %}
      </div>
    </body>
  </html>

这显示了一个带有表单的文本区域,并在提交表单后,在长 URL 下方显示了短链接。

这就是极简主义 URL 缩短器主页的样子:

存储会话

为了使这个工作,我们现在需要做的就是创建所需的 URL 映射,如下所示:

url_shortner/urlmapping.py

from django.conf.urls import patterns, url
from url.views import Index, Link
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
url(r'^$', Index.as_view()),
url(r'^link/(\w+)/$', Link.as_view()),
)

摘要

本章的目的是为您准备使用不同数据库创建项目,并为您提供有关数据库迁移以及这些迁移如何工作的基本概念。这不仅将帮助您调试迁移,还可以创建自己的数据迁移脚本,将数据从 JSON 文件或任何其他文件格式直接加载到 Django 应用程序中进行初始化。

本章还为您提供了如何使用 Django 和 MongoDB 设置的基本概念,并且我们还看到了一个小项目演示,随后是在这里使用 MongoDB 扩展 Django 系统的实际应用。

第十二章:使用第三方软件包

现在是时候将我们迄今学到的所有理论和原则结合起来,尝试理解我们如何利用第三方软件包来实现许多可能的项目,比如 Twitter API 的使用,Social Auth 等等。

在本章中,您将学习以下主题:

  • 深入开源世界

  • 在 Django 项目中使用 Social Auth

  • 在 Django 中构建 REST API

除了使用 Django 和 Python 构建网站所需的核心模块之外,我们还需要一些第三方软件包。互联网上有许多免费的第三方软件包;您可以在www.djangopackages.com/找到许多有用的软件包。我们将尝试为我们的项目使用开源第三方软件包。

深入开源世界

当我们看到开源这个词时,首先浮现在我们脑海中的问题是开源实际上是什么意思?

嗯,开源是一个指的是设计公开可访问并且可以根据任何人的需要进行修改,而无需事先获得任何许可的术语。

好的,那么,让我们继续,深入探讨开源世界的各个方面。

什么是开源软件?

开源软件意味着软件的源代码是公开可访问的,因此可以以任何可能的方式进行修改。此外,任何人都可以为源代码做出贡献,这通常会导致软件的增强。

现在,大多数软件用户从未看到源代码,程序员可以修改源代码以满足他们的需求;这基本上意味着程序员手中有源代码可以完全控制软件。

然后程序员可以通过修复任何错误或添加任何新功能来继续使用软件。

开源和其他软件有什么区别?

如果源代码没有公开访问,或者代码只对创建它的特定人群可访问,这种类型的软件称为专有软件闭源软件。闭源软件的例子包括微软产品,如 Microsoft Windows,Word,Excel,PowerPoint,Adobe Photoshop 等。

要使用专有软件,用户必须同意(通常是通过签署许可证,该许可证在第一次运行该软件时显示)他们不会对软件进行任何软件作者未明确允许的操作。

而开源软件是不同的。开源软件的作者将其代码提供给其他人,希望他们可以查看代码,复制代码,从中学习,修改代码或分享代码。Python 和 Django 程序是开源软件的例子。

就像专有软件有许可证一样,开源软件也有许可证,但是有很大的不同。这些许可证促进了开源开发;它们允许修改和修复源代码。

开源难道不只是指某物是免费的吗?

“开源不仅意味着获得访问源代码。”正如开源倡议所解释的那样,这意味着任何人都应该能够修改源代码以满足程序员的需求。

对于开源生态系统可能会有一些误解。程序员可以对他们创建的开源软件收费,但这没有任何意义,因为购买者有权修改并免费分发它。程序员不是为开源软件收费,而是为他们围绕它构建的服务收费,比如支持或其他增值的次要组件。像 Red Hat 这样的公司通过为其开源 Red Hat 操作系统提供支持而收费。Elasticsearch 收费的是一个名为 marvel 的组件,用于监视 Elasticsearch,在 Elasticsearch 运行时非常有帮助。

很多人认为只有互联网上有名的摇滚明星程序员才能为开源项目做出贡献,但事实上,开源社区靠初学者到专家,甚至非程序员的贡献而蓬勃发展。

在 Django 项目中使用 SocialAuth

每个网站都需要存储用户数据,以给他们更好和独特的体验,但为了做到这一点,网站需要你通过填写用户详细信息表格进行注册,他们要求你输入基本信息。填写这些信息可能会很无聊和繁琐。这个问题的一个实际解决方案是Social Auth,通过单击即可从你已经注册的社交网站自动填写你的基本信息注册到网站上。

例如,你可能在浏览网页时看到许多网站提供了一些社交按钮的选项,比如 Google、Facebook、Twitter 等,用于在他们的网站上登录或注册。如果你使用这些社交按钮登录或注册,它们将从社交网站上拉取你的基本信息,比如电子邮件、性别等,这样你就不需要手动填写表格。

单独构建这个完整的端到端实现可能是 Django 中的一个项目,如果你希望你的网站具有相同的功能,你不需要重复造轮子。我们只需导入一个第三方库,在settings.py文件中进行最小的配置更改,就可以让用户通过他们现有的社交账户登录或注册。

OAuth 的工作原理

要理解OAuth的工作原理,让我们考虑以下例子。

OAuth 就像是 Web 的代客泊车钥匙。大多数豪华车都配备了代客泊车钥匙,车主将其交给停车员。有了这把钥匙,车辆就不允许行驶更远的距离,其他功能,比如行李箱和豪华功能都被禁用了。

同样,你在网站上看到的登录按钮并不会给予网站对你社交账户的完全访问权限;它只会传递你授予的详细信息,或者默认信息,比如电子邮件、性别等。

为了访问这些信息,网站过去通常要求用户输入用户名和密码,这增加了个人信息泄露或账户被盗的风险。人们可能会在他们的银行账户上使用相同的用户名和密码,这使得情况更加危险。

因此,OAuth 的目的是为用户提供一种方法,让第三方访问他们的信息,而不用分享密码。通过遵循这种方法,也可以授予有限的访问权限(比如,电子邮件、创建帖子的权限等)。

例如,对于一个登录注册网站,如果他们要求访问你的个人照片,那将会非常奇怪。因此,在使用 OAuth 给予应用程序权限的时候,权限实际上是可以被审查的。

以下图表给出了 OAuth 机制的概述:

OAuth 的工作原理

在上图中,你可以看到需要你的凭据的客户端应用程序要求你使用任何社交账户登录或注册。这在图的第一部分中显示,客户端要求用户进行社交账户授权。

一旦你决定通过社交账户登录,并授予客户端应用程序访问你的社交账户的权限,已经在同一社交网站上注册并拥有自己 API 密钥的客户端应用程序,会向社交网站请求你的用户详细信息。在这个阶段,你可能已经看到了客户端应用程序将访问的记录列表。一些网站也许会让你编辑这些访问权限。在服务器授权客户端应用程序之后,客户端会获得你的社交账户访问的访问令牌。

客户端应用程序可能会存储此访问令牌以供将来使用,或者,如它通常被称为的离线访问

使用此社交 OAuth 方法注册和登录的区别在于,当您已经注册时,客户端应用程序可能会存储您的访问令牌,这样下次尝试登录时,您就不必再次通过相同的社交网站授权页面,因为您已经向他们提供了授权凭据。

实施社交 OAuth

在本节中,我们将学习如何在现有项目中实现社交 OAuth。为了为我们的应用程序实现社交认证,我们将使用一个名为python-social-auth的第三方库。我们将使用 Twitter 社交 Auth 来验证我们的用户。让我们来看一下以下步骤:

  1. 首先,我们将安装名为Python-Social-Auth的第三方应用程序。可以使用以下命令简单地安装python-social-auth
$pip install python-social-auth

  1. 完成安装此第三方库后,我们将转到我们的 mytweet 应用程序,并在settings.py文件中进行配置更改。

我们将此第三方库作为应用程序包含在我们的应用程序中,因此我们必须在INSTALLED_APPS变量中创建此应用程序的条目。

因此,将'social.apps.django_app.default'参数添加到INSTALLED_APPS变量中,如下所示:

INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'user_profile',
'tweet',
'social.apps.django_app.default',
)
  1. 接下来,我们需要在settings.py文件中添加AUTHENTICATION_BACKEND变量,列出我们想要支持的所有社交登录站点。对于此演示,我们将仅添加 Twitter 社交 Auth,但根据用例,您可以添加任何或尽可能多的 Twitter 社交 Auth。AUTHENTICATION_BACKENDS参数是 Python 类路径的列表,它知道如何验证用户。默认情况下指向'django.contrib.auth.backends.ModelBackend'参数。我们将'social.backends.twitter.TwitterOAuth'参数添加到AUTHENTICATION_BACKENDS变量中:
AUTHENTICATION_BACKENDS = (
  'social.backends.twitter.TwitterOAuth',
  'django.contrib.auth.backends.ModelBackend',
)
  1. 我们需要添加TEMPLATE_CONTEXT_PROCESSORS参数,它将在模板的上下文中添加后端和关联数据,这将反过来使用三个条目加载后端密钥,如下所示:
  • 关联:如果用户已登录,则这将是 UserSocialAuth 实例的列表;否则,它将为空。

  • 未关联:如果用户已登录,则这将是未关联后端的列表;否则,它将包含所有可用后端的列表。

  • 后端:这是所有可用后端名称的列表。让我们来看一下以下代码片段:

TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.contrib.messages.context_processors.messages',
'social.apps.django_app.context_processors.backends',
)
  1. 我们的 mytweet 应用程序已经有一个用户模型,通过该模型用户可以登录并发布推文。我们将使用相同的模型类来从社交 Auth 创建用户。为此,我们需要添加此行,告诉python-social-auth使用现有的user_profile参数:
SOCIAL_AUTH_USER_MODEL = 'user_profile.User'
  1. 现在,我们将添加用于社交 Auth 的自定义 URL:
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/profile/'
SOCIAL_AUTH_LOGIN_ERROR_URL = '/login-error/'
SOCIAL_AUTH_LOGIN_URL = '/login/'
SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = '/logout/'

将这些添加到settings.py文件中告诉社交 Auth 在以下情况下使用相应的 URL:

  • SOCIAL_AUTH_LOGIN_REDIRECT_URL:当社交认证成功时,将触发此 URL。我们将使用此 URL 向已登录用户发送他的个人资料页面。

  • SOCIAL_AUTH_LOGIN_ERROR_URL:在社交认证期间出现错误时,将触发此 URL。

  • SOCIAL_AUTH_LOGIN_URL:这是进行社交 Auth 的 URL。

  • SOCIAL_AUTH_DISCONNECT_REDIRECT_URL:用户注销后,将重定向到此 URL。

  1. 由于我们在现有项目中添加了一个新应用程序,因此我们需要在数据库中创建相应的表,这是我们在之前章节中已经学习过的。

现在,我们需要迁移我们的数据库:

$ python manage.py makemigrations
Migrations for 'default':
0002_auto_XXXX_XXXX.py:
- Alter field user on user_profile
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, default, contenttypes, auth, sessions
Running migrations:
Applying default.0001_initial... OK
Applying default.0002_auto_XXXX_XXXX... OK

  1. 对于最后的配置更改,我们需要向社交 Auth URLs 添加一个条目:
url('', include('social.apps.django_app.urls', namespace='social'))

更新后的 URL 模式将如下所示:

urlpatterns = patterns('',
....
url('', include('social.apps.django_app.urls', namespace='social'))
)

创建 Twitter 应用程序

现在,我们将继续创建一个 Twitter 应用程序,该应用程序将为我们提供 API 密钥,以使这个社交认证工作:

  1. 登录到您的 Twitter 账户并打开apps.twitter.com/app/new

页面将看起来有点像这样:

创建 Twitter 应用程序

  1. 填写详细信息并创建您的 Twitter 应用程序。

由于我们正在本地测试我们的应用程序,请将http://127.0.0.1:8000/complete/twitter作为回调 URL,并检查允许此应用程序用于使用 Twitter 登录复选框。

当成功创建时,您的应用程序将如下所示:

创建 Twitter 应用程序

  1. 继续使用Keys and Access Tokens选项卡,并复制Consumer Key(API 密钥)和Consumer Secret(API 密钥)密钥,如下截图所示:创建 Twitter 应用程序

  2. 将以下行添加到settings.py文件中:

SOCIAL_AUTH_TWITTER_KEY = 'your_key'
SOCIAL_AUTH_TWITTER_SECRET = 'your_secret'
  1. 更新我们的用户类以适当地使用 Auth:
class User(AbstractBaseUser, PermissionsMixin):
"""
Custom user class.
"""
  username = models.CharField('username', max_length=10, unique=True, db_index=True)
  email = models.EmailField('email address', unique=True)
  date_joined = models.DateTimeField(auto_now_add=True)
  is_active = models.BooleanField(default=True)
  is_admin = models.BooleanField(default=False)
  is_staff = models.BooleanField(default=False)

  USERNAME_FIELD = 'username'
  objects = UserManager()
  REQUIRED_FIELDS = ['email']
  class Meta:
    db_table = u'user'
    def __unicode__(self):
  return self.username 

importing the PermissionsMixin as from |django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
  1. 现在,启动服务器或打开http://127.0.0.1:8000/login/twitter/

这将带您到以下授权页面:

创建 Twitter 应用程序

  1. 点击登录按钮,因为我们将使用这个 Twitter 应用程序来登录我们的应用程序。

完成后,它将将请求重定向回 mytweet 应用程序,并显示您的基本信息,如下截图所示:

创建 Twitter 应用程序

如果用户名在我们的数据库中不存在,它将使用 Twitter 用户名创建用户配置文件。

  1. 让我们创建两条推文并保存它们。创建 Twitter 应用程序

现在,只是为了检查社交认证是否有效,我们将注销并尝试再次打开 URL。重定向后,您将被重定向到相同的先前配置文件页面。

因此,我们学会了如何逐步创建 Twitter API,通过在 Twitter 注册您的应用程序来设置程序中的密钥。然后,我们看到我们的应用程序如何将您发送到 Twitter 网站进行身份验证,以及如何在 Twitter 网站完成身份验证后将您重定向到我们的网站。

在 Django 中构建 REST API

表述性状态转移REST)是 Web 的基本架构原则。遵循 REST 原则的任何 API 都是设计成这样,即这里的浏览器客户端不需要了解 API 的结构。API 服务器只需要响应客户端发出的请求。

HTTP 的工作是应用于资源的动词。一些非常流行的动词是 GET 和 POST,但还有其他重要的动词,比如 PUT,DELETE 等。

例如,我们将使用由 Web 服务管理的 Twitter 数据库作为 REST API。对于所有 REST 通信,媒体类型是 API 服务器必须关心的主要内容,以及它必须响应客户端请求的格式。我们的 API 服务使用基于 JSON 的自定义超媒体,为此我们将分配/json+tweetdb MIME 类型应用程序。

对基本资源的请求将返回如下内容:

Request
GET /
Accept: application/json+tweetdb
Response
200 OK
Content-Type: application/json+tweetdb
{
  "version": "1.0",
  "links": [
    {
      "href": "/tweets",
      "rel": "list",
      "method": "GET" 
    },
    {
      "href": "/tweet",
      "rel": "create",
      "method": "POST"
    }
  ]
}

我们可以通过引用href链接来观察输出,通过这些链接我们试图发送或检索信息,这些链接就是超媒体控制。我们可以通过/user命令和GET请求发送另一个请求来获取用户列表:

Request
GET /user
Accept: application/json+tweetdb
  Response
  200 OK
  Content-Type: application/json+tweetdb

    {
      "users": [
      {
        "id": 1,
        "name": "Ratan",
        "country: "India",
        "links": [
          {
            "href": "/user/1",
            "rel": "self",
            "method": "GET"
          },
          {
            "href": "/user/1",
            "rel": "edit",
            "method": "PUT"
          },
          {
            "href": "/user/1",
            "rel": "delete",
            "method": "DELETE"
          }
        ]
      },
      {
        "id": 2,
        "name": "Sanjeev",
        "country: "India",
        "links": [
        {
          "href": "/user/2",
          "rel": "self",
          "method": "GET"
        },
        {
          "href": "/user/2",
          "rel": "edit",
          "method": "PUT"
        },
        {
          "href": "/user/2",
          "rel": "delete",
          "method": "DELETE"
        }
      ]
    }
  ],
  "links": [
    {
      "href": "/user",
      "rel": "create",
      "method": "POST"
    }
  ]
}

查看前面生成的输出,我们可以猜出所有用户是谁,以及我们可以发送哪些请求,比如DELETEPUT请求。同样,我们甚至可以通过向/user发送POST请求来创建新用户,如下面的代码片段所示:

Request
POST /user
Accept: application/json+tweetdb
  Content-Type: application/json+tweetdb
  {
    "name": "Zuke",
    "country": "United States"
  }
  Response
  201 Created
  Content-Type: application/json+tweetdb
  {
    "user": {
      "id": 3,
      "name": "Zuke",
      "country": "United States",
      "links": [
        {
          "href": "/user/3",
          "rel": "self",
          "method": "GET"
        },
        {
          "href": "/user/3",
          "rel": "edit",
          "method": "PUT"
        },
        {
          "href": "/user/3",
          "rel": "delete",
          "method": "DELETE"
        }
      ]
    },
    "links": {
      "href": "/user",
      "rel": "list",
      "method": "GET"
    }
  }

我们也可以更新现有的数据:

Request
PUT /user/1
Accept: application/json+tweetdb
  Content-Type: application/json+tweetdb
  {
    "name": "Ratan Kumar",
    "country": "United States"
  }
  Response
  200 OK
  Content-Type: application/json+tweetdb
  {
    "user": {
      "id": 1,
      "name": "Ratan Kumar",
      "country": "United States",
      "links": [
        {
          "href": "/user/1",
          "rel": "self",
          "method": "GET"
        },
        {
          "href": "/user/1",
          "rel": "edit",
          "method": "PUT"
        },
        {
          "href": "/user/1",
          "rel": "delete",
          "method": "DELETE"
        }
      ]
    },
    "links": {
      "href": "/user",
      "rel": "list",
      "method": "GET"
    }
  }

正如您可以轻松注意到的那样,我们正在使用不同的HTTP动词(GETPUTPOSTDELETE等)来操作这些资源。

现在,您已经对 REST 的工作原理有了基本的了解,所以我们将继续使用一个名为Tastypie的第三方库来操作我们的 mytweets 应用程序。

使用 Django Tastypie

Django Tastypie 使为 Web 应用程序开发 RESTful API 变得更加容易。

要安装 Tastypie,请运行以下命令:

$pip install django-tastypie

settings.py文件中的INSTALLED_APPS变量中添加tastypie参数。

API 需要许多其他可配置的设置,例如 API 调用的限制等,但默认情况下它们最初设置为默认值。您可以更改这一点,也可以保持不变。

一些您应该了解并根据需要修改的 API 设置如下:

  • API_LIMIT_PER_PAGE(可选):此选项控制 Tastypie 在用户未指定 GET 参数的情况下在view.applies列表中返回的默认记录数。结果的数量不会被resource子类覆盖。

例如:

API_LIMIT_PER_PAGE = 15

这里的默认限制是 20。

  • TASTYPIE_FULL_DEBUG(可选):当发生异常时,此设置控制是否显示 REST 响应还是 500 错误页面。

如果设置为True并且settings.DEBUG = True,将显示500 错误页面。

如果未设置或设置为False,Tastypie 将返回序列化响应。

如果settings.DEBUGTrue,您将获得实际的异常消息和跟踪。

如果settings.DEBUGFalse,Tastypie 将调用mail_admins()函数并在响应中提供一个预定义的错误消息(您可以用TASTYPIE_CANNED_ERROR覆盖)。

例如:

TASTYPIE_FULL_DEBUG = True

默认值为False

  • TASTYPIE_CANNED_ERROR(可选):当发生未处理的异常并且settings.DEBUGFalse时,您可以编写自定义错误消息。

例如:

TASTYPIE_CANNED_ERROR = "it's not your fault, it's our we will fix it soon."

这里的默认值是“抱歉,无法处理此请求。请稍后重试。”

  • TASTYPIE_ALLOW_MISSING_SLASH(可选):您可以在不提供最终斜杠的情况下调用 REST API,这主要用于与其他系统迭代 API。

您还必须有settings.APPEND_SLASH = False,以便 Django 不发出 HTTP 302 重定向。

例如:

TASTYPIE_ALLOW_MISSING_SLASH = True

这里的默认值是False

  • TASTYPIE_DATETIME_FORMATTING(可选):此设置配置 API 的全局日期/时间数据。

此设置的有效选项包括:

  • iso-8601

  • DateTime::ISO8601

  • ISO-8601(例如:2015-02-15T18:37:01+0000)

  • iso-8601-strict,与 iso-8601 相同,但会触发微秒

  • rfc-2822

  • DateTime::RFC2822

  • RFC 2822(例如,Sun, 15 Feb 2015 18:37:01 +0000)

TASTYPIE_DATETIME_FORMATTING = 'rfc-2822'

以以下代码为例:

这里的默认值是 iso-8601。

  • TASTYPIE_DEFAULT_FORMATS(可选):这个设置全局配置整个站点的序列化格式列表。

例如:

TASTYPIE_DEFAULT_FORMATS = [json, xml]

默认为[json, xml, yaml,html, plist]。

实施简单的 JSON API

为了创建 REST 风格的架构,我们需要为我们的 tweets 定义资源类,所以让我们在tweets文件夹中创建一个api.py文件,内容如下:

from tastypie.resources import ModelResource
from tweet.models import Tweet

class TweetResource(ModelResource):
class Meta:
queryset = Tweet.objects.all()
resource_name = 'tweet'

我们还需要一个 URL,用于所有 API 请求的 Tweet 资源,因此让我们在urls.py文件中添加一个条目:

from tastypie.api import Api
from tweet.api import TweetResource

v1_api = Api(api_name='v1')
v1_api.register(TweetResource())

urlpatterns = patterns('',
...
url(r'^api/', include(v1_api.urls)),
)

这就是我们创建 tweets 的基本 REST API 所需的全部内容。

现在,我们将根据 REST URL 的变化来看各种输出。在浏览器中打开以下 URL,并观察.json格式的输出。

第一个 URL 将以.json格式显示 Tweet API 的详细信息:

http://127.0.0.1:8000/api/v1/?format=json

{
  "tweet": {
    "list_endpoint": "/api/v1/tweet/",
    "schema": "/api/v1/tweet/schema/"
  }
}

根据第一个输出,我们将调用我们的 tweet API,这将给我们 tweet 信息和其他细节,如下所示:

http://127.0.0.1:8000/api/v1/tweet/?format=json

{
  "meta": {
    "limit": 20,
    "next": null,
    "offset": 0,
    "previous": null,
    "total_count": 1
  },
  "objects": [
    {
      "country": "Global",
      "created_date": "2014-12-28T20:54:27",
      "id": 1,
      "is_active": true,
      "resource_uri": "/api/v1/tweet/1/",
      "text": "#Django is awesome"
    }
  ]
}

我们的基本 REST API 已经准备就绪,可以列出所有的 tweets。如果您查看架构,它会给我们很多关于 API 的细节,比如允许使用哪些 HTTP 方法,输出将是哪种格式,以及其他不同的字段。这实际上帮助我们了解我们可以使用我们的 API 做什么:

http://127.0.0.1:8000/api/v1/tweet/schema/?format=json

{
  "allowed_detail_http_methods": [
    "get",
    "post",
    "put",
    "delete",
    "patch"
  ],
  "allowed_list_http_methods": [
    "get",
    "post",
    "put",
    "delete",
    "patch"
  ],
  "default_format": "application/json",
  "default_limit": 20,
  "fields": {
    "country": {
      "blank": false,
      "default": "Global",
      "help_text": "Unicode string data. Ex: \"Hello World\"",
      "nullable": false,
      "readonly": false,
      "type": "string",
      "unique": false
    },
    "created_date": {
      "blank": true,
      "default": true,
      "help_text": "A date & time as a string. Ex: \"2010-11- 10T03:07:43\"",
      "nullable": false,
      "readonly": false,
      "type": "datetime",
      "unique": false
    },
    "id": {
      "blank": true,
      "default": "",
      "help_text": "Integer data. Ex: 2673",
      "nullable": false,
      "readonly": false,
      "type": "integer",
      "unique": true
    },
    "is_active": {
      "blank": true,
      "default": true,
      "help_text": "Boolean data. Ex: True",
      "nullable": false,
      "readonly": false,
      "type": "boolean",
      "unique": false
    },
    "resource_uri": {
      "blank": false,
      "default": "No default provided.",
      "help_text": "Unicode string data. Ex: \"Hello World\"",
      "nullable": false,
      "readonly": true,
      "type": "string",
      "unique": false
    },
    "text": {
      "blank": false,
      "default": "No default provided.",
      "help_text": "Unicode string data. Ex: \"Hello World\"",
      "nullable": false,
      "readonly": false,
      "type": "string",
      "unique": false
    }
  }
}

一些 API 可能需要授权访问,比如用户资料、账户详情等等。只需添加一个基本授权行,就可以在 Tastypie API 中添加基本的 HTTP 授权:

authentication = BasicAuthentication()

基本的 HTTP 授权可以通过头文件添加:

from tastypie.authentication import BasicAuthentication

这将通过一个基本的 HTTP 请求来请求认证,看起来像下面的截图。一旦成功,当前会话中的所有请求都将得到认证。

实现一个简单的 JSON API

这之后,通过演示,展示了如何使用 MongoDB 扩展 Django 系统的真实应用。

摘要

在本章中,您了解了开源以及如何在我们的项目中使用和实现开源的第三方包。现在,您将可以舒适地实现来自 Twitter 的社交认证。您也可以尝试自己实现 Facebook 和 Google+的相同功能。

在下一章中,您将学习更多关于调试技术的知识,当我们在代码中遇到任何错误或警告,或者一些配置问题时,我们需要使用这些技术。您还将学习产品开发工具,比如 Git,Sublime Text 编辑器等等。

第十三章:调试的艺术

在本章中,您将学习关于 Django 的 Web 开发的三个重要内容,每个程序员都应该了解。这些是您在代码出错时需要的概念和技术:

  • 记录

  • 调试

  • IPDB-消除错误的交互方式

记录

每个在生产环境中运行的应用程序都必须启用一些日志记录;如果没有启用,那么很难弄清楚出了什么问题以及问题出现在哪里。

Django 使用 Python 的基本日志记录,因此我们将在以下部分详细介绍 Python 日志记录,并看看我们如何在 Django 中使用日志记录服务。

日志的正式定义是软件中事件的跟踪。开发人员调用日志服务来说明事件已经发生或将要发生。日志可以包括需要跟踪的某些重要变量的描述或值。

Python 的logging模块带有五个基于事件严重性分类的日志函数。这些是debug()info()warning()error()critical()

这些按严重性分类在表格中,从最不严重到最严重:

  • debug():在修复错误时使用,通常包含数据的详细信息。

  • info():当事情按照预期进行时,会记录日志。这基本上告诉执行是否成功。

  • 警告():当发生意外事件时会引发此警告。这实际上并不会停止执行,但可能会在将来停止执行。例如,“磁盘空间不足”。

  • error():这是警告的下一个级别,表示某个函数的执行可能已经停止。

  • critical():这是任何日志函数的最高级别。当发生非常严重的错误时,可能会停止整个程序的执行。

logging模块分为以下四个类别:

  • 记录器:记录器是系统日志消息的入口点。程序将日志信息写入记录器,然后处理是否将其输出到控制台或写入文件。

每个记录器包括前面五个日志函数。写入记录器的每条消息称为日志记录。日志记录包含日志的严重性以及重要的日志变量或详细信息,例如错误代码或完整的堆栈跟踪。

记录器本身具有日志级别,其工作原理是:如果日志消息的日志级别大于或等于记录器的日志级别,则消息将进一步进行日志记录;否则,记录器将忽略该消息。

当记录器对日志的评估进行预处理并且需要处理生成的日志时,消息将传递给处理程序。

  • 处理程序:处理程序实际上决定如何处理日志消息。它们负责对日志记录采取行动,例如写入控制台或文件,或通过网络发送。

与记录器一样,处理程序也有日志级别。如果日志记录的日志级别不大于或等于处理程序的级别,则处理程序将忽略日志消息。

可以将多个处理程序绑定到记录器,例如,可以为将 ERROR 和 CRITICAL 消息发送到电子邮件的记录器添加一个处理程序,而另一个处理程序可以将相同的日志写入文件以供以后调试分析。

  • 过滤器:当日志记录从记录器传递到处理程序时,过滤器会添加额外的评估。默认行为是当日志消息级别达到处理程序级别时开始处理邮件。

此过程可以通过应用过滤器进一步中断进行额外评估。

例如,过滤器只允许一个来源将 ERROR 消息记录到处理程序。

过滤器还可以用于改变日志记录的优先级,以便相应地触发记录器和处理器。

  • 格式化程序:在实际记录日志消息之前的最后一步是格式化程序实际格式化由 Python 格式化字符串组成的日志记录。

为了在我们的应用程序中启用日志记录,我们首先需要创建一个记录器。我们需要在settings.py文件中创建描述记录器、处理器、过滤器和格式化程序的 LOGGING 字典。

有关日志设置的完整文档可以在docs.python.org/2/library/logging.config.html找到。

以下是一个简单日志设置的示例:

# settings.py
LOGGING = {
  'version': 1,
  'disable_existing_loggers': False,
  'formatters': {
    'simple': {
      'format': '%(levelname)s %(message)s'
    },
  },
  'handlers': {
    'file':{
      'level':'DEBUG',
      'class': 'logging.FileHandler',
      'formatter': 'simple',
      'filename': 'debug.log',
    }
  },
  'loggers': {
    'django': {
      'handlers':['file'],
      'propagate': True,
      'level':'INFO',
    },
  }
}

这个日志设置定义了一个用于 Django 请求的记录器(Django),以及一个写入日志文件的处理器(文件)和一个格式化程序。

我们将使用相同的方法来测试我们的mytweet项目的日志记录。

现在,我们需要将记录器的条目添加到我们想要跟踪事件的视图中。

为了测试项目,我们将更新我们的用户资料重定向类,以便在未经授权的用户尝试访问时进行日志记录,以及在注册用户尝试打开 URL 时也进行日志记录。

打开tweet/view.py文件,并将UserRedirect类更改为以下内容:

class UserRedirect(View):
  def get(self, request):
    if request.user.is_authenticated():
      logger.info('authorized user')
      return HttpResponseRedirect('/user/'+request.user.username)
    else:
      logger.info('unauthorized user')
      return HttpResponseRedirect('/login/')

还要用import语句初始化记录器,并将以下代码添加到前面的代码中:

import logging
logger = logging.getLogger('django')

就是这样。现在,打开浏览器,单击 URL http://localhost:8000/profile

如果您尚未登录,将被重定向到登录页面。

现在,打开debug.log文件。它包含未经授权用户的INFO,这意味着我们的记录器工作正常:

INFO unauthorized user

调试

调试是查找和消除错误(bug)的过程。当我们使用 Django 开发 Web 应用程序时,我们经常需要知道在 Ajax 请求中提交的变量。

调试工具有:

  • Django 调试工具栏

  • IPDB(交互式调试器)

Django 调试工具栏

这是一组面板,用于显示当前页面请求/响应的各种信息,当单击面板时会显示更详细的信息。

与其简单地在 HTML 注释中显示调试信息,Django 调试工具以更高级的方式显示它。

安装 Django 调试工具栏

要安装 Django 调试工具栏,请运行以下命令:

$ pip install django-debug-toolbar

安装后,我们需要进行基本配置更改以查看 Django 调试工具栏。

settings.py文件的INSTALLED_APPS变量中添加debug_toolbar参数:

# Application definition
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user_profile',
    'tweet',
    'social.apps.django_app.default',
    'tastypie',
    'debug_toolbar',
)

对于一个简单的 Django 项目来说,这已经足够了。当服务器运行在开发模式下时,Django 调试工具栏将自动调整自己。

重新启动服务器以查看 Django 调试工具栏,如下截图所示:

安装 Django 调试工具栏

如您所见,个人资料页面右侧有一个工具栏。Django 调试工具栏有许多面板,默认安装了一些,您可以在前面的截图中看到,还可以在此安装其他第三方面板。

现在,我们将讨论默认启用的面板:

  • VersionPathdebug_toolbar.panels.versions.VersionsPanel。该面板显示了基本信息,例如 Python、Django 的版本以及其他已安装应用的版本,如果信息可用:安装 Django 调试工具栏

  • TimerPathdebug_toolbar.panels.timer.TimerPanel安装 Django 调试工具栏

该面板包含了 Django 开发的一些非常重要的统计信息。它显示了两个表,如前面的截图所示,分别是资源使用浏览器定时

  • 资源使用:显示服务器机器上 Django 的资源消耗。

  • 浏览器时间:这显示了客户端的详细信息。请求和响应时间对于了解代码是否可以优化至关重要,如果渲染过多导致页面加载缓慢,可以查看 domLoading。

  • SettingsPathdebug_toolbar.panels.settings.SettingsPanelsettings.py文件中定义的设置列表为headers

  • 路径debug_toolbar.panels.headers.HeadersPanel安装 Django 调试工具栏

该面板显示 WSGI 环境中的 HTTP 请求和响应头和变量。

  • 请求路径debug_toolbar.panels.request.RequestPanel安装 Django 调试工具栏

该面板显示了从框架中的变量,从视图变量开始,还有ratancs参数变量;然后是CookiesSession,以及 GET 和 POST 变量,因为这些对调试表单提交非常有帮助。

  • SQL 路径debug_toolbar.panels.sql.SQLPanel安装 Django 调试工具栏

这个面板也非常重要,因为它显示了页面响应的数据库查询。这在应用程序扩展时非常有帮助,因为可以彻底检查查询并将其组合在一起,以减少数据库访问并改善页面响应性能。

这还显示了生成 SQL 调用的代码片段,这在调试应用程序时也非常有帮助。

  • 静态文件路径debug_toolbar.panels.staticfiles.StaticFilesPanel安装 Django 调试工具栏

这将列出从我们在settings.py文件中设置的静态文件位置使用的所有静态文件。

  • 模板路径debug_toolbar.panels.templates.TemplatesPanel安装 Django 调试工具栏

这将列出当前请求使用的模板和上下文。

  • 缓存路径debug_toolbar.panels.cache.CachePanel安装 Django 调试工具栏

如果我们启用了缓存,那么这将显示给定 URL 的缓存命中的详细信息。

  • 信号路径debug_toolbar.panels.signals.SignalsPanel安装 Django 调试工具栏

该面板显示信号列表及其参数和接收器。

  • 日志路径debug_toolbar.panels.logging.LoggingPanel

如果启用了日志记录,那么该面板将显示日志消息,如下截图所示:

安装 Django 调试工具栏

  • 重定向路径debug_toolbar.panels.redirects.RedirectsPanel

当 URL 发生页面重定向时,启用此功能以调试中间页面。通常不调试重定向 URL,因此默认情况下此功能已禁用。

安装 Django 调试工具栏

IPDB - 消灭错误的交互方式

Ipdb是 Python 程序的交互式源代码调试器。

运行以下命令安装 Ipdb:

$pip install ipdb

Ipdb 是调试 Python 应用程序的交互方式。安装 Ipdb 后,要在任何函数中使用它,只需编写以下代码:

import ipdb;ipdb.set_trace()

这行神奇的代码将在代码出现的地方停止整个 Django 执行,并为您提供一个活动控制台,在那里您可以实时查找错误或检查变量的值。

在活动控制台中,Ipdb 的快捷键是:

  • n:这表示下一个

  • ENTER:这表示重复上一个

  • q:这表示退出

  • p <variable>:这是打印值

  • c:这表示继续

  • l:这是你所在的列表

  • s:这是进入子程序的步骤

  • r:这意味着继续执行子程序直到结束

  • !<python 命令>:在活动控制台中运行 Python 命令

总结

这一章涵盖的内容远不止这些。这些只是我们在 Django 项目中要使用的调试基础知识。你学会了如何记录和调试我们的代码,以便更好地进行高效编码实践。我们还看到了如何使用 Ipdb 进行更多的调试。

在下一章中,你将学习部署 Django 项目的各种方法。

第十四章:部署 Django 项目

因此,您在 Web 应用程序上做了很多工作,现在是时候让它上线了。为了确保从开发到生产的过渡顺利进行,必须对应用程序进行一些更改。本章涵盖了以下主题的更改,以帮助您成功启动 Web 应用程序:

  • 生产 Web 服务器

  • 生产数据库

  • 关闭调试模式

  • 更改配置变量

  • 设置错误页面

  • 云上的 Django

生产 Web 服务器

在本书中,我们一直在使用 Django 自带的开发 Web 服务器。虽然这个服务器非常适合开发过程,但绝对不适合作为生产 Web 服务器,因为它没有考虑安全性或性能。因此,它绝对不适合生产环境。

在选择 Web 服务器时有几个选项可供选择,但Apache是迄今为止最受欢迎的选择,Django 开发团队实际上推荐使用它。如何使用 Apache 设置 Django 取决于您的托管解决方案。一些托管计划提供预配置的 Django 托管解决方案,您只需将项目文件复制到服务器上,而其他托管计划则允许您自行配置一切。

如何设置 Apache 的详细信息因多种因素而异,超出了本书的范围。如果您想自己配置 Apache,请查阅 Django 在线文档docs.djangoproject.com/en/1.8/howto/deployment/wsgi/apache-auth/获取详细说明。

在本节中,我们将在 Apache 和mod_wsgi模块上部署我们的 Django 应用程序。因此,让我们首先安装这两个。

运行以下命令安装 Apache:

$sudo apt-get install apache2

mod_wsgi参数是 Apache HTTP 服务器模块,提供符合Web 服务器网关接口WSGI)标准的接口,用于在 Apache 下托管基于 Python 2.3+的 Web 应用程序。

运行以下命令安装mod_wsgi模块:

$sudo aptitude install libapache2-mod-wsgi

使用 Apache 和mod_wsgi模块的 Django 是在生产中部署 Django 的最流行方式。

在大多数情况下,开发机器和部署机器是不同的。因此,建议您将项目文件夹复制到/var/www/html/文件夹,以便您的部署文件具有有限的权限和访问权限。

安装了 Apache 服务器后,请尝试在浏览器中访问localhost,即127.0.0.1。通过这样做,您应该会看到默认的 Apache 页面,如下截图所示:

生产 Web 服务器

我们必须将 Apache 服务器设置为我们的 Django 项目。为此,我们需要为 Apache 创建configuration文件。

为此,在/etc/apache2/sites-available导航到的sites-available文件夹中创建一个mytweets.conf文件,内容如下:

<VirtualHost *:80>
  ServerAdmin mail@ratankumar.org
  ServerName mytweets.com
  ServerAlias www.mytweets.com
  WSGIScriptAlias / /var/www/html/mytweets/mytweets/wsgi.py
  Alias /static/ /var/www/html/mytweets/static/
  <Location "/static/">
    Options -Indexes
  </Location>
</VirtualHost>

让我们来看看以下术语列表,描述了前面代码片段中使用的各种参数:

  • ServerAdmin:如果您没有配置自定义错误页面,将显示此电子邮件地址,该页面将告诉用户联系此电子邮件地址。

  • ServerName:这是您想在其上运行项目的服务器的名称。

  • ServerAlias:这是您要在项目上运行的站点的名称。

  • WSGIScriptAlias:这是项目的wsgi.py文件的位置,在我们运行第一个命令创建 Django 项目时已经存在。

  • Alias:这是路径别名,磁盘上的文件夹的实际位置被映射为项目目录。

现在,我们需要使用a2ensite命令启用此站点配置,并使用a2dissite命令禁用现有站点配置。

让我们通过以下命令为 Apache 启用mytweets.conf文件:

$a2ensite mytweets.conf

这将启用我们的mytweets.conf文件。你也可以使用以下命令禁用default 000-default.conf配置:

$a2dissite 000-default.conf

注意

验证项目静态文件的文件权限。不要忘记在settings.py文件中允许主机的条目。

现在,重新启动服务器:

$sudo service apache2 restart

这样,Django 现在运行在部署模式下,也就是说,它现在已经准备好投入生产。

生产数据库

到目前为止,我们一直在使用 SQLite 作为我们的数据库引擎。它简单,不需要常驻内存中的服务器。对于小型网站,SQLite 在生产模式下表现良好。然而,强烈建议您在生产中切换到使用客户端-服务器模型的数据库引擎。正如我们在前面的章节中看到的,Django 支持多种数据库引擎,包括所有流行的数据库引擎。Django 团队建议您使用 PostgreSQL,但 MySQL 也应该可以。无论你的选择是什么,你只需要在settings.py文件中更改数据库选项,就可以切换到不同的数据库引擎。

如果你想使用 MySQL,为 Django 创建一个数据库、用户名和密码。然后,相应地更改DATABASE_*变量。其他一切都应该保持不变。这就是 Django 数据库层的全部意义。

关闭调试模式

在开发过程中发生错误时,Django 会呈现一个详细的错误页面,提供大量有用的信息。然而,当应用进入生产阶段时,你不希望用户看到这样的信息。除了让用户感到困惑,如果让陌生人看到这样的信息,你还会面临网站安全问题的风险。

当我们使用django-admin.py mytweets命令时,它为项目创建了所有基本配置,我们在settings.py文件中使用了debug=True参数,当这个模式为True时。Django 会做一些额外的工作来帮助你更快地调试问题。Django 的内存使用更多,因为所有的查询都存储在数据库中的django.db.connection.queries中。

对于每个错误消息,都会显示消息的适当堆栈跟踪,这在生产模式下是不推荐的,因为这可能包含敏感信息,可能会削弱整个 Web 应用程序的安全性。

关闭调试模式非常简单。打开settings.py文件,将DEBUG变量更改为False

DEBUG = False

禁用调试信息还有一个额外的好处;你可以提高网站的性能,因为 Django 不必跟踪调试数据以显示它。

更改配置变量

有许多需要为生产创建或更新的配置变量。生产环境是一个非常恶劣的环境。以下是你应该在部署过程中检查的清单。仔细检查setting.py文件,因为每个设置必须以正确的方式定义,以保持项目的安全。

设置可以是特定于环境的,比如在本地运行设置时。数据库凭据可能会改变,甚至数据库也可能会根据环境而改变。在进行部署过程时,启用可选的安全功能。

启用性能优化。第一步是禁用调试,这会提高网站的性能。如果有一个合适的错误报告机制,一旦DEBUGFalse,就很难知道出了什么问题,所以最好在禁用调试模式后准备好你的日志。

在进行 Django 部署时,必须注意以下关键设置:

  • SECRET_KEY:此密钥必须选择大且随机,并且应保密。事实上,建议您永远不要将此信息保存在settings.py文件或版本控制存储库中。相反,将此信息保存在非版本控制文件中或环境路径中的安全位置:
import os
SECRET_KEY = os.environ['SECRET_KEY']

这将从当前操作系统的环境中导入密钥。另一种建议的方法是从文件中导入,可以使用以下方法完成:

with open('/etc/secret_key.txt') as f:
    SECRET_KEY = f.read().strip()
  • ALLOWED_HOSTS:这必须具有有效的主机配置。当调试模式关闭时,这用于保护 CSRF 攻击:
ALLOWED_HOSTS = [
    '.example.com',  # Allow domain and subdomains
    '.example.com.',  # Also allow FQDN and subdomains
]
  • ADMINADMIN键保存站点管理员的名称和电子邮件地址。您将在settings.py文件中找到它,注释如下:
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)

在此处插入您的姓名和电子邮件地址,并删除#符号以取消注释,以便在发生代码错误时接收电子邮件通知。

DEBUG=False并且视图引发异常时,Django 将通过电子邮件向这些人发送完整的异常信息。

  • EMAIL:由于您的生产服务器的电子邮件服务器很可能与您的开发机器不同,因此您可能需要更新电子邮件配置变量。在settings.py文件中查找以下变量并更新它们:

  • EMAIL_HOST

  • EMAIL_PORT

  • EMAIL_HOST_USER

  • EMAIL_HOST_PASSWORD

此外,您的 Web 应用程序现在有自己的域名,因此您需要更新以下设置以反映这一点:SITE_HOSTDEFAULT_FROM_EMAIL

最后,如果您使用缓存,请确保在CACHE_BACKEND参数中设置正确的设置(理想情况下是memcached参数);在生产环境中,您不希望开发后端出现在这里。

设置错误页面

在调试模式禁用时,您应该为错误页面创建模板,特别是这两个文件:

  • 404.html:当请求的 URL 不存在时(换句话说,当页面未找到时,例如未捕获的异常时),将显示此模板。

创建两个文件,内容随意。例如,您可以在404.html模板中放置一个“页面未找到”的消息,或者一个搜索表单。

  • 500.html:当发生内部服务器错误时,将显示此模板。

建议您通过从站点的基本模板派生它们,使这些模板具有一致的外观。将模板放在templates文件夹的顶部,Django 将自动使用它们。

这应涵盖对生产至关重要的配置更改。当然,此部分并非穷尽一切,还有其他您可能感兴趣的设置。例如,您可以配置 Django 在请求的页面未找到时通过电子邮件通知您,或者提供可以查看调试信息的 IP 地址列表。有关这些以及更多信息,请参阅settings.py文件中的 Django 文档。

希望本节能够帮助您使从开发到生产的过渡更加顺利。

云上的 Django

Web 开发中的部署方式随着时间的推移发生了变化。大多数初创公司正在转向云设置,远离传统的 VPS 托管方法,这是因为可靠性、性能和易于扩展性。

提供基础设施即服务IAS)的最受欢迎的云平台是 Amazon EC2 和 Google Compute Engine。

然后,我们还有其他众所周知的选项,例如平台即服务PaaS),在这种服务中,您可以像将代码推送到普通存储库一样将代码推送,以便自动部署。这些包括 Google App Engine、Heroku 等。

让我们逐一介绍它们。

EC2

EC2上部署很简单。按照给定的步骤在 EC2 上部署所需的设置:

  1. 为 AWS 创建一个帐户。请访问aws.amazon.com并单击创建免费帐户,如下面的屏幕截图所示:EC2

  2. 注册并添加信用卡以获取结算明细。完成后,登录,您将看到一个仪表板。为了部署,我们需要在 AWS 上创建一个名为 EC2 实例(它可以被视为服务器)的服务器。

  3. 点击 EC2(在左上角),如下截图所示:EC2

如前面的截图所示,我已经有一个正在运行的实例(1 Running Instances)。单击启动实例以创建新实例。这将显示可用的 AWS 映像(类似于 VMware 中的截图或上次备份的磁盘):

EC2

  1. 向下滚动以选择 Ubuntu 64 位实例(Ubuntu 服务器)。

接下来,选择一个实例类型;最初,选择免费套餐,这是 AWS 为每个新帐户提供的t2.micro实例类型。检查其他设置,因为大多数设置都保持默认值。转到标签实例并为您的实例命名:

EC2

  1. 接下来要做的重要事情是选择安全组。AWS 具有此功能,可保护您的服务器免受攻击。在这里,您可以配置哪些特定端口将是公开可访问的。基本上,您需要打开两个端口以使推文公开可访问。

  2. 您应该使用 SSH(端口 22)从本地机器连接系统以部署代码。

  3. HTTP(端口 80)用于运行您的 Django 服务器。

注意

由于我们将使用的数据库运行在同一实例上,因此我们不会将 MySQL 端口添加到安全组中。

确保您已经配置了类似以下内容:

EC2

接下来,审查并启动实例。此外,您必须创建一个密钥对以通过 SSH 访问您的 AWS 机器。密钥是一个.pem文件,您将使用它与 SSH 远程登录到您的机器。创建一个密钥对并下载.pem文件。

注意

确保PEM文件具有特定的 400 权限。如果要使 SSH 工作,您的密钥文件不得公开可见。如有需要,请使用以下命令:chmod 400 mykey.pem

这将需要一段时间,并且将作为正在运行的实例重新显示在您的仪表板上。

单击屏幕左侧的实例。然后,您可以看到正在运行的实例。单击实例行以在屏幕底部获取更多详细信息,如下图所示:

EC2

在详细信息的右侧,您可以看到公共 DNS:和公共 IP:。这就是您需要的一切(当然还有.pem文件)来登录到您的实例。

在您的机器上,转到终端中下载PEM文件的文件夹,并在终端上键入$ssh -i <pemfilename>.pem ubuntu@<pubic IP>

否则,输入以下内容:

$ssh -i <pemfilename>.pem ubuntu@<public Dns>

通过这样做,您将登录到远程服务器。

这是您从头开始的在线系统。如果要从本地机器部署网站,则可以转到以前的章节并安装虚拟环境所需的一切。Django 和 Apache 在此服务器上执行部署。

部署后,使用我们用于 SSH 的公共 IP,您应该看到已部署的服务器。

谷歌计算引擎

谷歌计算引擎的工作原理与 AWS EC2 相同。目前,谷歌计算引擎没有免费套餐。

谷歌服务器以其可靠性和性能而闻名。因此,如果您考虑具有此需求的项目,请选择它们。

谷歌云为您提供了一个云 SDK 来使用其实例,并且大部分初始配置可以从终端完成。

要在谷歌计算引擎上创建一个实例,请转到:

cloud.google.com/compute/docs/quickstart

此链接将帮助您设置在 Apache 服务器上运行的实例。

红帽开源混合云应用平台

红帽提供了另一种云部署解决方案,免费使用一定限额,名为 OpenShift 的服务。

您可以创建一个 OpenShift 帐户,并从www.openshift.com/app/account/new获取一个免费的基本 3 dynamo 云服务器。

创建帐户后,您可以转到openshift.redhat.com/app/console/applications并添加您的帐户。

OpenShift 为您提供了一个已经设置好版本控制的 Django 存储库。

您只需要进行更改并推送代码。它将自动部署代码。

OpenShift 还提供 SSH 功能,可以登录到您的云服务器,并进行一些基本的故障排除。

Heroku

这也是一个很好的平台,可以顺利地将您的 Django 代码部署到云端。与谷歌计算引擎一样,Heroku 还为您提供了一个 SDK 工具,可以从本地终端安装并执行配置更改。您需要获取一个工具包(Heroku 的 SDK)。

signup.heroku.com上创建一个 Heroku 帐户。

以下是从devcenter.heroku.com/articles/getting-started-with-python中获取的步骤。查看最新更新。以下步骤解释了如何创建和使用 Heroku:

  1. 首先,我们需要安装 Heroku Toolbelt。这为您提供了访问 Heroku 命令行实用程序的权限:
$wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh

将出现以下屏幕:

Heroku

  1. 它将在本地机器上安装 Heroku Toolbelt。从命令行登录到 Heroku:
$heroku login

  1. 使用与 Web 登录相同的用户名和密码。让我们来看一下下面的截图:Heroku

  2. 现在,访问devcenter.heroku.com/articles/getting-started-with-django来在 Heroku 上部署 Django。

谷歌应用引擎

谷歌应用引擎的工作方式不同,它不使用传统的数据库,而是有自己的数据库。因此,要在谷歌应用引擎上部署 Django,我们将使用一个名为 Django-nonrel 的单独项目。

Django-nonrel 是一个允许开发人员在非关系数据库上运行原生 Django 项目(包括 Django 的 ORM)的项目,其中之一就是谷歌应用引擎的数据存储。这是除了 Django 一直支持的标准传统 SQL 数据库之外的所有内容。谷歌应用引擎确实具有一些 Django 支持,但该支持主要涉及模板和视图。对于其他允许快速开发的工具,例如表单、内置管理界面或 Django 身份验证,这些都无法直接运行。Django-nonrel 改变了这一点,为 Django 开发人员提供了支持。

总结

本章涵盖了各种有趣的主题。您了解了几种在部署 Django 时有用的基于 Django 的部署选项。您还学会了如何将 Django 项目从开发环境迁移到生产环境。值得注意的是,您学到的这些框架都非常易于使用,因此您将能够在将来的项目中有效地利用它们。

第十五章:接下来是什么?

网络开发随着时间的推移发生了变化,用户消费信息的设备也发生了变化。网络最初是为大屏设备设计的,但最近的趋势表明,小屏设备和手持设备的使用量增加了。因此,有必要调整网络以适应小屏设备,但这些设备对功耗非常敏感。因此,在 Django 中有必要将后端功能与前端功能分开。

其中一个最广泛使用的解决方案是在 Django 后端使用启用了 API 的前端来使用它。对于这种情况,使用AngularJS是最合适的。

REST 一直是 Web 开发的未来,REST API 是现代 Web 的一个组成部分。随着设备之间的碎片化增加,出现了需要一个单一的最小端点的需求,该端点不执行任何呈现操作。例如,信息检索或通信可能尽可能快,也可能扩展,而这方面的呈现或业务逻辑则由现代浏览器使用前端框架来处理。

AngularJS 满足 Django

AngularJS 是一个现代的 JavaScript 框架,用于在浏览器中创建复杂的 Web 应用程序。

自 2009 年以来,AngularJS 一直在快速发展,并被广泛接受为生产级前端框架。现在由 Google 维护。

AngularJS 有一个非常有趣的诞生故事。当 angular 的一位创始人在 3 周内重新创建了一个网页应用程序时,引起了很大的关注,而最初开发这个应用程序需要 6 个月的时间,通过将代码行数从 17,000 行减少到 1,000 行。

AngularJS 在传统 Web 开发框架上有许多特点。其中,一些独特和创新的特点是双向数据绑定、依赖注入、易于测试的代码以及使用指令扩展 HTML 方言。

对于服务器端,我们可以使用Django REST 框架Tastypie来进行 REST 端点。然后,我们可以使用 AngularJS,它专注于 MVC 模型,以鼓励创建易于维护的模块。

Web 技术已经从同步发展到异步,也就是说,网站请求现在大量使用异步调用来刷新内容,而不重新加载页面,一个例子就是你的 Facebook 动态。

AngularJS 是 Django Web 开发中更好的异步需求解决方案之一。

在下面的示例中,我们将使用 AngularJS 创建一个单页面,该页面使用我们已经创建的推文 API。

我们将使用 AngulaJS 列出所有推文,但在此之前,我们需要熟悉 AngularJS 的关键术语:

  • 指令:为此,HTML 文件使用自定义属性和元素进行扩展。AngularJS 使用ng-directives扩展 HTML。ng-app指令用于定义 AngularJS 的应用程序。ng-model指令将 HTML 控件(输入、复选框、单选按钮、选择和文本区域)的值绑定到应用程序。data.ng-bind指令将应用程序数据绑定到 HTML 视图。

  • 模型:这是向用户显示的数据,用户与之交互。

  • 作用域:这是存储模型的上下文,以便控制器、指令和表达式可以访问它。

  • 控制器:这是视图背后的主要业务逻辑。

当我们设计基于 API 的 Web 应用程序时,很有可能(API 的后端和 Web 应用程序的前端)它们位于不同的服务器上。因此,有必要为跨域资源共享配置 Django。

根据维基百科上的定义:

跨域资源共享(CORS)是一种机制,允许从资源原始域之外的另一个域请求网页上的许多资源(例如字体、JavaScript 等)

我们需要修改我们的 Django API,以允许来自其他服务器的请求。我们现在将更新tweets应用程序的api.py文件,以允许对服务器的跨站点请求:

class CORSResource(object):
  """
  Adds CORS headers to resources that subclass this.
  """
  def create_response(self, *args, **kwargs):
    response = super(CORSResource, self).create_response(*args, **kwargs)
    response['Access-Control-Allow-Origin'] = '*'
    response['Access-Control-Allow-Headers'] = 'Content-Type'
    return response

  def method_check(self, request, allowed=None):
    if allowed is None:
      allowed = []

    request_method = request.method.lower()
    allows = ','.join(map(unicode.upper, allowed))
    if request_method == 'options':
      response = HttpResponse(allows)
      response['Access-Control-Allow-Origin'] = '*'
      response['Access-Control-Allow-Headers'] = 'Content-Type'
      response['Allow'] = allows
      raise ImmediateHttpResponse(response=response)

    if not request_method in allowed:
      response = http.HttpMethodNotAllowed(allows)
      response['Allow'] = allows
      raise ImmediateHttpResponse(response=response)
    return request_method

添加了这个类之后,我们可以创建任何资源的子类,以便为跨域请求公开。我们现在将更改我们的Tweet类,以便可以跨站点访问。

让我们将Tweet类更新为以下内容:

class TweetResource(CORSResource, ModelResource):
  class Meta:
    queryset = Tweet.objects.all()
    resource_name = 'tweet'

现在,推文资源已准备好从不同的域访问。

以下是一个基本的 AngularJS 示例:

创建一个名为app.html的单个 HTML 文件(由于此文件与我们现有的 Django 项目无关,因此可以在项目文件夹之外创建),内容如下。当前,此页面使用来自本地磁盘的 AngularJS,您也可以从 CDN 导入页面:

<html ng-app="tweets">
  <head>
    <title>Tweets App</title>
    <script src="img/angular.min.js"></script>
  </head>
  <body>
    <div ng-controller="tweetController"> 
      <table>
        <tr ng-repeat="tweet in tweets">
          <td>{{ tweet.country }}</td>
          <td>{{ tweet.text }}</td>
        </tr>
      </table>
    </div>
    <script src="img/app.js"></script>
  </body>
</html>

在以下代码中,ng-controller指令在其渲染时触发,它处理任何业务逻辑,并将计算的模型注入作用域内。

<div ng-controller="tweetController">标签是一个例子,其中tweetController参数在其div呈现之前被处理。

我们的业务逻辑完全在app.js文件中的 JavaScript 中:

var app = angular.module('tweets', []);
app.controller("tweetController", function($scope,$http) {
  $http({ headers: {'Content-Type': 'application/json; charset=utf-8'},
  method: 'GET',
  url: "http://127.0.0.1:8000/api/v1/tweet/?format=json"
  })
    .success(function (data) {
    $scope.tweets = data.objects;
  })
});

app.js文件向推文的 API 端点发出请求,并将tweets对象注入到作用域中,由 AngularJS 在视图(app.html)中使用ng-repeat循环指令呈现:

  <tr ng-repeat="tweet in tweets">
    <td>{{ tweet.country }}</td>
    <td>{{ tweet.text }}</td>
  </tr>

上述代码的输出如下图所示,显示了国家和推文:

AngularJS meets Django

这只是一个基本的 AngularJS 应用程序,因为高级 Web 开发已完全从后端转移到前端。基于 AngularJS 的应用程序最适合完整的单页应用程序。

使用 Elasticsearch 的 Django 搜索

搜索已成为我们现在处理的大多数应用程序的一个组成部分。从 Facebook,搜索朋友,到 Google,在那里您搜索整个 Web,从博客到日志,一切都需要搜索功能来解锁网站上的隐藏信息。

Web 正以指数速度发展。现在,1GB 的数据已经过时,每天产生数百 TB 的结构化和非结构化数据。

ElasticsearchES)比其他替代方案更好,因为除了提供全文搜索外,它还提供有意义的实时数据分析,并且在集群数据基础设施方面具有高度可扩展性的强大支持。

Elasticsearch 还为您提供了一个简单的 REST API,可以轻松集成到任何自定义应用程序和 Django(更广泛地说,Python)开发环境中,提供了许多很酷的开箱即用的工具来实现 Elasticsearch。

Elasticsearch 网站(www.elasticsearch.org/)包含详尽的文档,网上还有很多很好的例子,这些例子将帮助您构建任何您需要的搜索。通过充分利用 Elasticsearch,您可能可以用它构建自己的“Google”。

安装 Elasticsearch 服务器

首先安装 Java。然后,下载并提取 Elasticsearch。您可以将 ES 作为服务运行,也可以使用以下 Shell 命令启动 ES 服务器(根据您的系统更改路径):

set JAVA_HOME=\absolute\path\to\Java
\absolute\path\to\ES\bin\elasticsearch

如果做得正确,您可以在浏览器中调用以下 URL:

http://127.0.0.1:9200/

它将以以下方式给出响应,但build_hash参数不同:

{
  "status" : 200,
  "name" : "MN-E (Ultraverse)",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.4.1",
    "build_hash" : "89d3241d670db65f994242c8e8383b169779e2d4",
    "build_timestamp" : "2014-11-26T15:49:29Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.2"
  },
  "tagline" : "You Know, for Search"
}

Elasticsearch 带有基本的部署配置。但是,如果您想调整配置,那么请参考其在线文档,并在elasticsearch.yml文件中更改 Elasticsearch 配置。

Elasticsearch 与 Django 之间的通信

Django 可以使用基本的 Python 编程与 Elasticsearch 无缝集成。在此示例中,我们将使用 Python 请求库从 Django 向 Elasticsearch 发出请求。我们可以通过输入以下代码来安装请求:

$pip install requests

对于搜索功能,我们主要需要执行三个操作:

  1. 创建一个 Elasticsearch 索引。

  2. 向索引提供数据。

  3. 检索搜索结果。

创建 Elasticsearch 索引

在加载 Elasticsearch 索引并检索搜索结果之前,Elasticsearch 必须了解有关您的内容以及数据处理方式的一些详细信息。因此,我们创建了一个包含设置和映射的 ES 索引。映射是 ES 中 Django 模型的等价物-用于定义内容的数据字段。

虽然映射是完全可选的,因为 Elasticsearch 会根据其用于索引的信息动态创建映射,但建议您预定义用于索引的数据映射。

创建一个 ES 索引的 Python 示例如下:

  data = {
    "settings": {
      "number_of_shards": 4,
      "number_of_replicas": 1
    },
    "mappings": {
      "contacts": {
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" },
          "mobile": { "type": "string" }
        },
        "_source": {
          "enabled": "true"
        }
      }
    }
  }
}

import json, requests
response = requests.put('http://127.0.0.1:9200/contacts/', data=json.dumps(data))
print response.text

上述代码的输出如下图所示:

创建 Elasticsearch 索引

对于使用 Elasticearch 进行的每个操作,它都会给出一个响应消息,例如{"acknowledged":true},这意味着我们的索引已成功由 Elasticsearch 创建。

我们可以通过执行查询命令来检查映射是否实际已更新:

mapping_response = requests.get('http://127.0.0.1:9200/contacts/_mappings')
print mapping_response.text

以下图显示了 Elasticsearch 已更新了新的映射:

创建 Elasticsearch 索引

在创建了我们的第一个 Elasticsearch 索引之后,我们创建了包含信息的 JSON 字典,并通过 Python 请求将此信息转储到 Elasticsearch 中。"contacts"参数是我们选择的索引名称,我们将使用这个名称来向 Elasticsearch 服务器提供和检索数据。"mappings"键描述了您的索引将保存的数据。我们可以有尽可能多的不同映射。每个映射都包含一个字段,其中存储数据,就像 Django 模型一样。一些基本的核心字段是字符串、数字、日期、布尔值等等。完整的列表在 Elasticsearch 文档中给出。"shards"和"replicas"参数在 ES 术语表中有解释。没有"settings"键,ES 将简单地使用默认值-在大多数情况下这是完全可以的。

向索引提供数据

现在您已经创建了一个索引,让我们将内容存储在其中。一个包含标题、描述和内容作为文本字段的虚构 BlogPost 模型的示例 Python 代码如下:

import json, requests
data = json.dumps(
  {"name": "Ratan Kumar",
  "email": "mail@ratankumar.org",
  "mobile": "8892572775"})
response = requests.put('http://127.0.0.1:9200/contacts/contact/1', data=data)
print response.text

您将看到如下所示的输出:

向索引提供数据

这个确认显示我们的联系人数据已被索引。当然,索引单个数据并搜索它并没有太多意义,所以在进行检索查询之前,我们将索引更多的联系人。

Elasticsearch 还提供了批量索引,可以按如下方式使用:

import json, requests
contacts = [{"name": "Rahul Kumar",
  "email": "rahul@gmail.com",
  "mobile": "1234567890"},
  {"name": "Sanjeev Jaiswal",
  "email": "jassics@gmail.com",
  "mobile": "1122334455"},
  {"name": "Raj",
  "email": "raj@gmail.com",
  "mobile": "0071122334"},
  {"name": "Shamitabh",
  "email": "shabth@gmail.com",
  "mobile": "9988776655"}
]

for idx, contact in enumerate(contacts):
  data += '{"index": {"_id": "%s"}}\n' % idx
  data += json.dumps({
    "name": contact["name"],
    "email": contact["email"],
    "mobile": contact["mobile"]
  })+'\n'

让我们看一下以下的屏幕截图:

向索引提供数据

正如您在前面的屏幕截图中所看到的,"status": 201参数在 HTTP 状态中表示记录已成功创建。Elasticsearch 逐行读取数据,因此我们在每个数据集的末尾使用了"\n"。批量操作比运行多个单个请求要快得多。

这个例子是一个简单的 JSON 例子。当我们在 Django 应用程序中使用 Elasticsearch 时,相同的 JSON 对象可以被 Django 模型替换,并且可以通过ModelName.objects.all()查询获取所有 Django 模型对象,然后解析并保存它。此外,在手动 ID 的情况下,正如我们在前面的例子中使用的那样,它是索引计数,如果您将主键用作 Elasticsearch ID 进行索引,那将更加方便。这将帮助我们直接查询结果对象,如果我们没有将对象信息作为有效负载传递。

从索引中检索搜索结果

搜索索引相当简单。同样,我们使用 Python 请求将 JSON 编码的数据字符串发送到我们的 ES 端点:

data = {
  "query": {
    "query_string": { "query": "raj" }
  }
}

response = requests.post('http://127.0.0.1:9200/contacts/contact/_search', data=json.dumps(data))
print response.json()

这给出了如下图所示的结果:

从索引中检索搜索结果

在示例中,我们正在搜索我们的联系人索引中的术语"raj"。ES 以 JSON 编码格式返回所有按相关性排序的命中。每个命中都包含一个"_id"字段,该字段提供了所关联博客文章的主键。使用 Django 的 ORM,现在可以简单地从数据库中检索实际对象。

注意

ES 搜索端点提供了无限的选项和过滤器;从大型数据集中快速检索、分页以及构建强大搜索引擎所需的一切。

这只是冰山一角。当您使用 Elasticsearch 构建 Django 应用程序时,您将探索许多有趣的功能,例如聚合,可以在前面的示例中使用。它列出了 Ratan 的所有联系信息和自动完成,该功能将用于建议用户在搜索联系人的搜索框中开始输入时从 Elasticsearch 中完整地名称。

摘要

在本章中,我们了解了在涉及 Django 项目时最常用的两个重要组件,即 AngularJS 和 Elasticsearch。作为前端框架,AngularJS 不仅通过将渲染逻辑推送到浏览器来减少服务器的负载,还为用户在使用基于 AngularJS 的应用程序时提供丰富的体验。

另一方面,Elasticsearch 是最流行的搜索引擎之一,也是开源的。设置和扩展 Elasticsearch 的便利性使其成为任何搜索引擎需求的选择。您也学到了一些关于 Django 的知识。正如本章开始时所说,我们相信您的目标是学习一项技能并成为其中的专家。好吧,这只是开始;还有更多的事情需要您去探索,以达到本章讨论的每个主题的专家级水平。我们已经到达了本书的结尾。在本书中,我们从头开始构建了一个微型博客应用程序的过程,使用 Django 作为我们的框架。我们涵盖了许多与 Web 2.0 和社交应用程序相关的主题,以及许多 Django 组件。您可以随时参考 Django 的在线文档。如果您想了解特定功能或组件的更多信息,请访问docs.djangoproject.com

感谢选择本书来学习 Django Web 开发基础知识。我们祝愿您在职业生涯中取得成功。

posted @ 2024-05-20 16:48  绝不原创的飞龙  阅读(15)  评论(0编辑  收藏  举报