第11章 Web框架:Django

Python是唯一一种Web框架比语言关键字多的语言。

——Harald Armin Massa, 2005年12月

本章内容:

简介;

Web框架;

Django简介;

项目和应用;

“Hello World”应用(一个博客);

创建模型来添加数据库服务;

Python应用shell;

Django管理应用;

创建博客的用户界面;

改进输出;

处理用户输入;

表单和模型表单;

视图进阶;

*改善外观;

*单元测试;

中级Django应用:TweetApprover;

资源。

11.1 简介

本章不再介绍 Python 标准库,而介绍一个著名的 Web 框架:Django。首先简要介绍什么是Web框架,接着介绍使用Django开发应用。从基础开始介绍Django,并开发一个“Hello World”应用。接着逐步深入,介绍开发实际应用时所要了解的内容。这个路线图也组成了本章的架构:首先夯实基础;然后介绍中级应用,这个应用会涉及Twitter、电子邮件和OAuth(OAuth是一个开放的授权协议,用于通过应用编程接口[API]访问数据)。

本章旨在介绍一款工具,Python开发者每天都会用这款工具解决实际问题。通过本章,读者会学到一些技能和足够的知识,来通过Django构建更复杂的工具。读者可以带着这些技能去学习任何其他Python Web框架。首先,了解什么是Web框架。

11.2 Web框架

这里希望读者通过第10章的学习,对Web开发有了足够的了解。Web开发除了像上一章那样全部从头写起,还可以在其他人已有的基础上进行开发,简化开发流程。这些Web开发环境统称为Web框架,其目标是帮助开发者简化工作,如提供一些功能来完成一些通用任务,或提供一些资源来用于降低创建、更新、执行或扩展应用的工作量。

前面还提到,由于 CGI在可扩展性方面有缺陷,因此不建议使用它。所以Python 社区的人们寻求一种更强大的Web服务器解决方案,如Apache、ligHTTPD(发音为“lighty”),或nginx。有些服务器,如Pylons和CherryPy,拥有自己的框架生态系统。但服务方面的内容只是创建Web应用的一个方面。还需要关注一些辅助工具,如JavaScript框架、对象关系映射器(ORM)或底层数据库适配器。还有与 Web 不相关但其他类型的开发需要的工具,如单元测试和持续集成框架。Python Web框架既可以是单个或多个子组件,也可以是一个完整的全栈系统。

术语“全栈”表示可以开发Web应用所有阶段和层次的代码。框架可以提供所有相关的服务,如Web服务器、数据库ORM、模板和所有需要的中间件hook。有些还提供了JavaScript库。Django就是这当中一个广为人知的Web框架。许多人认为Django对于Python,就相当于Ruby on Rails对Ruby一样。Django包含了前面提到的所有服务,可作为全能解决方案(除了没有内置的 JavaScript 库,这样可以自由选择相应的库)。在第 12 章将看到 Google App Engine也提供了这些组件,但更适合于由Google托管并且侧重于可扩展性和快速请求/响应的Web和非Web应用。

Django是由一个开发团队作为单个突出创建的,但并不是所有框架都遵循这种哲学。以TurboGears为例,这是一个非常优秀的全栈系统,由分散在全世界的开发者开发,其作为胶水代码,将栈中其他独立的组件组合起来,如ToscaWidgets(高级Web部件,它可利用多种JavaScript框架,如Ex1tJs、jQuery等)、SQLAlchemy(ORM)、Pylons(Web服务器),还有Genshi(模板化)。遵循这种架构样式的框架能提供很好的灵活性,用户可以选择不同的模板系统、JS库、生成原始SQL语句的工具,以及不同的Web服务器。只须牺牲一点一致性和放弃单一工具的追求。但对框架的使用也许与之前的方式没什么区别。

Pyramid是另外一个非常著名的Web框架,这是repoze.bfg(或简称BFG)和Pylons的继承者。Pyramid的方式更加简单,它只提供一些基础功能,如URL分派、模板化、安全和一些资源。如果需要其他功能,必须手动添加。这种极简的方式带来的好处就是Pyramid拥有完整的测试和文档,以及从Pylons和BFG社区继承的用户,让Pyramid成为今日Python Web框架中有力的竞争者。

如果读者刚接触到 Python,可能会了解 Rails 或 PHP,这两者原先只想将语言嵌入到HTML中,后来扩展成一个庞大框架。Python的好处就是不必局限于“一种语言,一种框架”的形式。从Python中可以选择许多框架,就如同本章起始处的引用所说的那样。Web服务器网关接口(WSGI)标准的建立加速了Web框架的发展。Python WSGI由PEP 333定义,参见:http://python.org/dev/peps/pep-0333。

如果读者还不了解WSGI,这里有必要简要说明一下。WSGI不是实际的代码或API,而定义了一系列接口,让Web 框架的开发者无须为框架创建自定义Web 服务器,也让应用程序开发者可以自行选择Web服务器。有了WSGI,应用开发者就可以方便地切换(或开发新的)WSGI兼容的服务器,而无须担心需要改变应用代码。关于WSGI的更多内容,可阅读上一章。

有一点不知道是否应该在这里提及(特别是在书中),当热情的Python开发者不满足已有的框架时,他们就会创建一个新框架。Python中Web框架的数目比关键字的数目还多。其他框架还包括 web2py、web.py、Tornado、Diesel 和 Zope。可以在 Python 官网的维基页面http://wiki.python.org/moin/WebFrameworks来了解这些框架。

回到正文,现在在这些Web开发的相关知识的基础上来学习Django。

11.3 Django简介

Django自称是“能够很好地应对应用上线期限的Web框架”。其最初在21世纪初发布,由Lawrence Journal-Wor ld报业的在线业务的Web开发者创建。2005年正式发布,引入了以“新闻业的时间观开发应用”的方式。本章中我们会使用Django开发一个简单的博客应用,下一章会用Google App Engine开发相同的应用,比较两者来看Django的开发速度(这里的博客比较简单,读者需要自行完善)。尽管会直接给出这个例子,但在介绍的过程中仍然会详细解释示例。如果读者想深入了解,可以阅读 Python Web Development with Django(Addison-Wesley, 2009)第2章,该书由我和我尊敬的同事Jeff Forcier (Fabric主要开发者)和Paul Bissex(dpaste创建者)编写。

核心提示:对Python 3的支持

在撰写本书时,Django 2已经并仅支持Python 3,因此本章的所有示例都以Python 2.x编写。不过本书的网站中含有所有的Python 3版本的示例。

11.3.1 安装

在介绍Django开发之前,首先安装必需的组件,这包括依赖组件和Django本身。

预备条件

在安装Django之前,必须先安装Python。由于读者已经读到本书第10章了,因此假设已经安装了Python。大多数兼容POSIX的系统(Mac OS X、Linux、*BSD)都已经安装了Python。只有微软Windows需要自行下载并安装Python。

Apache是Web服务器中的王者,因此大多数部署都会使用这款服务器。Django团队建议使用mod_wdgi这个Apache模块,并提供了安装指南:http://docs.djangoproject.com/en/dev/topics/install/#install-apache-and-mod-wsgi,同时也提供了完整的开发文档,参见 http://docs.djangoproject.com/en/dev/howto/deployment/modwsgi/。还有一份更好的文档,其中介绍了使用一个 Apache 实例来持有多个 Django Web 站点(项目),参见 http://forum.webfaction.com/viewtopic.php?id=3646。如果想了解 mod_python,只能在老的 Django 安装包或在mod_wsgi 成为标准之前的一些操作系统的发行版中寻找。官方已经不支持 mod_python(实际上,从Django 1.5开始,就移除了mod_python)。

在结束对Web服务器的讨论之前 [3],还需要提醒读者,在生产环境的服务器中并不是一定要使用Apache,还可以有其他选择,其中有些内存占用量更少,速度更快。也许其中一个就更适合你的应用。可以在http://code.djangoproject.com/wiki/ServerArrangements中查找符合要求的Web服务器。

Django需要用到数据库。当前的标准版Django只可运行基于SQL的关系数据库管理系统(RDBMS)。用户主要使用4种数据库,分别是PostgreSQL、MySQL、Oracle和SQLite。其中最容易设置的是SQLite。另外,SQLite是这4个当中唯一一个无须部署数据库服务器的,所以使用起来也是最简单的。当然,简单并不代表无能,SQLite 功能和另外三个一样强大。

为什么 SQLite 很容易设置?SQLite 数据库适配器是所有Python 版本中自带的(从 2.5开始)。注意,这里说的是适配器。有些Python发行版自带了SQLite本身,有些会使用系统上安装的SQLite。而其他东西则需要手动下载和安装。

Django支持众多关系数据库,SQLite只是其中一种,所以如果不喜欢SQLite,可以使用其他数据库,特别是如果公司已经使用了某一款基于服务器的数据库。关于Django和数据库安装的更多内容,可以参考http://docs.djangoproject.com/en/dev/topics/install/#data- base-installation.。

最近还有快速发展的非关系数据库(NoSQL)。大概这是因为这种类型的系统提供了额外的可扩展性,能面对不断增长的数据量。如果处理像Facebook、Twitter或类似服务那样的海量数据,关系数据库需要手动分区(切分)。如果需要使用 NoSQL 数据库,如 MongoDB或Google App Engine的原生Datastore,可以尝试Django-nonrel,这样用户就可以选择使用关系或非关系数据库。(需要说明一下,Goolge App Engine 也有一个关系数据库(兼容MySQL),即 Google Cloud SQL)。

可以从http://www.allbuttonspressed.com/projects/ django-nonrel下载Django-nonrel,以及其适配器,参见https:// github.com/FlaPer87/django-mongodb-engine(Django和MongoDB),或者http://www.allbuttonspressed.com/projects/djangoappengine(Django和Google App Engine的Datastore)。在本书编写时,由于Django-nonrel是Django的分支,因此只能安装其中一个。主要原因是因为需要在开发环境和生产环境中使用相同的版本。如同在 http://www.allbuttonspressed.com/projects/django-nonrel上说的那样,“(Django-nonrel)只对Django进行了一丁点修改(可能少于100行)”。Django-nonrel可作为压缩文件下载,所以直接解压它,在对应的目录中执行下面的命令。

$ sudo python setup.py install

如果下载Django压缩包,其安装方法完全相同(如下所示),所以完全可以跳过下一节,直接开始学习教程。

安装Django

有多种方法可以安装 Django,下面对这些安装方法按难易程度排序,越靠前的越简单。

Python包管理器

操作系统包管理器

官方发布的压缩包

源码库

最简单的下载和安装方式是使用 Python 包管理工具,如 Setuptools 中的 easy_install (http://packages.python.org/distribute/easy_install.html),或 pip(http:// pip.openplans.org),所有平台上都可使用这两个工具。对于Windows用户,使用Setuptools时需要将easy_install.exe文件放在Python安装目录下的Scripts文件夹中。此时只须在DOS命令行窗口中使用一条命令就能安装Django。

C:\WINDOWS\system32>easy_install django

Searching for django

Reading http://pypi.python.org/simple/django/

Reading http://www.djangoproject.com/

Best match: Django 1.2.7

Downloading http://media.djangoproject.com/releases/1.2/Django-

1.2.7.tar.gz

Processing Django-1.2.7.tar.gz

Adding django 1.2.7 to easy-install.pth file

Installing django-admin.py script to c:\python27\Scripts

Installed c:\python27\lib\site-packages\django-1.2.7-py2.7.egg

Processing dependencies for django

Finished processing dependencies for django

为了无须输入easy_install.exe的全路径,建议将C:\Python2x\Scipts添加到PATH环境变量 [4]中,其中2.x根据Python的版本来决定。如果使用的是POSIX系统,easy_install会安装到众所周知的/usr/bin或/usr/local/bin中,所以无须再将其添加到PATH中,但可能需要使用sudo命令来将软件安装到一些典型的系统目录中,如/usr/local。命令如下所示。

$ sudo easy_install django

pip的命令(不使用virtuabanv)如下所示。

$ pip install django #sudo

只有在安装到需要超级用户权限的路径中时才会用到sudo;如果安装到用户目录中则不需要。这里还建议使用“容器”环境,如virtualenv。使用virtualenv可以同时安装多个版本的Python、Django、数据库等。每个环境在独立的容器中运行,可以自由创建、管理、执行、销毁。关于virtualenv的更多内容可以参见http://pypi.python.org/pypi/virtualenv。

另一种安装Django的方式是使用操作系统自带的包管理器(前提是系统有包管理器)。一般仅限于POSIX类的操作系统,如Linux和Mac OS X。操作命令如下所示。

(Linux) $ sudo COMMAND install django

(Mac OS X) $ sudo port install django

对于 Linux 用户,COMMAND是对应发行版的包管理器,如 apt-get、yum、aptitude等。可以从http://docs.djangoproject.com/en/dev/ misc/distributions中找到不同发行版的安装指导。

除了上面提到的方法之外,还可以从Django网站直接下载并安装原始发布的压缩包。下载并解压后,就可以使用普通的命令进行安装。

$ sudo python setup.py install

http://docs.djangoproject.com/en/dev/topics/install/#installing-an-official-release中可以找到更详细的安装指南。

专业开发者可能更喜欢从Subversion源码树中自行获取最新的源码。关于这种安装过程,可以参考http://docs.djangoproject.com/en/dev/topics/install/#installing-the-development-version。

最后,http://docs.djangoproject.com/en/dev/topics/install/#install-the-django-code 包含了所有的安装指南。

下一步是设置服务器,确保所有组件安装完毕并能正常工作。但在此之前,先介绍一些基本的Django概念、项目(project)和应用(app)。

11.4 项目和应用

Django中的项目和应用是什么?简单来说,可以认为项目是一系列文件,用来创建并运行一个完整的Web站点。在项目文件夹下,有一个或多个子文件夹,每个子文件夹有特定的功能,称为应用。应用并不一定要位于项目文件夹中。应用可以专注于项目某一方面的功能,或可以作为通用组件,用于不同的项目。应用是一个具有特定功能的子模块,这些子模块组合起来就能完成 Web 站点的功能。如管理用户/读者反馈、更新实时信息、处理数据、从站点聚合数据等。

从Pinax平台上能找到比较著名的可重用的Django应用。其中包括(但不限于)验证模块(OpenID支持、密码管理等)、消息处理(E-mail验证、通知、用户间联系、兴趣小组、主题讨论等),以及其他功能,如项目管理、博客、标签、导入联系人等。关于Pinax的更多内容可以访问其网站:http://pinaxproject.com。

项目和应用的概念简化了可插拔的使用方式,同时也强烈鼓励了敏捷设计和代码重用。现在知道了什么是项目和应用,下面开始创建一个项目。

11.4.1 在Django中创建项目

Django带有一个名为django-admin.py的工具,它可以简化任务,如创建前面提到的项目目录。在POSIX平台上,它一般会安装到/usr/local/bin、/usr/bin这样的目录中。如果使用的是 Windows系统,它会安装到Scripts文件夹下,该文件夹位于Python安装目录下,如 C:\Python27\Scripts。无论是 POSIX 还是 Windows 系统,都应该确保django-admin.py位于 PATH 环境变量中,这样它在可以在命令行中执行(否则需要使用全路径名调用解释器)。

对于Windows系统,需要手动将C:\Python27和C:\Python27\Scripts(或自己设定的其他Python安装路径)添加到PATH变量中。首先打开控制面板,单击“系统”;或右击“我的电脑”,接着选择“属性”。在打开的窗口中选择“高级”标签,单击“环境变量”按钮。可以选择编辑单个用户的PATH项(上方的列表框),或者所有用户的PATH(下方的列表框),接着在Variable Value文本框中的末尾添加“;C:\Python27;C:\Python27\Scripts”,如图11-1所示。

在(任意一个平台上)设置好PATH以后,应该可以执行Python并获得一个交互式解释器,并查看Django的django-admin.py命令的使用方法。打开UNIX shell或DOS命令行,执行命令的名称。如果一切正常,就继续下面的内容。

下一步是到转到需要放置代码的文件夹或目录中。要在当前目录中创建项目,可以使用下面的命令(这里使用比较常见的项目名,如mysite,读者也可以使用其他名称)。

$ django-admin.py startproject mysite

models.py - 图1 图11-1 将Python添加到Windows PATH变量中

注意,如果使用的是Windows PC,首先必须打开DOS命令行窗口。在DOS中,命令行提示符类似C:\WINDOWS\system32,而不是POSIX系统中的美元符号($)或老式机器中的百分号(%),现在来看这些命令创建在该目录下创建了哪些内容。在 POSIX 系统上它应该类似下面这样。

$ cd mysite

$ ls -l

total 32

-rw-r—r—  1 wesley   admin  0 Dec  7 17:13 init.py

-rw-r—r—  1 wesley   admin 546 Dec 7 17:13 manage.py

-rw-r—r—  1 wesley   admin 4778 Dec 7 17:13 settings.py

-rw-r—r—  1 wesley   admin  482 Dec 7 17:13 urls.py

如果在 Windows上开发,打开文件浏览器,找到这个文件夹,如图11-2 所示,已经预先创建了名为C:\py\django的文件夹,用于放置项目。

models.py - 图2 图11-2 Windows系统上的mysite文件夹

在Django中,基本的项目含有4个文件,分别是init.py、manage.py、setting.py、urls.py (后面会添加到应用中)。表11-1解释了这些文件的用途。

表11-1 Django项目文件 models.py - 图3

读者会注意到,startproject命令创建的每个文件都是纯Python源码文件,没有.ini文件、XML数据,或其他配置语法。Django尽力坚持“纯粹的Python”这一信条。这样既可以在不向框架添加复杂东西的情况下拥有灵活性,同时也可以根据不同的情况从其他文件导入额外的配置,或动态计算数值,而不是硬编码。Django中不适用其他内容,只有纯Python。读者也可能注意到了django-admin.py也是Python脚本。其作为用户和项目之间的命令行接口。而manage.py同样可以用这种方式管理应用(这两条命令都有Help选项,可以从中了解到关于使用方面更多的信息)。

11.4.2 运行开发服务器

到目前为止,还没有创建一个应用。尽管如此,已经可以使用一些Django功能了。其中一个最方便的是Django内置的Web服务器。该服务器运行在本地,专门用于开发阶段。注意,这里强烈建议不要用这个服务器部署公开页面,因为其仅用于开发用途。

为什么会存在这个开发服务器?主要有以下几点原因。

1.使用开发服务器,可以直接运行与测试项目和应用,无需完整的生产环境。

2.当改动Python源码文件并重新载入模块时,开发服务器会自动检测。这样既能节省时间,也能方便地使用系统,无须每次编辑代码后手动重启。

3.开发服务器知道如何为 Django管理应用程序寻找和显示静态媒体文件,所以无须立即了解管理方面的内容(后面会介绍相关内容,现在只是不要把它与django-admin.py脚本弄混了)。

通过项目中的manage.py工具,可以使用下面这个简单的命令运行开发服务器。

(POSIX) $ python ./manage.py runserver

(PCs) C:\py\django\mysite> python manage.py runserver

如果使用POSIX系统,并使用$ chmod 755 manage.py来授予脚本执行许可,就无须显式调用python,如$ ./manage.py runserver。在DOS命令行窗口中也同样可以做到,只需Python正确安装到Windows注册表中即可。

启动服务器后,应该能看到和下面例子相似的输出(Windows使用不同的键组合来退出程序)。

Validating models…

0 errors found.

Django version 1.2, using settings 'mysite.settings'

Development server is running at http://127.0.0.1:8000/

Quit the server with CONTROL-C.

在浏览器中打开链接(http://127.0.0.1:8000/或http://localhost:8000/),就可以看到Django的“It worked!”页面,如图11-3所示。

如果需要使用不同的端口运行服务器,可以在命令行中指定。例如,如果需要在端口8080运行它,可以使用这条命令:$ python ./manage.py runserver 8080。读者可以在下面这个链接中找到所有的 runserver 选项:http://docs.djangoproject.com/en/dev/ref/django-admin/#django-a dmin-runserver。

如果看到了图11-3中的“It worked!”页面,那么就表示一切正常。此时,如果查看命令行中的会话,可以看到开发服务器已经记录了GET请求。

[11/Dec/2010 14:15:51] "GET / HTTP/1.1" 200 2051

models.py - 图4 图11-3 Django初始的“It worked!”页面

日志的每一行含有4个部分,从左到右,依次是时间戳、请求、HTTP响应编码,以及字节数(读者可能有不同的字节数)。“It worked!”页面很友好地告诉用户开发服务器正在工作,现在可以创建应用了。如果服务器没有正常工作,检查前面的步骤。此时甚至可以直接删除整个项目,从头开始,而不是在这里就开始调试。

当服务器成功运行时,就可以设置第一个Django应用。

11.5“Hello World”应用(一个博客)

既然拥有了一个项目,就可以在其中创建应用。为了创建一个博客应用,继续使用manage.py:

$ ./manage.py startapp blog

如之前的项目一样,这里可以自行起名字,并不一定要使用blog这个名称。这一步与启动一个项目同样简单。现在在项目目录中有了一个blog目录。下面介绍了其中的内容,首先用POSIX格式列出其中的内容,接着使用Windows的截图显示(见图11-4)。

$ ls -l blog

total 24

-rw-r—r— 1 wesley admin 0 Dec 8 18:08 init.py

-rw-r—r— 1 wesley admin 175 Dec 10 18:30 models.py

-rw-r—r— 1 wesley admin 514 Dec 8 18:08 tests.py

-rw-r—r— 1 wesley admin 26 Dec 8 18:08 views.py

models.py - 图5 图11-4 Windows系统中的blog文件夹

表11-2介绍了其中的应用文件。

表11-2 Django应用文件 models.py - 图6

与项目类似,应用也是一个Python包。但在这里,models.py和views.py文件中目前还没有真正的代码,需要开发者在今后添加代码。单元测试文件 tests.py 也是如此。同样,即使可以使用项目的URLconf来分派访问,也不会自动创建本地应用的URLconf。这需要手动创建它,接着使用项目 URLconf 里的 include()指令将请求分配给应用的URLconf。

为了让Django知道这个新的应用是项目的一部分,需要编辑settings.py(可以将其理解为配置文件)。使用编辑器打开这个文件,找到位于底部的INSTALLED_APPS这个元组。将应用名称(blog)添加到元组的末尾,如下所示。

INSTALLED_APPS = (

'blog',

)

虽然结尾的逗号不是必需的,但如果今后向该元组中添加其他项,就无须添加逗号。Django使用INSTALLED_APPS来配置系统的不同部分,包括自动管理应用程序和测试框架。

11.6 创建模型来添加数据库服务

现在接触到了基于Django的博客系统的核心:models.py文件。在这里将定义博客的数据结构。遵循的规则是“不要自我重复”(Don’t Repeat Yourself,DRY)原则,Django可以从为应用提供的模型信息获取很多好处。首先创建一个基本模型,接着来看Django使用这些信息自动创建的内容。

数据模型表示将会存储在数据库每条记录中的数据类型。Django提供了许多字段,用来将数据映射到应用中。在这个应用中,将使用三个不同的字段类型(参见下面的示例代码)。

使用编辑器打开models.py,在文件中已存在的import语句后面直接添加下面的模型类。

models.py

from django.db import models

class BlogPost(models.Model):

title = models.CharField(max_length=150)

body = models.TextField()

timestamp = models.DateTimeField()

这是一个完整的模型,表示一个“博文”对象,其中含有三个字段(更准确地说,其中含有四个字段,还有一个是Django默认会自动创建的字段,该字段可以自动递增,且每个模型中唯一的)。现在来看刚刚创建的BlogPost类,这是django.db.models.Model的子类。Model是Django中用于数据模型的标准基类,这是Django强大的ORM的核心。BlogPost中的字段就像普通的类属性那样定义,每个都是特定字段类的实例,每个实例对应数据库中的一条记录。

对于这个应用,使用 CharField 作为博文的 title,并限制了该字段的最大长度。CharField可用于较短的单行文本。对于较长的文本,如博文的正文,使用TextField类型。最后,timestamp使用DateTimeField。DateTimeField使用Python的datetime.datetime对象表示。

这些字段类同样定义在 django.db.models 中,其中还有其他字段类型,如 BooleanField和 XMLField。若想了解可用字段的完整列表,可以阅读官方文档,参见 http://docs.djangoproject.com/en/dev/ref/models/fields/#field-types。

11.6.1 设置数据库

如果还没有安装并运行一个数据库服务器,则强烈建议使用方便易用的SQLite。SQLite速度快、可用范围广,SQLite将数据库另存为文件系统中的单个文件。访问控制就是简单的文件访问权限。如果不想使用SQLite,而是使用其他数据库服务器,如MySQL、PostgreSQL、Oracle,那么可以使用数据库管理工具为Django项目创建新的数据库。

使用MySQL

有了空的数据库后,剩下的就是通知 Django 来使用它。此时需要再次用到项目的settings.py文件。关于数据库,有6个相关的设置(虽然这里可能只会用到两个):ENGINE、NAME、HOST、PORT、USER和PASSWORD。从名称就能很明显地看出其中的用途。这里只须在相关设置选项后面填上需要让Django使用的数据库服务器中合适的值即可。例如,针对MySQL的设置会类似下面这样。

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.mysql',

'NAME': 'testdb',

'USER': 'wesley',

'PASSWORD': 's3Cr3T',

'HOST': '',

'PORT': '',

}

}

注意,如果使用的是较老版本的Django,则不会将所有设置放在单个字典类型的变量中,而是会存放在许多单独的、模块级别的变量中。

这里没有指定PORT的值,因为只有在数据库服务器在非标准端口上运行时才需要设定该值。例如,MySQL服务器默认情况下会使用3306端口。除非改变了设置,否则无须指定PORT。HOST一项也留空,表示数据库服务器与应用程序运行在同一台计算机上。在继续使用Django之前,要确认已经执行CREATE DATABASE testdb来创建了数据库,且用户和密码已经存在。PostgreSQL的设置与MySQL类似,但Oracle有所不同。

关于设置新的数据库、用户和配置的更多信息,可参考 http://docs.djangoproject.com/en/dev/intro/tutorial01/#database-setuphttp://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASES中的Django文档以及Python Web Development with Django一书的附录B (如果读者有这本书)。

使用SQLite

SQLite一般用于测试。它甚至可以用于特定环境下的部署,如应用于无须大量同时写入需求的场景。SQLite 没有主机、端口、用户,或密码信息。因为 SQLite 使用本地文件来存储数据,本地文件系统的访问权限就是数据库的访问控制。SQLite不仅可以使用本地文件,还可以使用纯内存数据库。因此针对SQLite数据库的配置,在settings.py中的DATABASES配置中只有ENGINE和NAME字段。

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.sqlite3',

'NAME': '/tmp/mysite.db', # use full pathname to avoid confusion

}

}

使用实际的Web服务器(如Apache)来使用SQLite时,需要确保拥有Web服务器进程的账户同时拥有数据库文件本身和含有数据库文件的目录的写入权限。当使用这里的开发服务器时,就无须关心权限问题,因为运行开发服务器的用户同时拥有项目文件和目录的访问权限。

Windows用户也经常选择SQLite,因为其中自带Python(从2.5版本开始)。由于已经在C:\py\django文件夹下创建了项目和应用,因此在这里继续创建db目录,并指定将要创建的数据库文件的名称。

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.sqlite3',

'NAME': r'C:\py\django\db\mysite.db', # full pathname

}

}

如果读者有一定的Python开发经验,可能明白在路径名称前面的“r”表示这是一个Python原始字符串。即Python会使用字符串中的每个原始字符,不会进行转义。如“\n”会解释成一个反斜杠“\”,紧接着是字母“n”,而不是单个换行符。一般在DOS文件的路径名,和正则表达式中会用到 Python 原始字符串,因为这两者中经常会含有反斜杠,反斜杠在 Python中是特殊的转义字符。更多内容,参见Core Python Programming或Core Python Language Fundamentals中“序列”一章中关于字符串的一节。

11.6.2 创建表

现在需要通知Django使用上面给出的链接信息来连接数据库,设置应用程序需要的表。需要使用manage.py和其中的syncdb命令,如下所示。

$ ./manage.py syncdb

Creating tables …

Creating table auth_permission

Creating table auth_group_permissions

Creating table auth_group

Creating table auth_user_user_permissions

Creating table auth_user_groups

Creating table auth_user

Creating table auth_message

Creating table django_content_type

Creating table django_session

Creating table django_site

Creating table blog_blogpost

当执行这个suncdb命令后,Django会查找INSTALLED_APPS中列出的应用的models.py文件。对于每个找到的模型,它会创建一个数据库表(大部分情况下如此)。如果使用的是SQLite,会注意到mysite.db这个数据文件刚好创建在设置中指定的文件夹里。

默认情况下,位于 INSTALLED_APPS 中的其他项都含有模型,也会这样处理。从manage.py syncdb命令的输出可以确认了这一点。从中可以看出,Django会为每个应用创建一个或多个表。下面列出来的并不是syncdb命令的所有输出。还有一些与django.contrib.auth应用相关的交互式查询(详见下面的例子)。这里建议创建一个超级用户,因为后面会用到。下面列出了syncdb命令的处理过程中的末尾部分。

You just installed Django's auth system, which means you don't have

any superusers defined.

Would you like to create one now? (yes/no): yes

Username (Leave blank to use 'wesley'):

E-mail address: @.com

Password:

Password (again):

Superuser created successfully.

Installing custom SQL …

Installing indexes …

No fixtures found.

现在,在授权系统中有了一个超级用户。这会简化后面添加Django的自动管理应用的流程。

最后,设置过程包含了与 fixtures 特性相关的一行,这个特性表示数据库预先存在的系列化内容。在任何新建的应用中,可以使用 fixtures 来预加载这种数据类型。初始的数据库设置此时已经完成。当下次在该项目中运行syncdg命令时(在任何时候想要在该项目中添加一个应用或模型时),将会看到只有少量输出,原因是不需要第二次设置这些表,也不会提示你创建一个超级用户。

此时,完成了应用的数据模型部分。它可以接受用户的输入。但还无法做到这一点。如果读者了解Web应用设计中的MVC模式,会意识到已经完成了模型,但还缺少视图(面向用户的HTML、模板等)和控制器(应用逻辑)。

核心提示:MVC与MTV

Django社区使用另一种形式的MVC模式。在Django 中,它称为模型-模板-视图,简称MTV。数据模型保持不变,但视图在Django中是模板,因为模板用来定义用户可见的内容。最后,Django中的视图表示视图函数,它们组成控制器的逻辑。这与MVC完全相同,但仅仅是对不同角色进行了不同的解释。关于这方面的 Django 哲理,可以从http://docs.djangoproject.com/en/dev/faq/general/#django-appears-to-be-a-mvc-framework-but-you-call-the-controller-the-view-and-the-view-the-tem- plate-how-come-you-don-t-use-the-standard-names中寻找FAQ答案。

11.7 Python 应用shell

Python用户都知道交互式解释器的强大之处。Django的创建者也不例外,他们将其集成进了 Django 中。本节将介绍使用 Python shell 来执行底层的数据自省和处理,这些任务在Web应用开发中不易完成。

11.7.1 在Django中使用Python shell

即使没有模板(view)或视图(controller),我们也依然可以通过添加一些BlogPost项来测试数据模型。如果应用由 RDBMS支持,如大多数 Django应用那样,则可以为每个 blog项的表添加一个数据记录。如果使用的是NoSQL数据库,如MongoDB或Google App Engine的Datastore,需要向数据库中添加其他对象、文档或实体。

那么如何做到这一点呢?Django提供了Python 应用shell,通过这个工具,可以实例化模型,并与应用交互。在使用manage.py中的shell命令时,Python用户会认出熟悉的交互式解释器的启动和提示信息。

$ python2.5 ./manage.py shell

Python 2.5.1 (r251:54863, Feb 9 2009, 18:49:36)

[GCC 4.0.1 (Apple Inc.build 5465)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

(InteractiveConsole)

>>>

Django shell和标准的Python交互式解释器的不同之处在于后面将要介绍的额外功能, Django shell更专注于Django项目的环境。可以与视图函数和数据模型交互,因为这个shell会自动设置环境变量,包括sys.path,它可以访问Django与自己项目中的模块和包,否则需要手动配置。除了标准 shell 之外,还有其他的交互式解释器可供选择,在 Core python Programming或Core Python Language Fundamentals的第1章中对其做了介绍。

Django更倾向于使用功能更丰富的shell,如IPython和bpython,这些shell在普通解释器的基础上提供极其强大的功能。运行shell命令时,Django首先查找含有扩展功能的shell,如果没找到则会返回标准解释器。

在前面的例子中,使用Python 2.5的解释器。因此,使用的也是标准解释器。现在执行manage.py shell时,由于找到了IPython,于是就使用这个shell。

$ ./manage.py shell

Python 2.7.1 (r271:86882M, Nov 30 2010, 09:39:13)

[GCC 4.0.1 (Apple Inc.build 5494)] on darwin

Type "copyright", "credits" or "license" for more information.

IPython 0.10.1 — An enhanced Interactive Python.

?     -> Introduction and overview of IPython's features.

%quickref -> Quick reference.

Help   -> Python's own help system.

object?  -> Details about 'object'.?object also works, ?? prints

more.

In [1]:

读者也可以使用—plain选项来强制使用普通解释器。

$ ./manage.py shell —plain

Python 2.7.1 (r271:86882M, Nov 30 2010, 09:39:13)

[GCC 4.0.1 (Apple Inc.build 5494)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

(InteractiveConsole)

>>>

需要说明的是,能否使用有扩展功能的shell与Python版本无关。仅仅是因为作者的机器上安装了用于Python 2.7版本的IPython,但没有安装用于Python 2.5的。

如果读者想安装扩展功能的 shell,需要用到前面介绍安装 Django 时用到的 easy_install或pip。下面是在Windows中安装IPython的方式。

C:\WINDOWS\system32>\python27\Scripts\easy_install ipython

Searching for ipython

Reading http://pypi.python.org/simple/ipython/

Reading http://ipython.scipy.org

Reading http://ipython.scipy.org/dist/0.10

Reading http://ipython.scipy.org/dist/0.9.1

Installing ipengine-script.py script to c:\python27\Scripts

Installing ipengine.exe script to c:\python27\Scripts

Installed c:\python27\lib\site-packages\ipython-0.10.1-py2.7.egg

Processing dependencies for ipython

Finished processing dependencies for ipython

11.7.2 测试数据模型

既然知道了如何启动Python shell,就启动IPython并输入一些Python或IPython命令来测试应用及其数据模型。

In [1]: from datetime import datetime

In [2]: from blog.models import BlogPost

In [3]: BlogPost.objects.all() # no objects saved yet!

Out[3]: []

In [4]: bp = BlogPost(title='test cmd-line entry', body='''

….: yo, my 1st blog post…

….: it's even multilined!''',

….: timestamp=datetime.now())

In [5]: bp

Out[5]: <BlogPost: BlogPost object>

In [6]: bp.save()

In [7]: BlogPost.objects.count()

Out[7]: 1

In [8]: exec _i3 # repeat cmd #3; should have 1 object now

Out[8]: [<BlogPost: BlogPost object>]

In [9]: bp = BlogPost.objects.all()[0]

In [10]: print bp.title

test cmd-line entry

In [11]: print bp.body # yes an extra \n in front, see above

yo, my 1st blog post…

it's even multilined!

In [12]: bp.timestamp.ctime()

Out[12]: 'Sat Dec 11 16:38:37 2010'

前几个命令导入了相应的对象。第3步查询数据库中的BlogPost对象,此时它为空。所以在第4步,通过实例化一个BlogPost对象来向数据库中添加第一个BlogPost对象,向其中传入对应属性的值(title、body和timestamp)。创建完对象后,需要通过BlogPost.save()方法将其写入到数据库中(第6步)。

完成了创建和写入后,可以使用第7步的BlogPost.objects.count()方法确认数据库中对象的个数从0变成了1。在第8步,使用了IPython的命令来重复第3步的命令,获得数据库中存储的所有BlogPost对象的列表。虽然可以重复输入BlogPost.objects.all(),但这里是仅演示扩展shell的强大之处。最后一步是在第9步获取含有所有BlogPost对象的列表中的第一个元素(也只有一个),获得其中刚刚存进去的值。

前面仅仅作为示例介绍了如何将交互式解释器与应用结合起来。shell的更多特性,可以参见http://docs.djangoproject.com/en/dev/intro/tutorial01/#playing-with-the-api。这些 Python shell是非常棒的开发者工具。除了Python自带的标准命令行工具之外,这些Django功能也可以与集成开发环境(IDE)一同工作,还可以通过第三方开发的交互式解释器,如 IPython 和bpython,获得更多的功能。

几乎所有用户和大部分开发者更愿意使用基于Web的创建、读取、更新、删除(CRUD)工具,对于每个开发出来的Web应用都是如此。但开发者真的希望为每个应用创建一个这样的Web管理控制台吗?看起来开发者只需要一个,Django管理应用可以用于这一点。

11.8 Django管理应用

自动后台管理应用,或简称 admin,被誉为 Django 皇冠上的明珠。对于那些厌烦了为Web 应用创建简单的 CRUD 接口的人来说,这是天赐之物。admin 是一个应用,每个 Web站点都需要它。为什么呢?因为需要确认应用能够创建、更新或删除新的记录。如果应用还没有完全完成,想进行这些操作就有点困难了。admin 应用解决了这个问题,其通过让开发者可以在完成完整的UI之前验证处理数据的代码。

11.8.1 设置admin

尽管Django自带这个admin应用,但依然需要在配置文件中明确启用这个应用。就如同之前启用blog应用一样。打开settings.py,再次滚动到最下方的INSTALLED_APPS元组。其中已经添加了“blog”,但可能会看到“blog”上面的四行。

INSTALLED_APPS = (

Uncomment the next line to enable the admin:

'django.contrib.admin',

Uncomment the next line to enable admin documentation:

'django.contrib.admindocs',

'blog',

)

这里关心的是第一个被注释掉的项,即“django.contrib.admin”。移除行首的井号(#)来启用这一选项。第二个是可选的Django管理文档生成器。Admindocs应用通过提取Python文档字符串(“docstring”)来为项目自动生成文档,并让 admin 访问。读者想启用它当然没问题,只是这个例子中不会用到。

每次向项目中添加新应用时,需要执行syncdb,来确保在数据库中创建所需的表。这里向INSTALLED_APPS中添加了admin应用,接着运行syncdb来在数据库中为其创建一个表。

$ ./manage.py syncdb

Creating tables …

Creating table django_admin_log

Installing custom SQL …

Installing indexes …

No fixtures found.

既然admin设置完成,所要做的就是给定一个URL,这样才能访问admin页面。在自动生成的项目urls.py中,可以在顶部发现如下内容。

Uncomment the next two lines to enable the admin:

from django.contrib import admin

admin.autodiscover()

而在底部有一个注释掉了这个二元组全局变urlpatters。

Uncomment the next line to enable the admin:

(r'^admin/', include(admin.site.urls)),

取消这三行的注释并保存文件。现在当用户访问 Web 站点的 http://localhost:8000/admin链接时,Django就能载入默认的admin页面,

最后,应用程序需要告知Django哪个模型需要在admin页面中显示并编辑。为了做到这一点,只须注册BlogPost。创建blog/admin.py,向其中添加下面的代码。

admin.py

from django.contrib import admin

from blog import models

admin.site.register(models.BlogPost)

前两行导入了admin和数据模型。紧接着用admin注册BlogPost类。这样admin就可以管理数据库中这种类型的对象(以及其他已经注册的对象)。

11.8.2 使用admin

既然通过admin注册了应用的模型,就试用一下。再次使用manage.py runserver命令,并访问与之前相同的链接(http://127.0.0.1:8000或http://localhost:8000)。看到了什么?其实是一个错误页面。具体来说,应该是一个404错误,如图11-5所示。

为什么会得到这个错误?因为还没有为“/”这个 URL定义动作。现在唯一启用的仅仅是/admin,所以需要直接访问这个 URL,即访问 http://127.0.0.1:8000/adminhttp://localhost:8000/admin,或直接在浏览器中现有的地址后面加上/admin。

models.py - 图7 图11-5 404错误

实际上,如果仔细查看错误页面,Django会通知你只有/admin可用,因为其尝试了所有链接。注意,那个“It Worked!”页面是一个特例,仅用于没有为应用设置任何URL的情形(如果没有处理这种特殊情形,同样会得到404错误)。

如果正常访问admin页面,可以看到如图11-6所示的非常友好的登录页面。

输入前面创建的超级用户的用户名和密码。登录后,可以看到如图11-7所示的admin主页。

其中有通过admin应用注册的所有类。由于admin允许操作位于数据库中的这些类,包括Users类,因此这意味着可以(通过友好的Web界面,而不是命令行或shell环境)添加其他内容或超级用户。

models.py - 图8 图11-6 admin登录页面

models.py - 图9 图11-7 admin主页

核心提示:为什么这里没有列出我的类

有时,列表中可能没有列出添加的类。有三种常见的原因会导致admin不会显示应用的数据。

1.忘记通过admin.site.register()注册模型类。

2.应用的models.py文件中存在错误。

3.忘记将应用添加到settings.py文件中的INSTALLED_APPS元组中。

现在,来看看admin的真正力量:处理数据的能力。如果单击Blog posts链接,会跳转到一个页面,其中列出数据库的所有BlogPost对象(见图11-8),目前只有一个前面在shell中输入的对象。

注意,图11-8中为这个BlogPost对象使用的是通用的“BlogPost object”标签。为什么这篇博文有这个拗口的名称?Django可以处理不同类型的内容,所以其不会猜测某篇文章最合适的标签,而是直接使用一个通用标签。

因为现在能确认这篇文章就是之前输入的数据,且不会与其他的 BlogPosts 对象弄混,所以无需这个对象的额外信息。单击这篇文章来进入编辑页面,如图11-9所示。

自由改变其中的内容,接着单击 Save 按钮,然后添加其他项,以此来验证可以从 Web表单中创建新的项(而不是从命令行中)。图11-10显示的表单与之前的编辑页面非常相似。

models.py - 图10 图11-8 唯一一个BlogPost对象

models.py - 图11 图11-9 命令行创建的BlogPost项的Web视图

models.py - 图12 图11-10 在保存了之前的文章后,现在可以添加新的文章

新的BlogPost怎么能没有内容呢?所以为文章设置标题和一些有趣的内容,如图11-11所示。对于时间戳,单击Today和Now链接来填充当前日期与时间。也可以单击日历和时钟图标,显示出一个易用的日期和时间选择器。完成博文正文的编写后,单击Save按钮。

models.py - 图13 图11-11 直接从admin中添加新的博文

文章保存到数据库中之后,就会弹出一个页面显示一条确认消息(The blog post“BlogPost object”was added successfully.),以及所有博文的列表,如图11-12所示。

注意,输出结果并没有改变。实际上,由于现在有了两个无法区分的BlogPost对象,情况还变得更糟了。现在所有博文都使用“BlogPost object”标签。不止一个读者会思考:“应该有更好的方法来让显示的结果更有用!”当然,Django可以做到。

在前面,使用了最精简的配置启用了admin工具,即使用admin应用本身注册了模型。但通过额外两行代码,并修改注册调用,可以更好且更有用地显示博文列表。更新blog/admin.py文件,使用新的BlogPostAdmin类,并将其添加到注册行中,如下所示。

admin.py

from django.contrib import admin

from blog import models

class BlogPostAdmin(admin.ModelAdmin):

list_display = ('title', 'timestamp')

admin.site.register(models.BlogPost, BlogPostAdmin)

models.py - 图14 图11-12 保存了新的BlogPost对象。现在有两篇无法区分的博文

注意,因为在这里定义了BlogPostAdmin,而没有在blog/models.py模块中作为属性来添加,所以没有注册 models.BlogPostAdmin。如果刷新 admin 页面查看 BlogPost 对象(见图11-13),现在会看到更有效的输出,这个列表根据添加到BlogPostAdmin类中新的list_display变量显示内容。

图11-13中的内容,很好地区分开了两篇文章。对于刚接触Django的开发者来说,可能会惊讶只改动三行代码就对结果产生了如此大的变化。

models.py - 图15 图11-13 改进后的结果

尝试单击Title或Timestamp一栏,可以看到对博文进行了排序。例如,单击Title栏,文章按照标题升序进行排列,第二次单击会按降序排列。还可以尝试按照时间排序。这些功能已经内置到admin中。无须像以前那样手动添加相关代码。

admin还有其他有用的功能,这些功能只需一两行代码就能启用,如搜索、自定义排序、过滤等。现在仅仅刚刚接触到admin的皮毛,但希望已经激发了读者对admin的兴趣。

11.9 创建博客的用户界面

前面完成的任务仅仅针对开发者。应用的用户既不会使用Django shell,也不会使用admin工具。所以现在需要构建面向用户的界面。从Django的角度来看,Web页面应该有以下几个经典组件。

一个模板,用于显示通过Python类字典对象传入的信息。

一个视图函数,用于执行针对请求的核心逻辑。视图会从数据库中获取信息,并格式化显示结果。

一个URL模式,将传入的请求映射到对应的视图中,同时也可以将参数传递给视图。

在理解这些内容时,可以先看Django是如何处理请求的。Django自底向上处理请求,它首先查找匹配的URL模式,接着调用对应的视图函数,最后将渲染好的数据通过模板展现给用户。

而这里将用稍微不同的顺序来构建应用。

1.因为需要一些可以观察的内容,所以先创建基本的模板。

2.设计一个简单的URL模式,让Django可以立刻访问应用。

3.开发出一个视图函数原型,然后在此基础上迭代开发。

使用这个顺序的主要原因是因为模板和URL模式不会发生较大的改变。而应用的核心是视图,所以先快速构建一个基本视图,在此基础上逐步完善。这非常符合测试驱动模型(TDD)的开发模式。

11.9.1 创建模板

Django的模板语言非常简单,所以直接介绍其示例代码。下面是一个简单的模板,用于显示一篇博文(基于BlogPost对象的属性)。

<h2>{{ post.title }}</h2>

<p>{{ post.timestamp }}</p>

<p>{{ post.body }}</p>

读者可能已经注意到,这只是一个HTML文件(尽管Django模板可用于任何形式的文本输出),外加一些由花括号({{ …}})括起来的标签。这些标签称为变量标签,花括号内用于显示对象的内容。在变量标签中,可以使用Python风格的点分割标识访问这些变量的属性。这些值可以是纯数据,也可以是可调用对象,如果是后者,会自动调用这些对象,而无须添加圆括号“()”来表示这是个函数或方法调用。

在变量标签中还可以使用特殊函数,它们称为过滤器。过滤器是函数,它能在标签中立即对变量进行处理。所要做的只是在变量的右边插入一个管道符号(“|”),接着跟上过滤器名称。例如,如果想获得BlogPost标题的首字母大写形式,则可以通过下面的形式调用title()过滤器。

<h2>{{ post.title|title }}</h2>

这意味着当模板遇到“test admin entry”这样的 post.title,最终的 HTML 输出会转成<h2>Test Admin Entry</h2>。

传递给模板的变量是特殊的Python 字典,称为上下文(context)。在前面的例子中,假设通过上下文传入的BlogPost对象名称为“post”。原来的三行分别用于获取BlogPost对象的title、body和timestamp字段。现在向模板添加一些有用的功能。如通过上下文传入所有的博文,这样就可以通过循环来显示所有文章。

<!— archive.html —>

{% for post in posts %}

<h2>{{ post.title }}</h2>

<p>{{ post.timestamp }}</p>

<p>{{ post.body }}</p>

<hr>

{% endfor %}

原先的那三行没有改动,只是通过一个循环将这三行包裹起来,遍历所有文章。为了做到这一点,需要引入Django模板语言的另一个结构:块标签。变量标签通过一对花括号分割,而块标签通过花括号和百分号来表示:{% …%}。它们用于向HTML模板中插入如循环或判断这样的逻辑。

将上面的 HTML 模板代码保存到一个简单的模板文件中,命名为 archive.html。放置在应用文件夹下新建的templates文件夹中。这样,模板文件的路径应该为mysite/blog/templates/archive.html。模板的名称可以任取(甚至可以命名为foo.html),但模板目录必须为templates。默认情况下,搜索模板时,Django会在每个安装的应用的子目录中搜索templates目录。

关于模板和标签的更多信息,请参考官方文档页面 http://docs.djangoproject.com/en/dev/ref/templates/api/#basics。

下一步是为创建视图函数做准备,执行视图才能看到全新的模板。在创建视图之前,先从用户的角度了解视图。

11.9.2 创建URL模式

本节将介绍用户浏览器中URL的路径名如何映射到应用的不同部分。当用户通过浏览器发出一个请求时,因特网会通过某种方式将主机名和IP地址映射起来,接着客户端在80或其他端口(Django开发服务器默认使用8000端口)与服务器的地址连接起来。

项目的URLconf

服务器通过WSGI的功能,最终会将请求传递给Django。接受请求的类型(GET、POST等)和路径(URL 中除了协议、主机、端口之外的内容)并传递到项目的 URLconf 文件(mysite/urls.py)。这些信息必须通过正则表达式正确匹配到对应的路径中。否则,服务器会返回404错误,就像11.8.2节中遇到的那样,因为没有为“/”定义处理程序。

此时需要直接在mysite/urls.py中创建URL模式,但这样会混淆项目和应用的URL。我们希望在其他地方也能使用blog应用,所以需要应用能负责自己的URL。这样符合代码重用、DRY、在一处调试相同的代码等准则。为了正确分离项目和应用的URL配置,需要通过两步来定义 URL 映射规则并创建两个 URLconf:一个是用于项目,一个用于应用。

第一步就像之前启用admin那样。在自动生成的mysite/urls.py中,注释掉的那两行就是要用到的。它在urlpatterns变量的起始处。

urlpatterns = patterns('',

Example:

(r'^mysite/', include('mysite.foo.urls')),

取消这里的注释,对名称进行相应的修改,让其指向应用的URLconf。

(r'^blog/', include('blog.urls')),

include()函数将动作推迟到其他URLconf(当然是应用的URLconf)中。在这个例子中,将以blog/开头的请求缓存起来,并传递给将要创建的mysite/blog/urls.py(关于include()的更多内容将在下面介绍)。

与之前设置admin应用一样,现在项目的URLconf应该如下所示。

mysite/urls.py

from django.conf.urls.defaults import *

from django.contrib import admin

admin.autodiscover()

urlpatterns = patterns('',

(r'^blog/', include('blog.urls')),

(r'^admin/', include(admin.site.urls)),

)

patterns()函数接受两个元组(URL 正则表达式、目标)。正则表达式很容易理解,但目标是什么?目标要么是 URL 需要的匹配其模式的视图函数,要么是 include()中另一个URLconf文件。

当使用include()时,会移除当前的URL路径头,路径中剩下的部分传递给下游URlconf中的patterns()函数。例如,当在客户端浏览器输入http://localhost:8000/blog/foo/bar这个URL时,项目的 URLconf 接收到的是 blog/foo/bar。其匹配“^blog”正则表达式,并找到一个include()函数(与视图函数相反),所以其将foo/bar传递给mysite/blog/urls.py中匹配的URL处理程序。

include()中的参数“blog.urls”就负责处理这个事情。另一个类似的情形是对于http://localhost:8000/admin/xxx/yyy/zzz,其中 xxx/yyy/zzz 会传递给 admin/site/urls.py,即inlcude(admin.site.urls)中指定的。现在如果读者细心,可能注意到在代码中有奇怪的地方,是否少了一些内容?仔细看看include()函数的调用。

读者是否注意到blog.urls用引号括起来,而admin.site.urls没有?这不是一个输入错误。patterns()和include()都接受字符串或对象。一般使用字符串,但有些开发者更愿意传递对象。所要记住的是,当传递对象时,要确保已经导入该对象。在前面的例子中,import django.contrib.admin完成了这个任务。

下一节将看到另一个使用示例。关于字符串与对象类型的更多内容参见这个文档页面:http://docs.djangoproject.com/en/dev/topics/http/urls/#passing-callable-objects-instead-of-strings。

应用的URLconf

通过include()包含blog.urls,让匹配blog应用的URL将剩余的部分传递到blog应用中处理。创建一个新文件mysite/blog/urls.py,添加下面的代码。

urls.py

from django.conf.urls.defaults import *

import blog.views

urlpatterns = patterns('',

(r'^$', blog.views.archive),

)

这与项目的URLconf非常相似。首先,提醒一下,请求URL的头部分(blog/)匹配到的是根URLconf,它已经被去除了。所以只须匹配到空字符串,即通过正则表达式^$处理。blog应用现在可以重用并无须担心它挂载到blog/还是news/,或者其他链接下面。唯一没介绍的是这里发送请求时用到的archive()视图函数。

与新的视图函数一起工作只须简单地向URLconf添加一行代码。换句话说,如果添加视图函数foo()和bar(),只须将urlpatterns修改成下面这样(仅仅是演示,不要真的把foo和bar添加到自己的文件中了)。

urlpatterns = patterns('',

(r'^$', blog.views.archive),

(r'foo/', blog.views.foo),

(r'bar/', blog.views.bar),

)

目前一切正常,如果继续在Django中开发,需要一次次回头修改这个文件,就会意识到这样违反DRY原则。读者是否注意到所有引用都是blog.views中的视图函数?这表示应该使用patterns()中的一个特性,即第一个参数,目前这个参数是个空字符串。

这个参数是视图的前缀,所以可以将blog.views移到这里,移除下面重复的内容,并修改import语句,使其不会出现NameError。修改后的URLconf应该如下所示。

from django.conf.urls.defaults import *

from blog.views import *

urlpatterns = patterns('blog.views',

(r'^$', archive),

(r'foo/', foo),

(r'bar/', bar),

)

根据import语句,这三个函数都应该位于mysite/blog/views.py中。从前面的介绍中可以知道,导入它了之后,就可以传递对象了,就如同之前例子中的archive、foo、bar那样。但能否更懒一点,直接不使用import语句呢?

前一节介绍过,除了对象之外,Django还支持在参数中使用字符串,所以可以省去那些导入语句。如果移除导入语句,并在视图名称两侧加上引号,一切正常。

from django.conf.urls.defaults import *

urlpatterns = patterns('blog.views',

(r'^$', 'archive'),

(r'foo/', 'foo'),

(r'bar/', 'bar'),

)

foo()和bar()在示例应用中不存在,但一般正式的项目在应用的URLconf有多个视图。这里仅仅介绍了基本的代码清理方式。关于整理 URLconf 文件的更多内容,可以参考 Django文档:http://docs.djangoproject.com/en/dev/intro/ tutorial03/#simplifying- the-urlconfs.

最后一部分是控制器,控制器会调用匹配URL路径的视图函数。

11.9.3 创建视图函数

本节将重点讨论视图函数,这是应用的核心功能。视图函数的开发过程比较长,所以首先为那些心急的读者展示如何快速开始,然后再介绍细节,揭示如何在实际中正确处理视图函数。

“Hello World”伪视图

想在开发早期创建完整的的视图之前就调试HTML模板和URLconf?完全能做到!生成一个假的BlogPost,立即渲染到模板中创建“Hello World”mysite/blog/views.py这个只含有6行代码的文件(有一个折行)。

views.py

from datetime import datetime

from django.shortcuts import render_to_response

from blog.models import BlogPost

def archive(request):

post = BlogPost(title='mocktitle', body='mockbody',

timestamp=datetime.now())

return render_to_response('archive.html', {'posts': [post]})

根据URLconf中的标识,知道这个视图需要由archive()调用。这个代码创建一篇假的博文,以只含有单个元素的博文列表的形式传递给模板(不要调用post.save(),猜猜为什么)。

后面将回到 render_to_response(),但读者可以想象一下,猜测其将模板(archive.html,位于mysite/blog/templates中)和上下文字典合并到一起,返回给用户生成的HTML。这个读者猜对了。

启动开发服务器(或真正的Web服务器)。处理URLconf或模板中可能的错误,当它能正常工作后,能看到类似图11-14中的内容。

models.py - 图16 图11-14 伪视图的输出结果

使用伪视图和半伪造的数据能快速验证应用的基本设置是否正确。这种迭代过程非常快速,表明现在可以安全地开始真正的工作。

真实的视图

现在将会创建一些真的东西,一个简单的视图函数会从数据库中获取所有博文,并使用模板显示给用户。首先,将用正常的方式来完成该任务,意味着将遵循下面的步骤,从获取数据到向客户端返回HTTP响应。

向数据库查询所有博客条目。

载入模板文件。

为模板创建上下文字典。

将上下文传递给模板。

将模板渲染到HTML中 。

通过HTTP响应返回HTML 。

打开 blog/views.py,输入下面的代码。这段代码会执行与前面相同的内容,它仅仅替换了假的views.py文件中的内容。

views.py

from django.http import HttpResponse

from django.template import loader, Context

from blog.models import BlogPost

def archive(request):

posts = BlogPost.objects.all()

t = loader.get_template("archive.html")

c = Context({'posts': posts})

return HttpResponse(t.render(c))

检查开发(或真实的Web)服务器,在浏览器中访问应用。可以看到一个简单的、渲染的博文(使用的是真实的数据)列表。其中含有标题、时间戳、文章正文,用水平线分割(<hr>),与图11-15类似(这里只有两篇文章)。

很好!但记住“不要自我重复”这个传统,Django的开发者也知道这条极其常见的模式(获取数据,渲染到模板,返回响应),所以他们为从简单的视图函数渲染到模板创建了快捷方式。这就需要再次接触到render_to_response()。

models.py - 图17 图11-15 用户看到的博文

前面在伪视图中见过render_to_response(),但现在处理的是真的视图。从django.shortcuts导入render_to_response(),并移除现在不需要的loader、Context、HttpResponse,替换掉视图中最后三行。现在它应该如下所示。

views.py

from django.shortcuts import render_to_response

from blog.models import BlogPost

def archive(request):

posts = BlogPost.objects.all()

return render_to_response('archive.html', {'posts': posts})

如果刷新浏览器,不会有任何改变,因为仅仅是缩短了代码,而功能没有发生任何改变。关于使用render_to_response()的更多内容,可以查看下面的官方文档。

http://docs.djangoproject.com/en/dev/intro/tutorial03/#a-shortcut-render-to-response

http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#render-to-response

快捷方式仅仅是一个开始。还有其他类型的视图函数,称为通用视图,它比 render_to_response()更易用。通过通用视图,甚至无须编写视图函数,直接在 URLconf 中映射 Django提供的现成的通用视图。通用视图的主要目标之一就是避免编写任何代码!

11.10 改进输出

就是这样,现在完成了三个步骤,得到了一个可以工作的应用,有了面向用户的界面(不依赖Admin中对数据的CRUD操作)。有了可以工作的简单博客,它可以响应客户端的请求,从数据库中提取信息,向用户显示所有博文。很好,但依然可以做一些有用的改进,来实现一些实际的功能。

其中一个合理的方向是按时间逆序显示博文,即将最新的博文显示在最前面。另一个是限制显示的数目。如果有超过10篇(哪怕是5篇)文章显示在同一页,用户就会觉得太长了。首先,来看看按时间逆序排列。

很容易让Django做到这一点。实际上,有多种方式可以做到这一点。可以向模型添加默认的排序方式,也可以向视图代码添加查询方式。这里先使用后者,因为后者更容易解释。

11.10.1 修改查询

先回想一下前面的内容,BlogPost是数据模型类。Objects属性是模型的Manager类,其中含有all()方法来获取QuerySet。可以认为QuerySet是数据库中的每行数据。但其实QuerySet不是真实的每一行数据,因为QuerySet执行“惰性迭代”。

只有在求值时才会真正查询数据库。换句话说,QuerySet可以进行任何操作,但并没有接触到数据。若想了解 QuerySet 在什么时候求值,可以查看官方文档:http://docs.djangoproject.com/en/dev/ref/models/querysets/。

结束了背景知识的介绍。现在可以简单地告诉读者,只须在调用 order_by()方法时提供一个排序参数即可。在这里,需要新的博文排在前面,这意味着是根据时间戳逆序排列。只须将查询语句改成以下形式。

posts = BlogPost.objects.all().order_by('-timestamp')

在timestamp前面加上减号(-),就可以指定按时间逆序排列。对于正常的升序排列,只需移除减号。

为了测试能获取最前面的10 篇博文,需要数据库中有更多的文章。所以这里在Django shell中直接执行几行代码(使用普通的shell,没有使用IPython或bpython),在数据库中自动生成一堆记录。

$ ./manage.py shell —plain

Python 2.7.1 (r271:86882M, Nov 30 2010, 09:39:13)

[GCC 4.0.1 (Apple Inc.build 5494)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

(InteractiveConsole)

>>> from datetime import datetime as dt

>>> from blog.models import BlogPost

>>> for i in range(10):

…   bp = BlogPost(title='post #%d' % i,

…     body='body of post #%d' % i, timestamp=dt.now())

…   bp.save()

图11-16显示了刷新后浏览器中的改动。

shell还可用于测试刚刚做的改动,以及进行新的查询。

>>> posts = BlogPost.objects.all().order_by('-timestamp')

>>> for p in posts:

…   print p.timestamp.ctime(), p.title

Fri Dec 17 15:59:37 2010 post #9

Fri Dec 17 15:59:37 2010 post #8

Fri Dec 17 15:59:37 2010 post #7

Fri Dec 17 15:59:37 2010 post #6

Fri Dec 17 15:59:37 2010 post #5

Fri Dec 17 15:59:37 2010 post #4

Fri Dec 17 15:59:37 2010 post #3

Fri Dec 17 15:59:37 2010 post #2

Fri Dec 17 15:59:37 2010 post #1

Fri Dec 17 15:59:37 2010 post #0

Mon Dec 13 00:13:01 2010 test admin entry

Sat Dec 11 16:38:37 2010 test cmd-line entry

models.py - 图18 图11-16 原先的两篇文章外加10篇刚添加的文章

这样在某种程度上可以确认在向视图函数添加新的内容时,这些内容可以立即使用。

另外,可以用Python友好的切片语法([:10])来只显示10篇文章,所以把这个功能也添加上。对blog/views.py文件应用这些更改,结果如下所示。

views.py

from django.shortcuts import render_to_response

from blog.models import BlogPost

def archive(request):

posts = BlogPost.objects.all().order_by('-timestamp')[:10]

return render_to_response('archive.html', {'posts': posts})

保存文件,再次刷新浏览器。应该可以看到两处改动,一是博客文章按照时间逆序排列,二是总共有12篇文章,但只显示最新的10篇,最初的两篇文章已经看不到了,如图11-17所示。

改变查询方式非常直接,但对于这个特定的情况,在模型中设置默认的顺序是更加符合逻辑的,因为按照时间顺序显示最新的N篇文章是博客中唯一符合逻辑的排序方式。

models.py - 图19 图11-17 只显示了最新的10篇博客文章

设置模型的默认排序方式

如果在模型中设置首选的排序方式,其他基于Django的应用或访问这个数据的项目也会使用这个顺序。为了给模型设置默认顺序,需要创建一个名为 Meta 的内部类,在其中设置一个名为ordering的属性。

class Meta:

ordering = ('-timestamp',)

最有效的方式是将order_by('-timestamp')从查询移动到模型中。对两个文件都做修改,最后结果应该如下所示。

models.py

from django.db import models

class BlogPost(models.Model):

title = models.CharField(max_length=150)

body = models.TextField()

timestamp = models.DateTimeField()

class Meta:

ordering = ('-timestamp',)

views.py

from django.shortcuts import render_to_response

from blog.models import BlogPost

def archive(request):

posts = BlogPost.objects.all()[:10]

return render_to_response('archive.html', {'posts': posts})

核心提示(黑客园地):将archive()精简为单行较长的Python代码

使用lambda可以将archive()压缩成单行。

archive = lambda req: render_to_response('archive.html',

{'posts': BlogPost.objects.all()[:10]})

Python 风格代码的标志之一就是可读性。而富有表现力的语言,如 Python,就是为了在降低代码量的同时保留可读性。尽管在这里降低了代码行数,但也降低了可读性。因此,将其放在黑客园地(Hacker’s Corner)中。

另一处与原先不同的地方是,request变量精简成了req,同时由于没有posts变量,从而节省了一丁点内存。如果读者刚接触Python,建议阅读Core Python Programming或Core Python Language Fundamentals一书的“函数”章节,其中介绍了lambda。

此时刷新浏览器,会发现没有任何改动,这也是计划内的。现在花点时间来改进从数据库中提取数据的过程,建议减少与数据库的交互。

11.11 处理用户输入

现在应用已经完成了。可以通过shell或admin来添加博文。可以通过面向用户的演示功能查看数据。那么真的已经完成了吗?还早呢!

也许读者满足于通过shell或更友好的admin来创建对象,但用户可能不了解Python shell,更加不知道如何使用它,而且真的会让用户访问项目的admin应用吗?不可能!

如果读者深入理解了第10章的内容,以及本章目前学到的内容。可能会聪明地意识到这同样是一个三步流程。

添加一个HTML表单,让用户可以输入数据。

插入(URL,视图)这样的URLconf项。

创建视图来处理用户输入。

这里将逐步介绍这三步,就如同之前的一样。

11.11.1 模板:添加HTML表单

第一步很简单:为用户创建表单。为了简化开发流程,现在只将下面的HTML代码添加到blog/templates/archive.html的顶部(在BlogPost对象之前),后面会将其分离到其他文件中。

<!— archive.html —>

<form action="/blog/create/" method="post">

Title:

<input type=text name=title><br>

Body:

<textarea name=body rows=3 cols=60></textarea><br>

<input type=submit>

</form>

<hr>

{% for post in posts %}

在开发时将这些内容放置在同一个模板中是因为可以在同一个页面上显示用户的输入和博文。换句话说,无须在不同的表单项页面和BlogPost列表显示之间来回切换。

11.11.2 添加URLconf项

下一步是添加URLconf项。使用前面的HTML,需要用到/blog/create/的路径,所以需要将其关联到一个视图函数中,该函数用于把内容保存到数据库中。将这个函数命名为create_blogpost(),向应用URLconf的urlpatterns变量中添加一个二元组,如下所示。

urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('blog.views',

(r'^$', 'archive'),

(r'^create/', 'create_blogpost'),

)

剩下的任务是添加create_blogpost()的代码。

11.11.3 视图:处理用户输入

在Django中处理Web表单,与第10章中看到的处理通用网关接口(CGI)变量非常相似。只须在 Django 中完成等价的步骤即可。其实阅读 Django 的文档就能明白应该向blog/views.py添加哪些内容。首先需要新的导入语句,如下所示。

from datetime import datetime

from django.http import HttpResponseRedirect

实际的视图函数如下所示。

def create_blogpost(request):

if request.method == 'POST':

BlogPost(

title=request.POST.get('title'),

body=request.POST.get('body'),

timestamp=datetime.now(),

).save()

return HttpResponseRedirect('/blog/')

与archive()函数相似,请求会自动传入。因为表单的输入通过POST传入,所以需要检查POST请求。接下来,创建新的 BlogPost 项,其中含有表单数据并用当前的时间作为时间戳。最后保存到数据库中。此时重定向回/blog,查看最新的博文(顶部也会显示用于下一篇博文的空白表单)。

同样,再次检查开发或实际的Web服务器,访问应用的页面。会发现博客数据的上方会显示表单(见图11-18),可以测试驱动新的特性。

models.py - 图20 图11-18 第一个用户表单(后面跟有已有的博文)

11.11.4 跨站点请求伪造

如果能够调试应用,获得一个表单并提交,会看到浏览器尝试访问/blog/create/这个URL,但会出现图11-19中显示的错误并停止。(Django有数据保留特性。这不允许不安全的POST通过跨站点请求伪造(Cross-Site Request Forgery,CSRF)来进行攻击,对CSRF的解释超出了本书的范畴,但读者可以通过下面的链接进行了解。

http://docs.djangoproject.com/en/dev/intro/tutorial04/#write-a-simple-form

http://docs.djangoproject.com/en/dev/ref/contrib/csrf/

对于这个简单的应用,需要修改两个地方,这两处都需要向已有的代码中添加一些代码。

1.向表单中添加CSRF标记({% csrf_token %}),让这些POST回到对应的页面。

2.通过模板发送向这些标记请求的上下文实例。

models.py - 图21 图11-19 CSRF错误页面

请求上下文实际上是一个字典,它含有关于请求的信息。如果阅读上面提供的CSRF文档页面,会发现django.template.RequestContext总是通过含有内置的CSRF保护进行处理。

通过向表单添加标记已经完成了第一步。编辑 mysite/blog/templates/archive.html 中的<FORM>标题行,在表单中添加CSRF标记,如下所示。

<form action="/blog/create/" method=post>{% csrf_token %}

第二部分涉及编辑 mysite/blog/views.py。修改 archive()视图函数的 return 一行,添加RequestContext实例,如下所示。

return render_to_response('archive.html', {'posts': posts,},

RequestContext(request))

不要忘记导入django.template.RequestContext。

from django.template import RequestContext

保存这些更改后,就可以从表单(而不是admin或shell)中向应用提交数据。提交新的BlogPost的过程中不会再出现CSRF错误。

11.12 表单和模型表单

在前一节中,通过显示创建HTML表单的步骤演示了如何处理用户的输入。现在将展示Django 如何简化接受用户数据(Django 表单)的工作,特别是含有组成数据模型的表单(Django模型表单)。

11.12.1 Django表单简介

除了需要处理CSRF这样的一次性工作之外,前面集成进简单输入表单的三步工作看起来就太费力且重复了。毕竟,这是用的是严格遵循DRY原则的Django。

应用中最有可能重复的部分是数据模型嵌入到其他地方的时候。在表单中可以见到这样的名称和标题。

Title: <input type=text name=title><br>

Body: <textarea name=body rows=3 cols=60></textarea><br>

在create_blogpost()视图中,可以见到同样的内容。

BlogPost(

title=request.POST.get('title'),

body=request.POST.get('body'),

timestamp=datetime.now(),

).save()

关键是在定义数据模型后,应该只有一个地方能见到title、body,以及timestamp(尽管最后一个有点特殊,因为不要求用户输入这个值)。单独基于数据模型,直接希望Web框架带有表单的字段不是很简单吗?为什么开发者必须要向数据模型添加这些额外内容?Django表单就用上派场了。

首先,为输入数据创建一个Django表单。

from django import forms

class BlogPostForm(forms.Form):

title = forms.CharField(max_length=150)

body = forms.CharField(widget=forms.Textarea)

timestamp = forms.DateTimeField()

这样还没有完全完成。在HTML表单中,指定了HTML文本区域元素含有三行,60个字符宽。由于这里通过编写代码自动生成HTML代码替换原始的HTML代码,因此需要找到一种方法来指定这些需求。在这里,方法是直接传递这些属性,如下所示。

body = forms.CharField(

widget=forms.Textarea(attrs={'rows':3, 'cols':60})

)

11.12.2 模型表单示例

除了指定属性这个小变化之外,读者是否仔细查看了BlogPostForm的定义?其中是不是也含有重复的内容?从下面可以看出,它与这个属性模型非常相似。

class BlogPost(models.Model):

title = models.CharField(max_length=150)

body = models.TextField()

timestamp = models.DateTimeField()

你是对的,它们看起来就像是双胞胎一样。对于任何Django脚本来说,这样的重复显得有点太多了。前面创建独立的Form对象没什么问题,因为这是为Web页面从零创建表单,没有使用数据模型。

但如果表单字段完全匹配一个数据模型,则一个Form就不是想要的,可以通过Django ModelForm更好地完成任务,如下所示。

class BlogPostForm(forms.ModelForm):

class Meta:

model = BlogPost

好多了,这是想要的最懒的方法。通过从Form换到ModelForm,可以定义一个Meta类,它表示这个表单基于哪个数据模型。当生成HTML表单时,会含有对应数据模型中的所有属性字段。

在这个例子中,不信赖用户输入正确的时间戳,而是想让应用为每篇博文以可编程的方式添加这个内容。这一点不难,只须添加额外一个名为exclude的属性,从生成的HTML中移除这个表单项。向blog/models.py文件中添加相应的导入语句,并在该文件的底部,BlogPost定义的后面添加完整的BlogPostForm类。

blog/models.py

from django.db import models

from django import forms

class BlogPost(models.Model):

class BlogPostForm(forms.ModelForm):

class Meta:

model = BlogPost

exclude = ('timestamp',)

11.12.3 使用ModelForm来生成HTML表单

这个有什么用?其实,现在只能从表单中取出这些字段。所以将 mysite/blog/templates/archive.html顶部的代码修改为下面这样。

<form action="/blog/create/" method=post>{% csrf_token %}

<table>{{ form }}</table><br>

<input type=submit>

</form>

这里需要保留提交按钮。同时表单内部还含有一个表格。不信,只须在Django shell中创建一个BlogPostForm实例,进行一些处理,如下所示。

>>> from blog.models import BlogPostForm

>>> form = BlogPostForm()

>>> form

<blog.models.BlogPostForm object at 0x12d32d0>

>>> str(form)

'<tr><th><label for="id_title">Title:</label></th><td><input

id="id_title" type="text" name="title" maxlength="150" /></td></

tr>\n<tr><th><label for="id_body">Body:</label></th><td><textarea

id="id_body" rows="10" cols="40" name="body"></textarea></td></tr>'

开发者无须编写这些HTML(同样,exclude字段去除了表单中的timestamp。为了测试,可以暂时注释掉前面的exclude字段,这样生成的HTML中也会有额外的timestamp字段)。

如果不想用 HTML 表格的行和列的形式输入,也可以通过 as_*()方法输出。如{{ form.as_p }}会以<p>…</p>分割文本,{{ form.as_ul }}会以<li>列表元素显示,等等。

URLconf保持不变,所以最后一个所需的改动是更新视图函数,将ModelForm发送至模板。为了做到这一点,需要实例化它并作为上下文字典中额外的键值对传递它。所以改变blog/views.py中archive()的最后一行,如下所示。

return render_to_response('archive.html', {'posts': posts,

'form': BlogPostForm()}, RequestContext(request))

不要忘记在views.py的起始处添加对数据和表单模型的导入。

from blog.models import BlogPost, BlogPostForm

11.12.4 处理ModelForm数据

刚刚做的改动创建了ModelForm,并让其生成向用户展示的HTML。那么用户提交相关信息后会发生什么?在blog/views.py中的create_blogpost()视图中依然会看到重复的内容。在BlogPostForm中,定义Meta类来让其获取ModelForm中的字段。这里的处理方法与之类似,无须像下面这样在create_blogpost()中创建对象。

def create_blogpost(request):

if request.method == 'POST':

BlogPost(

title=request.POST.get('title'),

body=request.POST.get('body'),

timestamp=datetime.now(),

).save()

return HttpResponseRedirect('/blog/')

无须提及title、body等,因为这些已经在数据模型中了。可以用下面这种更简短的方式。

def create_blogpost(request):

if request.method == 'POST':

form = BlogPostForm(request.POST)

if form.is_valid():

form.save()

return HttpResponseRedirect('/blog/')

遗憾的是,因为有timestamp,所以不能这样做。必须对前面的HTML表单生成过程进行特殊处理。所以需要像下面这样,使用if语句。

if form.is_valid():

post = form.save(commit=False)

post.timestamp=datetime.now()

post.save()

读者可以看到,必须向数据添加时间戳,然后手动保存对象来获得想要的结果。注意,这是表单的save(),不是模型的save(),这个save()返回Blog模型的实例,但由于commit=False,因此在调用post.save()之前不会保存数据。这些改动生效后就可以正常使用表单,如图11-20所示。

models.py - 图22 图11-20 自动生成的用户表单

11.13 视图进阶

最后讨论其他Django书籍不会介绍的重要内容,即通用视图。目前为止,当应用需要控制器或逻辑时,会创建自定义视图。但 Django 严格遵循 DRY 原则,因此会使用类似render_to_response()这样的快捷方式。

通用视图非常强大,也很抽象,使用通用视图后,就无须编写任何视图。只须将其连接到URLconf中,传递一些所需的数据,甚至无须在views.py中编辑/创建任何代码。只须给予读者足够的背景知识就可以让读者使用通用视图了。首先回到前面关于CSRF的简短介绍中,但不会真正讨论它。这意味着什么呢?

11.13.1 半通用视图

由于应用必须警惕发送过来的 CSRF,因此前面发送请求上下文的实例非常啰嗦。同时它对初学者也很不人性化。因此这里开始接触通用视图,而不必实际上这样使用它。首先修改自定义视图,使用通用视图来完成主要工作。这种方式称为半通用视图。

使用编辑器打开mysite/blog/views.py,用下面的代码替换掉archive()中的最后一行。

return render_to_response('archive.html', {'posts': posts,

'form': BlogPostForm()}, RequestContext(request))

添加下面新的导入语句,移除的render_to_response()导入语句。

from django.views.generic.simple import direct_to_template

修改最后一行。

return direct_to_template(request, 'archive.html',

{'posts': posts, 'form': BlogPostForm()})

等一下,这些是干什么的? Django 通过减少需要编写的代码量来简化工作,但这里仅删除了请求上下文的实例。还有其他用途吗?目前没有。这里只是为后续做准备。因为这个例子中目前没真正使用direct_to_template()作为通用视图,并且由于使用的是自定义视图,所以将其转换成半通用视图。

另外,纯通用视图意味着需要从URL-conf中直接调用它,无需view.py中的任何代码。通用视图是一些经常会重用的基本功能,但依然不希望每次需要相同功能的时候就重新创建一个。例如,将用户重定向到静态页面、为对象提供泛型输出等。

真正使用通用视图

尽管在前面的小节中部署了通用视图函数,但没有将其真正作为纯通用视图使用。现在真正地使用通用视图。找到项目的 URLconf(mysite/urls.py)。还记得 11.8.2 节中访问http://localhost:8000/时出现的404错误吗?

在那里解释了Django只能处理匹配正则表达式的路径。“/”无法匹 配“/blog”和“/admin/”,所以强迫用户在应用中仅访问这些链接。这样无法让用户方便地访问顶层的“/”路径,也无法让应用自动重定向到“/blog”。

这时就能完美地体现出redirect_to()通用视图的用途。所要做的就是向urlpatterns添加一行代码,如下所示。

urlpatterns = patterns('',

(r'^$', 'django.views.generic.simple.redirect_to',

{'url': '/blog/'}),

(r'^blog/', include('blog.urls')),

(r'^admin/', include(admin.site.urls)),

)

其实是两行,但这是一行语句。同时,因为这里使用的是字符串,而不是对象,所以无需其他导入语句。现在当用户访问“/”时,会重定向到“/blog/”,这与目标一致。无须修改view.py,所要做的仅仅是在项目或应用的URLconf文件中调用这个。这就是通用视图(如果读者想了解更多的内容,本章末尾会有更复杂的通用视图的练习)。

目前为止,已经介绍了 directto_template()和 redirect_to()通用视图,但还有其他会经常用到的通用视图。其中包括object_list()和object_detail(),同时还有面向时间的通用视图,如archive{day, week, month, year, today, index}()。最后,还有用于CRUD的通用视图,如{create, update, delete}_object。

最后要提醒的是,在Django 1.3中引入了基于类的通用视图,这也是将来的趋势。通用视图非常强大,将其转换成基于类的通用视图会使其更加强大(这就如同从Python 1.5中将基于普通字符串的异常转变成基于类的异常)。

关于通用视图和基于类的通用视图的更多信息,可以参见http://docs.django-project.com/en/dev/topics/generic-views/和http://docs.djangoproject.com/en/ dev/topics/class-based-views中的官方文档。

剩下的章节不是很重要,但仍然含有一些非常有用的信息,可以稍后阅读。如果想继续学习其他内容,可以跳过这些中级的Django应用,直接阅读第12章。

11.14 *改善外观

从这里开始,通过以下措施改善应用的工作方式并让站点拥有更一致的外观。

1.创建CSS文件。

2.创建基模板,并使用模板继承。

CSS很简单,所以这里不会介绍,但了解一下模板继承的简短例子。

<!— base.html —>

Generic welcome to your web page [Login - Help - FAQ]

<h1>Blog Central</h1>

{% block content %}

{% endblock %}

&copy; 2011 your company [About - Contact]

</body>

</html>

这样并不很高大上,但的确能达到目的。这段代码将常见的标题资源,如公司logo、签入/签出,以及其他链接等,放在页面顶部。而在底部,会有如版权信息、其他链接等。但这里重要的是代码中间部分的{% block …%}标签。这个标签定义了一个由子模板控制的命名区域。

为了使用这个新的基模板,必须对其进行扩展并定义由这个基模板控制的区域。例如,如果想让面向用户的blog应用页面使用该模板,只须添加相应的样板文件就可以了。为了避免与archive.html混淆,一般将其命名为index.html。

<!— index.html —>

{% extends "base.html" %}

{% block content %}

{% for post in posts %}

<h2>{{ post.title }}</h2>

<p>{{ post.timestamp }}</p>

<p>{{ post.body }}</p>

<hr>

{% endfor %}

{% endblock %}

其中,{% extends …%}标签让Django查找名为base.html的模板,并将该模板中的任何命名区域插入base.html中对应的区域中。如果需要使用模板继承,要确保视图用index.html作为模板文件,而不是原先的archive.html。

11.15 *单元测试

测试是所有开发者必须要做的内容。开发者需要吃饭、睡觉,还要为自己开发的程序编写测试。如同编程的许多方面,Django 通过扩展 Python 自带的单元测试模块来提供测试功能。Django还可以测试文档字符串(即docstring),这称为文档测试(doctest),可以 在Django文档页面的测试部分了解相关内容,所以这里就没有介绍。相比而言,单元测试更重要。

创建单元测试比较简单。在创建应用时,Django通过自动生成test.py文件来促使开发者创建测试。用示例11-1中的代码替换掉mysite/blog/tests.py。

示例11-1 blog应用的单元测试模块(tests.py)

models.py - 图23

models.py - 图24

逐行解释

第1~5行

首先导入用于博客时间戳的datetime,接着是主要的测试类django.test.TestCase,然后是测试Web客户端django.test.client.Client,最后是BlogPost类。

第8~13行

测试方法必须以“test_”开头,方法名的后面部分可以随意取。这里的test_obj_create()方法仅仅通过测试确保对象成功创建,并验证标题的内容。assertEqual()方法中,如果两个参数相等则测试成功,否则该测试失败。这里通过assertEqual()验证对象的数目(count)和标题(title)。这是非常基本的测试,可以在此基础上进行更复杂的测试。读者也许还想测试ModelForm。

第15~21行

接下来的两个测试方法检测用户界面。这两个方法生成Web调用,与test_obj_create()仅仅测试对象的创建有所不同。test_home()方法在“/blog/”中调用应用的主页面,确保收到的是 200 这个 HTTP“错误”。test_slash()方法实际上与之相同,但确认 URLconf 使用 redirect_to()通用视图来完成重定向工作。这里的断言稍微有所不同,因为期望的重定向响应编码是301或302。这里更期望的是301,但就如同assertIn()测试方法所做的一样,如果是302也不要报错。在最后两个测试方法中重用了这个断言,后面这两个测试方法都应返回302响应。在第16行和第20行,读者可能会奇怪这里的self.client是从哪里来的。如果子类化django.test.TestCase,会自动免费获得一个Django测试客户端的实例,并直接使用self.client来引用它。

第23~35行

最后两个方法都测试为“/blog/create/”生成的视图 create_blogpost()。第一个方法是test_empty_create(),测试某人在没有任何数据就错误地生成GET请求这样的情形。代码应该忽略掉这个请求,然后重定向到“/blog”。第二个方法 test_post_create()模拟真实用户请求通过POST发送真实数据,创建博客项,然后将用户重定向到“/blog”。这里对三个内容进行断言:302重定向、添加新博客文章,以及数据验证。

现在尝试执行下面的命令,并观察输出内容。

$ manage.py test Creating test database 'default'...---------------------------------------------------------------------Ran 288 tests in 7.061s OK Destroying test database 'default'...

默认情况下,系统会创建独立的内存数据库(称为default)来进行测试,所以无须担心测试会损坏实际生产数据。其中的每个点表示通过了一个测试。对于未通过的测试,“E”表示错误,“F”表示失败。关于 Django 测试的更多信息,请参考 http://docs.djangoproject.com/en/dev/topics/testing中的官方文档。

11.15.1 blog应用的代码审查

现在同时查看应用最终版本的代码(以及空的init.py和示例11-1的tests.py)。这里不包含代码中的注释,但可以从本书的配套网站上下载简化版或完整版的代码。

首先查看示例11-2中 mysite/urls.py,尽管这个项目级别的URLconf 文件并不是博客应用正式的组成部分。

示例11-2 mysite项目URLconf(urls.py)

models.py - 图26

逐行解释

第1~4行

这里为项目URLconf导入相关的内容,以及一些启用admin的代码。并不是所有的应用都会部署admin,所以第2行和第3行在用不到的时候可以省略。

第6~11行

urlpatterns指定了通用视图或项目中应用的行为和指令。第一个模式针对“/”,使用通用视图redirect_to()将其重定向到“/blog/”的处理程序。第二个模式针对“/blog/”,将所有请求发送到博客应用的URLconf(后面会介绍)。最后一个是针对admin的请求。

下一个要看的文件是示例11-3的mysite/blog/urls.py,这是应用的URLconf。

示例11-3 blog应用的URLconf(urls.py)

这是blog应用的URLconf文件,用来处理URL,调用相应的视图函数或class方法。

models.py - 图27

逐行解释

第4~7行

urls.py 的核心是定义了 URL 映射(即 urlpatterns)。当用户访问“/blog/”时,将由blog.views.archive()处理它们。记住,“/blog”已经由项目的URLconf裁剪过了,所以这里的URL路径只是“/”。只有来自表单及其数据的POST请求才会调用“/blog/create/”,这个请求由blog.views.create_blogpost()视图函数处理。

在示例11-4中,将介绍blog应用的数据模型,即mysite/blog/models.py。其中还含有表单类。

示例11-4 blog应用的数据和表单模型文件(models.py)

这里含有数据模型,但后面的部分可以分离到单独的文件中。

models.py - 图28

逐行解释

第1~3行

导入定义模型和表单所需的类。这里由于应用比较简单,所以用一个文件包含了所有的类。如果有更多的模型和1或表单,则需要将表单分离到单独的forms.py文件中。

第5~10行

这里定义BlogPost模型。它包含其数据属性,以及每行的时间戳字段,所有数据库查询根据这个字段让对象按时间逆序排列(通过Meta这个内部类完成)。

第12~15行

这里创建了BlogPostForm对象,即数据模型的表单版本。Model.model属性指定了它所要基于的数据模型,Meta.exclude 变量表示其中的数据字段需要从自动生成的表单中排除。其期望开发者在将BlogPost实例存入数据库之前填充这个字段(根据需要)。

示例11-5中的mysite/blog/admin.py文件只在应用启用了admin时才会用到。该文件包含在admin用到的注册类,以及任何特定的admin类。

示例11-5 blog应用的Admin配置文件(admin.py)

models.py - 图29

逐行解释

第5~8行

这里仅用于Django admin,其中BlogPostAdmin类的list_display属性用于告知admin在admin控制台中显示那个字段,帮助用户区分不同的数据记录。还有其他属性,这里就不一一介绍了。但建议读者阅读文档:http://docs.djangoproject.com/ en/dev/ref/contrib/admin/#modeladmin-options。没有list_display标识,则只会看到每行的泛型对象名称,因此几乎无法区分不同的实例。最后要做的是(在第8行)注册admin应用的数据和admin模型。

示例11-6显示了应用的核心,代码位于mysite/blog/views.py中。这里就是所有视图所在的位置,它等同于大部分Web应用的控制器的代码。具有讽刺意味的是,Django遵循DRY原则,强大的通用视图就是为了不再需要用到views.py(但有人会觉得这样隐藏了太多的东西,让源码更难阅读和理解)。读者在这个文件中创建的任何自定义或半通用视图代码需要尽量短小、易读,且可重用。换句话说,尽可能符合Python风格。创建良好的测试和文档就不用说了。

示例11-6 blog的视图文件(views.py)

应用的所有逻辑都位于views.py文件中,通过URLconf调用其中的组件。

models.py - 图30

逐行解释

第1~5行

这里有许多导入语句,所以是时候分享另一种好习惯了,即根据与应用的相似程度组织导入语句。也就是说,首先访问所有标准库模块(这里是 datetime)和包。第二部分是所依赖的框架模块和包(django.*)。最后是应用自己的导入语句(blog.models)。按照这种顺序组织导入语句会避免大多数很明显的依赖问题。

第7~11行

blog.views.archive()函数是应用的主要视图。该函数从数据库中提取出最新的 10 个BlogPost对象,打包这些数据并为用户创建一个输入表单。它接着将数据和表单作为上下文传递给archive.html模板。快捷函数render_to_response()被direct_to_template()通用视图替换(在处理时将archive()转成半通用视图)。

在最初,render_to_response()不仅将模板名称和上下文作为参数,还接收CSTF验证所需的RequestContext对象,将最终响应返回给客户端。当使用direct_to_template()后,无须传递请求上下文实例,因为这些内容已经推到通用视图中处理,这里只须处理应用的核心内容,也就是针对原来快捷方式的快捷方式。

第12~19行

因为URLconf将所有的POST请求关联到这个视图中,所以blog.views.create_blogpost()函数与 template/archive.html 中的表单行为关联。如果该请求确实是 POST 请求,则创建BlogPostForm 对象,提取用户填充的表单字段。当在第 15 行成功验证后,在第 16 行调用form.save()方法返回创建的BlogPost的实例。

前面提到过,commit=False标记让save()不要将实例存储到数据库中(因为还需要填充时间戳字段)。这需要显式调用实例的post.save()方法来实际存储它。如果is_valid()返回False,则跳过存储数据。如果该请求是 GET 请求,也同样如此,这种情形是用户直接在地址栏中输入URL。

最后一个要查看的文件是示例11-7中myblog/apps/templates/archive.html这个模板文件。

示例11-7 blog应用的主页面的模板文件(archive.html)

模板文件用于提供HTML,以及用编程方式控制程序输出。

models.py - 图31

逐行解释

第1~6行

这个模板的前半部分表示用户的输入表单。在提交时,服务器执行前面提到的create_blogpost()视图函数,在数据库中创建新的 BlogPost 项。第 2 行的表单变量来自BlogPostForm 的实例,它是基于数据模型的表单(以表格形式)。前面提到过,可以从其他格式选择。还提到了第1行的csrf_token用于防止CSRF,这也是必须在archive()视图函数中提供RequestContext的原因,这样才能在这里使用模板。

第8~13行

该模板的后半部分只处理一些最新的BlogPost对象,最多10个。遍历这些BlogPost对象,为用户生成独立的博文。在每篇文章之间(以及该循环前面)用水平分割线进行视觉上的分割。

11.15.2 blog应用总结

当然,还可以继续向这个blog应用添加其他特性,不过希望到现在已经向读者充分展示了Django的强大之处。更多挑战可以去看看本章末尾的练习。在构建这个blog原型应用的过程中,可以看到Django优雅、省力的特性。其中包括以下几点。

内置的开发服务器,可以没有外部依赖,并且在编辑代码后自动重新载入。

纯Python创建数据模型,无须编写或维护SQL代码或XML描述文件。

自动的abmin应用,提供全方位的内容编辑功能,无技术背景的用户也能使用。

模板系统,可以用来生成HTML、CSS、JavaScript或任何文本输出格式。

模板过滤器,可以在不干扰应用业务逻辑的情况下展现数据(如日期)。

URLconf系统,可以灵活设计URL,同时不会到影响应用中特定部分的URL。

ModelForm对象,只须稍微编码就可以方便地创建基于数据模型的表单数据。

最后,建议将应用部署到真正连接到因特网的服务器,不再使用开发服务器。放弃localhost/127.0.0.1,让应用在生产环境中工作。

如果读者喜欢这个示例,可以在Python Web Development with Django一书中了解到更完善的示例。既然读者对 Django 有了一定的了解,就通过学习一个实用的项目来更进一步。这个项目包括处理电子邮件、与Twitter交互、使用OAuth。这个项目也是其他更大项目的起点。

11.16 *中级Django应用:TweetApprover

既然读者对Django已经有了基本了解,就创建一个更真实的应用,完成一些有用的事情。关于Django的第二部分将介绍如何完成下面的任务。

1.在Django中分割较大的Web应用(项目)。

2.使用第三方库。

3.使用Django的许可系统。

4.从Django发送电子邮件。

这个应用将解决更常见的使用情形,即一个公司有一个Twitter账户,希望普通员工发布关于其业绩、产品的消息。但也需要处理一些业务逻辑,并且每条消息必须先由经理批准后才能发布。

当审核者批准了一条推文后,公司Twitter账户就自动发布该推文,但当审核者拒绝了一条推文时,该推文会退回至作者,并通知拒绝原因及修改意见。具体工作流见图11-21。

可以从零开始编写这个应用。如果这样必须构建数据模型,编写连接到数据库并进行数据读写的代码、将数据项映射到Python类,编写处理Web请求的代码,在数据返回给用户前,将数据组织成HTML格式,等等。而使用Django可以非常轻松地完成这些任务。即使Django没有与Twitter交互的内置功能,但可以使用其他Python库来完成该作业。

models.py - 图32 图11-21 TweetApprover工作流

11.16.1 创建项目文件结构

当设计一个新的 Django 应用时,首先应该设计应用结构。有了 Django,可以将一个项目分割成若干单独的应用。在前面的 blog 示例中,项目中只有一个应用(blog)。但如同本章开篇所述,应用的数目可以不止一个。当编写一个实际的应用时,项目中管理多个较小的应用比管理一个庞大、单一且笨重的应用要容易。

“TweetApprover”需要面向两种用户,一是发布推文的普通员工,二是对推文进行审核的管理者。在TweetApprover项目中,会分别针对这两种用户构建一个Django应用,应用分别称为poster和approver。

首先,创建这个Django项目。在命令行中运行下面的命令,这与前面创建mysite 项目中要到的django-admin.py startproject命令类似。

$ django-admin.py startproject myproject

为了与之前的 mysite 项目进行区分,这里把它命名为“myproject”。当然,读者也可以使用其他名称。这条命令创建了myproject文件夹,其中含有前面介绍的一些标准样板文件。

从命令行跳转到myproject文件夹中,在该文件夹中创建两个应用,即poster和approver。

$ manage.py startapp poster approver

这条命令会在myproject文件夹中创建poster和approver文件夹,其中分别含有标准应用样板文件。现在项目文件的基本框架应如下所示。

$ ls -l *

-rw-r—r—  1 wesley   admin    0 Jan 11 10:13 init.py

-rwxr-xr-x  1 wesley   admin   546 Jan 11 10:13 manage.py

-rw-r—r—  1 wesley   admin  4790 Jan 11 10:13 settings.py

-rw-r—r—  1 wesley   admin   494 Jan 11 10:13 urls.py

approver:

total 24

-rw-r—r—  1 wesley   admin    0 Jan 11 10:14 init.py

-rw-r—r—  1 wesley   admin   57 Jan 11 10:14 models.py

-rw-r—r—  1 wesley   admin   514 Jan 11 10:14 tests.py

-rw-r—r—  1 wesley   admin   26 Jan 11 10:14 views.py

poster:

total 24

-rw-r—r—  1 wesley   admin    0 Jan 11 10:14 init.py

-rw-r—r—  1 wesley   admin   57 Jan 11 10:14 models.py

-rw-r—r—  1 wesley   admin   514 Jan 11 10:14 tests.py

-rw-r—r—  1 wesley   admin   26 Jan 11 10:14 views.py

设置文件

创建新的Django项目之后,通常需要打开settings.py文件,进行编辑以安装项目。对于TweetApprover,需要添加一些设置文件中默认情况下没有的配置。首先,添加新的设置,指定当提交新推文提交并需要审核时应该通知谁。

TWEET_APPROVER_EMAIL = 'someone@mydomain.com'

注意,这并不是标准的Django设置,仅仅是这个应用需要用到的。因为设置文件是标准的Python文件,所以可以自由添加自己的设定。但除了将这条信息分别放到每个应用的设置中,还可以统一放到项目级别的设置中。注意将这里作为示例的电子邮件地址替换成审核者真实的电子邮件地址。

同样,需要告知 Django 如何发送电子邮件。Django 会读取下面设置,设置文件中默认情况下不含有这些内容,所以首先要将它们添加进去。

EMAIL_HOST = 'smtp.mydomain.com'

EMAIL_HOST_USER = 'username'

EMAIL_HOST_PASSWORD = 'password'

DEFAULT_FROM_EMAIL = 'username@mydomain.com'

SERVER_EMAIL = 'username@mydomain.com'

将上面作为示例的值替换成真实的电子邮件服务器的值。如果没有访问邮件服务器的权限,则可以跳过这一部分,并注释掉 TweetApprover 中发送电子邮件的代码。到时候会提醒的。关于Django设置的更多内容,可以访问http://docs.djangoproject.com/en/dev/ref/settings。

TweetApprover会使用Twitter的公共API发布推文。为了做到这一点,应用需要支持OAuth凭证(后续的边栏会进一步介绍OAuth)。OAuth凭证与普通的用户名和密码类似,只是应用(在OAuth中称为“使用者”)需要一对凭证,用户也需要一对凭证。

若想调用Twitter API并正常工作,必须将这4块数据发送至Twitter。就像这一节的第一个例子中的TWEET_APPROVER_EMAIL那样,这些设置也不是Django的标准设置,仅仅是TweetApprover应用的自定义设置。

TWITTER_CONSUMER_KEY = '…'

TWITTER_CONSUMER_SECRET = '…'

TWITTER_OAUTH_TOKEN = '…'

TWITTER_OAUTH_TOKEN_SECRET = '…'

幸运的是,Twitter可以方便地获取这4个值。访问http://dev.twitter.com,登录,然后单击Your apps。接下来,如果还没有应用,就单击Register New App;否则,选择已有的应用。对于创建新应用,填写图11-22所示的表单。在“Application Website”字段输入什么内容都无所谓。注意,在本章的演示中,使用的是 TweetApprover 这个名称,已经占用它了,所以读者需要自己重新选择一个名称。

填写完表单后,单击Save Application,单击Application Detail按钮。在明细页面,找到OAuth 1.0a Settings。从这里分别复制Consumer Key和Consumer Secret values到设置文件中的TWITTER_CONSUMER_KEY和TWITTER_CONSUMER_SECRET变量中。

最后,需要设置TWITTER_OAUTH_TOKEN和TWITTER_OAUTH_TOKEN。单击My Access Token按钮,可以看到类似图11-23所示的页面,其中含有所需的值。

models.py - 图33 图11-22 使用Twitter注册一个新的应用

models.py - 图34 图11-23 从Twitter获取OAuth token和OAuth token secret

核心提示:OAuth和授权与验证

OAuth 是一个开放的授权协议,用于让应用可以安全地通过 API 访问数据。不仅可以保证在不暴露用户名和密码的情况下访问应用,还可以方便地取消访问。大量的Web API都在使用OAuth,如Twitter。关于OAuth工作方式的更多信息可以参考下面这些链接:

http://hueniverse.com/oauth

http://oauth.net

http://en.wikipedia.org/wiki/Oauth

注意,OAuth仅仅是一个授权协议的示例,还有其他的协议,如OpenID。授权的目的不是为了访问数据,而是为了验证身份,即获得用户名和密码。有时会分别用到验证和授权,例如应用需要用户通过Twitter验证,然后用户授权应用更新用户的Twitter流状态。

如往常一样,应该编辑DATABASES变量,指向TweetApprover用来存储数据的数据库。在前面简单的博客应用中,使用SQLite,但同时也说过可以使用Django支持的任何数据库。如果依然想使用 SQLite,则只须从前面的博客应用中复制相应的配置即可。不要忘记执行manage.py syncdb。

另外,和之前的一样,为了更好地执行对数据的CRUD访问,最好启用Django的amin。在前面的blog应用中,大部分时候使用开发服务器运行admin,admin页面的图片和样式表都是自动处理的。如果在真实的Web服务器如(Apache)上运行,则需要确保ADMIN_MEDIA_PREFIX变量指向保存文件的Web目录。更多信息可以访问这个链接:http://docs.djangoproject.com/ en/dev/howto/deployment/modwsgi/#serving-the-admin-files。

用于Web页面的HTML模板一般位于应用的templates目录中,如果模板不在这个通常所在的位置,还可以告诉Django在哪里找到。例如,如果想创建一个独立存储模板的位置,则需要显式在settings.py文件中指出这个位置。

对于TweetApprover,需要在myproject目录中有一个统一存放模板的templates文件夹。为了做到这一点,编辑 settings.py,并确保 TEMPLATES_DIRS 变量指向这个物理地址。在POSIX系统上,它类似下面这样。

TEMPLATES_DIRS = (

'/home/username/myproject/templates',

)

在Windows系统上,由于使用的是DOS的文件路径名称,目录的路径会有所不同。如果将项目添加到已存在的C:\py\django文件夹中,则路径如下所示。

r'c:\py\django\myproject\templates',

前面提到过,前导“r”指出这是 Python 原始字符串,在这里字符串中含有多个反斜杠时非常有用。

最后,需要通知 Django 这里创建了两个应用(poster 和 approver)。需要将“myproject.approver”和“myproject.poster”添加到设置文件中的INSTALLED_APPS变量中。

11.16.2 安装Twython库

TweetApprover应用会使用Twitter的公共API向全世界发布推文。幸运的是,有一些非常好的库可以轻松完成这一点。Twitter 维护了这样一个 Python 库列表 http://dev.twitter.com/pages/libraries#python。第13章会介绍Twython库和Tweepy库。

对于这个应用,会使用Twython库来在Twitter和应用之间沟通。这里会使用easy_install,但读者也可以使用pip安装。easy_install会安装twython及其依赖,包括oauth2、httplib2和simplejson。但由于命名约定问题,尽管Python 2.6及后续版本带有simplejson,但它重命名为json,因此easy_install仍然会安装这三个twython依赖的库,输出如下所示。

$ sudo easy_install twython

Password: *

Searching for twython

Processing twython-1.3.4.tar.gz

Running twython-1.3.4/setup.py -q bdist_egg —dist-dir /tmp/

easy_install-QrkR6M/twython-1.3.4/egg-dist-tmp-PpJhMK

Adding twython 1.3.4 to easy-install.pth file

Processing dependencies for twython

Searching for oauth2

Processing oauth2-1.2.0.tar.gz

Running oauth2-1.2.0/setup.py -q bdist_egg —dist-dir /tmp/

easy_install-br8On8/oauth2-1.2.0/egg-dist-tmp-cx3yEm

Adding oauth2 1.2.0 to easy-install.pth file

Searching for simplejson

Processing simplejson-2.1.2.tar.gz

Running simplejson-2.1.2/setup.py -q bdist_egg —dist-dir /tmp/

easy_install-ZiTOri/simplejson-2.1.2/egg-dist-tmp-FWOza6

Adding simplejson 2.1.2 to easy-install.pth file

Searching for httplib2

Processing httplib2-0.6.0.zip

Running httplib2-0.6.0/setup.py -q bdist_egg —dist-dir /tmp/

easy_install-rafDWd/httplib2-0.6.0/egg-dist-tmp-zqPmmT

Adding httplib2 0.6.0 to easy-install.pth file

Finished processing dependencies for twython

核心提示:安装疑难解答

在Python 2.6中进行安装可能不会像这里这么流畅。下面是一些容易出错的地方。

1.在Mac上的Python 2.5中安装simplejson,但easy_install无法正确安装,报错并退出。此时,可以用下面这种较老的方式解决。

找到并下载压缩包(这里是simplejson的压缩包)。

解压并进入解压后的顶层文件夹。

运行python setup.py install 。

2.另一个读者发现的问题是,当编译 simplejson 可选的加速组件时,由于这是一个Python 扩展,因此须要安装所有依赖的工具来编译这个扩展,包括让编译器可以找到Python.h等。在Linux系统上,只须安装python-dev这个包即可。

当然,还有其他警告,但希望这里的介绍能解决读者可能遇到的类似问题。兼容性方面的小问题到处都有,但希望不会影响到读者前进的脚步。网上能得到许多帮助。

当成功安装后,就可以决定TweetApprover使用哪些URL了,以及如何映射到不同的用户行为上。

11.16.3 URL结构

为了创建一个统一的URI策略,需要所有用于poster应用的URL以/post开头,而用于apporver应用的URL以/approve开头。这意味着如果TweetApprover运行在example.com这个域名下,poster的URL会以http://example.com/post开头,approver的URL会以https://example.com/approve开头。

现在来看关于发布新推文的页面的更多细节,这些页面位于/post下。需要有一个页面让用户提交新推文,当接收到只以/post结尾的URL时,将用户带到这个页面。用户提交了一则推文后,需要另一个页面通知已经提交,这个页面放在/post/thankyou中。最后,需要一个URL将用户带到需要编辑的已有推文中,这个页面放在/post/edit/X下,其中X是需要编辑的推文ID。

管理者的页面位于/approve中。当用户访问这个URL时,用列表形式显示出需要审核及已发布的推文。还需要一个页面来审核一则特定的推文,并可以留下反馈,该页面位于/approve/review/X中,X是推文ID。

最后,需要决定当用户访问根 URL(example.com/)时显示哪个页面。TweetApprover的大多数用户是提交新推文的雇员,所以将根URL指向与/post相同的页面。

前面已经了解,Django使用配置文件来将URL映射到代码。在项目级别,在myproject目录中,会发现urls.py文件,该文件让Django将请求分派到对应的应用中。示例11-8中的文件实现了前面介绍的URL结构。

示例11-8 项目的URLconf文件(myproject/urls.py)

与前面的示例相同,这个项目URLconf会处理应用或admin页面。

models.py - 图35

逐行解释

第1~4行

前几行是URLconf文件中一直存在的样板文件,包括相应的导入语句,以及开发所需的amin。(完成开发后,或者不需要admin,则可以方便地删除掉admin。)

第6~14行

这里的urlpatterns变量很有趣。第7行让Django将所有以post/开头(在此之前是域名)的 URL 都会查找对应的 URL 配置文件,即 myproject.poster.urls。这个配置位于myproject/poster/urls.py文件中。下一行(第8行)让poster应用配置处理所有空URL(即只有域名)。第9行让Django将以approve/开头的URL分派到approver应用中。

最后,还有含有将URL导向admin(第10行)和登录、注销页面(第11行)的指令。这里许多功能都是Django自带的,所以无须自行编写相关代码。这里还没有介绍验证问题,但这里它很简单,只须在URLconf中包含一个二元组即可。Django提供了自有的验证系统,但也可以自己创建一个。在第12章中,你将发现Google App Engine提供了两个验证选项,一个是Google Account,另一个是使用OpenID的联合登录名。

为了回顾一下,完整的URL分派方式如表11-3所示。

从表 11-3 中可以看到,项目的 URLconf 主要目的是将请求分派到合适的应用及其处理程序中,所以需要继续了解应用层面的urls.py文件。首先来看poster应用。

与刚刚看到的项目的URLconf类似,匹配/post或“/”的URL会重定向到poster应用的URLconf,即myproject/poster/urls.py中。示例11-9显示了这个文件,该文件的任务是将URL剩余的部分映射到poster应用中需要执行的实际代码中。

表11-3 该项目处理的URL以及对应的行为 models.py - 图36

示例11-9 poster应用的urls.py URLconf文件

该URLconf用于让poster应用执行相应的行为。

models.py - 图37

这个文件中的正则表达式只会查看 URL 中/post/之后的内容,根据 patterns()的第一个参数,可以看到所有的视图函数都位于myproject/poster/views.py中。对于第一个URL模式,如果它是空的(即原先的请求要么是/post/,要么是“/”),则会调用 post_tweet()。如果该部分是thankyou,则调用thank_you()。最后,如果该部分是edit/X,其中X是数字,则会调用post_tweet(),X作为tweet_id参数传入给方法。很清爽,不是吗?如果不熟悉这里用正则表达式语法匹配变量名称,而是使用默认的整数匹配方式,可以回顾第 1 章来了解更多内容。

因为将项目分割成两个不同的应用,所以URLconf和视图函数文件都保持在最小水平。同时更易学习和重用。现在完成了poster应用的设置,接下来对approver应用进行配置。

与 poster 的 URLconf 相同,当 Django 获得一个请求时,会在示例 11-10 显示的myproject/approver/urls.py文件中查找以/approve/开头的URL。如果路径只有/approve/,则调用list_tweets()。如果URL路径匹配/approve/review/X,则调用review_tweet(tweet_id=X)。

示例11-10 approver应用的urls.py URLconf文件

approver应用的URLconf,用于处理approver的行为。

models.py - 图38

这个URLconf短一些,因为approver应用只有少数几个行为。此时,根据入站的URL路径,很清楚需要将用户导向哪里。接下来的任务是处理项目用到的数据模型。

11.16.4 数据模型

TweetApprover需要在数据库中存储推文。但管理者审核推文时,需要能够对其注释,所以每条推文可以有不同的注释。推文和注释都需要一些数据字段,如图11-24所示。

State 字段用来存储每条推文所处环节的不同阶段。图 11-25 显示了有三个不同阶段, Django会确保不会有推文会位于其他阶段。

如前面见到的,有了 Django,就能很容易在数据库中创建正确的表并读写 Tweet 和Comment 对象。在这里,数据模型既可以位于 myproject/poster/models.py 中,也可以位于myproject/approver/models.py中。如示例11-11所示,这里选择放在第一个文件中。不用担心, approver应用仍然能访问这个数据模型。

models.py - 图39 图11-24 TweetApprover的数据模型

models.py - 图40 图11-25 TweetApprover中的用于推文的状态模型

示例11-11 用于poster应用的models.py数据模型文件

这个数据模型的文件用于poster应用,含有用于发布(Tweet)和反馈(Comment)的类。

models.py - 图41

第一个数据模型是Tweet类。该类用来表示消息本身,即通常所称的post或推文,消息的作者试图将消息提交到Twitter的服务,但必须先由管理者批准。管理者可以对Tweet对象进行注释,所以 Comment对象用来表示一个Tweet所含有的一个或多个注释。现在来进一步了解这些类以及其中的属性。

Tweet类的text字段和author_email字段的长度分别限制在140和200个字符。推文长度受限于短消息服务(shrot message service, SMS)或手机上文本消息的最大长度,而大多数普通的电子邮件地址长度小于200个字符。

对于created_at字段,使用Django中方便的auto_now_add特性。这意味着无论什么时候创建推文并保存进数据库中,created_at字段会自动含有当前的日期和时间,除非显式设定。而另一个DateTimeField,即published_at,允许含有空值。该字段用于未发布到Twitter的推文。

接下来,能看到一组状态的枚举值,以及state字段的一个定义。调用这些状态,并将状态变量绑定上去。Django只允许Tweet对象的状态是这三者之一。这里定义了unicode()方法,告知Django在管理的Web站点中显示每个Tweet对象的text属性。回想一下本章前面BlogPost对象,显示文本属性是不是很有用?每条Tweet对象应该有独一无二的标签。

前面已经接触到了Meta内部类,但仍然提醒一下,可以使用这个内部类通知Django对数据实体的其他要求。在这里,它用来警告Django新的许可标表。默认情况下,Django在添加、改变、删除数据模型中的所有实体时创建许可标志。应用可以检查当前登录的用户是否有添加Tweet对象的权限。通过Django admin,网站管理员可以将权限授权给注册用户。

这样没问题,但TweetApprover应用需要一个特定的权限标志,用于向Twitter发布推文。这与前面添加、改变、删除Tweet对象有所不同。将这个标记添加到Meta类中,Django会在数据库中创建几个合适的标识。后面将会介绍如何读取这个标记,以确保只有管理者可以批准或拒绝推文。

Comment类重要性较低,但依然值得一提。该类有一个ForeignKey字段,它指向Tweet类。这个字段让Django在数据库中的Tweet和Comment对象之间创建一对多关系。与Tweet对象类似,Comment记录同样有text和created_at字段,二者与在Tweet中的同名字段含义相同。

当完成模型文件后,可以运行syncdb命令在数据库中创建对应的表,并创建超级用户登录名。

$ ./manage.py syncdb

最后,如示例11-12所示,需要添加myproject/来允许编辑Tweet,并添加poster/admin.py文件让Django在admin中显示Comment对象。

示例11-12 使用admin注册模型(admin.py)

poster应用的这个URLconf处理poster的行为。

t admin ,Comment)

所有部分都准备完成,Django可以以此为应用自动生成管理Web站点。如果现在就想尝试 admin Web 站点,在编写 approver 和 poster 视图之前,需要临时注释掉示例 11-13 (myproject/urls.py)的第 6~8 行,这几行会引用这些视图。接着可以通过/admin 这个 URL访问admin web站点(见图11-26)。记得在创建完poster/views.py和approver/views.py之后取消对这几行的注释。

示例11-13 临时的项目URLconf文件(myproject/urls.py)

这里还没有对视图的引用,所以先注释掉,不过可以尝试Django的adminweb站点。

models.py - 图43

图 11-27 显示了当创建一个新用户时,会看到自定义许可标志,该标志可以批准或拒绝推文(“Can approve or reject tweets”)。创建一个用户并确保新用户有这个权限,在后面测试TweetApprover时需要用到这个功能。创建完新用户后,需要能够编辑用户的个人资料,并设置自定义权限(不能够在创建新用户的时候设置这些权限)。

models.py - 图44 图11-26 内置的Django管理站点

models.py - 图45 图11-27 向新用户授予自定义权限

核心提示:最大限度降低代码量

目前为止,大部分都是配置,很少涉及真正的编程。Django 的一个优势就是如果正确进行了配置,就无须编写大量的代码。是的,不鼓励开发者编写代码听起来有点讽刺。但需要明白的是,Django在一个大部分用户都是记者,而不是Web开发者的公司中创建。让新闻作者或报社的其他员工了解如何使用计算机当然很好。因为现在是为了让他们具有一些Web开发技能,但不是让他们不知所措,或改变他们的职业。这种面向非专职开发人员的用户友好性融入到了Django中。

11.16.5 提交新推文以便审核

创建完poster应用后,Django在应用的目录中生成几乎为空的views.py文件。这里定义了URL配置文件中引用的方法。示例11-14显示的就是完整的myproject/poster/views.py文件的内容:

示例11-14 poster应用的视图函数(views.py)

这里用于放置poster应用的核心逻辑。

models.py - 图46

35 { 'form': form })

逐行解释

第1~10行

这里只有基本的导入语句通过这些语句,引入所需的Django功能。

第12~18行

在导入语句之后,根据 Tweet 实体定义了 TweetForm。TweetForm 中只含有 text 和author_email字段,剩下的用户不可见。该类还指定text字段应该作为HTML文本域(即多行文本框)部件显示,而不是单行较长的文本域。这个表单定义会在post_tweet()方法中用到。

第20~36行

当访问/post或/post/edit/X这样的URL时,调用post_tweet()方法。在前面的URL配置文件中定义了这个行为。这个方法完成了任务的1/4,如图11-28所示。

models.py - 图48 图11-28 post_tweet()方法的行为

用户从图11-28上面的方框开始,通过单击表单的submit按钮向下移动。这个用例和if语句的这种模式在用于处理表单的Django视图方法中很常见。当所完成了该方法中所有主要处理流程后,它会调用post_tweet.html模板,并将TweetForm实例传递过去。同样,注意通过调用 send_review_email()方法向审核者发送一封电子邮件。如果没有访问邮件服务器的权限,且没有在设置文件中设置过邮件服务器,那么需要移除这一行。

这一块代码还提供了前面没见过的新功能,即get_object_or_404()快捷方法。这个方法可能有些难懂,但开发者经常会用到这个方便的方法。该方法将一个数据模型类和一个主键作为参数,尝试获取一个含有给定ID的对象。如果找到该对象,将其赋值给tweet变量。否则,抛出一个HTTP 404错误(没有发现)。使用这种行为控制不遵循规则、手动修改URL的用户,此时会在浏览器中获得这个错误,无论用户是出于恶意还是其他原因。

第37~42行

send_review_email()方法是一个简单的辅助函数,用来在提交需要审核的新推文,或已有推文更新后,向管理者发送电子邮件通知。该方法使用了 Django 的 send_email()方法, send_email()使用设置文件中提供的服务器和凭证信息发送电子邮件。

第44~48行

当用户提交了 TweetForm 后,会把他重定向到/post/thankyou/,此时会调用 thank_you()方法。该方法使用Django内置的数据访问功能来查询数据库中当前处于pending状态的Tweet对象。具有关系数据库背景的用户毫无疑问意识到Django ORM执行了SQL命令,例如, SELECT COUNT(id) FROM Tweet WHERE state="pending"。ORM的好处是不懂SQL的用户可以直接使用对象绑定的方法来执行这里见到的SQL语句。在这里,ORM神奇地帮助开发者执行了SQL相关操作。

当获取了 pending 状态的推文后,应用调用 thank_you.html 模板,并将所有这样的推文发送过去。如图 11-30 所示,如果有若干待审核推文,则模板会显示一些信息。示例 11-15和示例11-16显示了poster应用使用的模板文件。

示例11-15 提交表单的模板(post_tweet.html)

用于poster应用的提交表单,内容不多,因为大部分任务由TweetForm模型处理了。

models.py - 图49

示例11-16 提交之后的thank_you()模板(thank_you.html)

用于poster应用的“thank_you”表单,提供了告知用户当前所处位置的逻辑。

models.py - 图50

post_tweet.html模板很简单,它只在HTML表格中显示表单,并在表格下方添加提交按钮。与前面示例11-15中用于blog应用中表单的模板相比,这个模板几乎可以重用。重用当然应该受到鼓励,但分享HTML超过了这个范畴。

图 11-29 显示了模板的输出,它表示用户试图填写一条推文的输入表单。接下来会看到在用户提交推文之后生成“thanks for your submission”页面的模板,如图11-30所示。

models.py - 图51 图11-29 用于提交新推文的表单,位于/post页面下

models.py - 图52 图11-30 感谢页面,在提交新推文之后显示

11.16.6 审核推文

现在已经完成了poster应用,到了介绍approver应用的时候。文件myproject/approver/urls.py调用 myproject/apporver/views.py 中的 list_tweets()和 review_tweet()方法。完整的文件见示例11-17。

示例11-17 approver应用的视图函数(views.py)

这里含有approver应用的核心功能,包括表单,显示带审核推文,以及帮助处理决定。

models.py - 图53

models.py - 图54

models.py - 图55

逐行解释

第1~24行

在所有导入语句之后,第一个接触的是list_tweets()方法。该方法的任务是向用户返回待审核和已发布推文的列表。在这个方法头上面是@permission_required装饰器。该装饰器通知Django只有登录且拥有poster.can_approve_or_reject_tweet权限的用户才能访问该方法。这两个权限是在 myproject/poster/models.py 中声明的自定义许可权限。没有登录的用户,或登录但没有正确权限的用户会重定向到/login 页面(如果忘记了装饰器是什么,可以阅读 Core Ptyhon Programming或Core Ptyhon Language Fundamentals的“函数”章节)。

如果用户有正确的权限,则会执行该方法。其使用Django的数据访问功能来获取所有待审核的推文列表,以及所有已发布的推文列表。接着将这两个列表发送给list_tweets.html模板,并让模板渲染结果。更多内容参见下面的模板文件。

第26~36行

接下来,在myproject/approver/views.py中定义了ReviewForm。在Django中有两种方式定义表单。在myproject/poster/views.py中,以Tweet实体为基础定义了TweetForm。这里作为字段的集合定义了一个表单,没有任何底层数据实体。该表单用于让管理者批准或拒绝待审核的推文,其中没有用于表示审核决定的数据实体。表单使用一个选择集合(APPROVAL_CHOICES)来定义审核者需要做出的批准/拒绝选择,并用一组单选按钮显示出来。

第38~66行

接下来是review_tweet()方法(见图11-31)。这与myproject/poster/views.py文件中的表单处理方法类似,但该方法假设tweet_id总是存在。因为根本无法审核一条不存在的推文。

models.py - 图56 图11-31 review_tweet()方法中的表单处理

代码需要从表单中读取用户提交的数据。通过 Django,可以使用 form.cleaned_data[]数组完成这个任务,该数组含有用户通过表单提交的值,这些值已经转换成Python数据类型。

注意在 review_tweet()视图函数中请求对象时对 build_absolute_uri()方法的调用方式。该方法用于获取编辑推文表单的链接。该链接会通过拒信发送给推文的作者,这样推文作者可以了解管理者的反馈并记录这条推文。build_absolute_uri()方法针对特定方法返回对应的URL,在这里是post_tweet()。这里的URL是/poster/edit/X,其中,X是推文的ID。为什么不仅仅使用含有该URL的字符串呢?

如果决定将URL改变成/poster/change/X,则需要记住所有硬编码的URL模式(/poster/edit/X),并将其更新到新的URL。这样破坏了Django幕后的DRY原则。关于DRY原则和Django的其他设计原则,可以参见http://docs.djangoproject.com/en/dev/misc/design- philosophies。

刚刚提到的情况与在/post/thankyou中硬编码的URL不同,这个URL没有使用变量。在感谢页面中:1)该页面只有一个;2)页面不会改动;3)没有需要关联的视图函数。为了不对URL进行硬编码,需要使用另一个工具,在硬编码URL的地方使用django.core.urlresolvers.reverse()。这个方法做了些什么?通常会从一个URL开始,根据请求找到一个分配到的视图函数。在这种情况下,通过给定的视图函数构建URL(就如同这个函数的名称),视图函数与其他参数一起传递给reverse(),然后返回一个URL。关于使用reverse()的更多示例可以阅读Django的教程,参见https://docs.djangoproject.com/en/dev/intro/tutorial04/#write-a-simple-form。

第68~84行

send_approval_email()和 send_rejection_email()这两个辅助方法,用来使用 Django 的send_mail()函数向推文的作者发送邮件。同样,在无法访问邮件服务器的情况下从review_tweet()中移除对这些方法的调用。

第86~93行

publish_tweet()同样是辅助方法。其调用 Twython 包中的 updateStatus()方法来向 Twitter发布新的推文。注意,其中使用前面向settings.py文件中添加的4个Twitter凭证信息。另外,使用UTF-8对推文编码。

现在来看模板文件,首先来看登录页面之后的状态页面,因为后者比前者更有趣。示例11-18显示了状态页面使用的模板。用户的输出页面主要分成两部分:一组需要审核的推文;以及一组审核过并已发布的推文。

示例11-18 用于显示推文状态的模板(list_tweets.html)

用于poster应用的状态页面的模板主要有两个部分:待审核和已发布推文。

models.py - 图57

models.py - 图58

这个模板很有趣,这是第一个含有循环的模板,其遍历pending_tweets和published_tweets,接着将每条推文渲染到表格中的一行里,使用 cycle 结构让表格隔行用灰色显示,如图 11-32所示。同时还让每条待审核的推文的文本连接到/approve/review/X页面,X是推文的ID。最后,使用Django的timesince过滤器来显示推文创建到现在的时间,而不是显示原始的日期和时间。这样让列表稍微容易阅读,也让多时区的用户看起列表来更加合理。

当审核者选择了一条潜在的推文并进行决策时他们会看到单独用于审核这条推文的视图。如图11-33所示。

用于渲染待审核推文的表单的模板是review_tweet.html,见示例11-19。

如果用户在没有登录或没有正确的权限的情况下发送 login/这个 URL 会怎么样?在myproject/urls.py 中,Django 会运行方法 django.contrib.auth.views.login 中的代码,这个方法是 Django 自带的,用来处理登录问题。所要做的就是编写 login.html 模板。示例 11-20显示了这个应用使用的简单模板。若想详细了解 Django 验证系统,可以查看官方文档(https://docs.djangoproject.com/en/dev/topics/auth/)。

models.py - 图59 图11-32 一系列待发和已发布的推文

models.py - 图60 图11-33 批准待发推文

示例11-19 myproject/templates/review_tweet.html

该模板用于poster应用的推文审核页面。

models.py - 图61

示例11-20 myproject/templates/login.html

这是poster应用登录页面的模板,它利用了Django的验证系统。

models.py - 图62

models.py - 图63

试用TweetApprover

现在所有组件都完成了,返回URLconf,取消前面对所有添加的行为的注释。如果还没有创建可以批准或拒绝推文的用户,那么请现在创建。接着通过Web浏览器跳转到/post下, (在你的域中)创建一条新推文。最后,跳转到/approve下,批准或拒绝这条推文。当批准一条推文后,访问Twitter的页面,验证这条推文是否已发布。

读者可以从本书的配套网站上下载项目的完整代码:http://corepython.com。

11.17 资源

表11-4列出了与本章涵盖的主题和项目相关的资源。

表11-4 其他Web框架和资源 models.py - 图64

11.18 总结

读者刚刚接触到了Django的冰山一角。与Python相比,Web开发的范畴非常广。有许多地方需要探索,所以建议读者阅读优秀的 Django 文档,特别是 http://docs.djangoproject.com/en/ dev/intro/tutorial01中的教程。读者还可以了解Pinax中可重用的插件式应用。

另外,读者可以通过Python Web Development with Django一书继续深入了解Django。现在还可以了解其他Python Web框架,如Pyramid、TurboGears、web2py,或其他更轻量级的框架,如Bottle、Flask和Tipfy。另一个方向是可以开始探索云计算。第12章将对其进行介绍。

11.19 练习

Web框架

11-1 复习术语。CGI和WSGI是什么意思?

11-2 复习术语。纯CGI主要有哪些问题,为什么当今Web生产环境中的服务很少用到纯CGI?

11-3 复习术语。WSGI解决了哪些问题?

11-4 Web框架。Web框架的目的是什么?

11-5 Web框架。Web开发使用的框架一般遵循MVC模式。描述这三个组件。

11-6 Web框架。举例说出Python的一些全栈Web框架。使用其中的每个框架创建一个简单的“Hello World”应用。记录每个框架开发和执行时的异同。

11-7 Web框架。对不同的Python模板系统做一些研究。创建网络或电子表格来比较其中的异同。要确保含有下面的语法项比较:a)显示数据变量,b)调用函数或方法,c)嵌入纯Python代码,d)执行循环,e)if-elseif-else条件语句,以及f)模板继承。

Django

11-8 背景。Django框架在何时何地创建?其主要目标是什么?

11-9 术语。Django项目和Django应用之间有什么区别?

11-10 术语。Django没有用MVC,而是使用MTV(Model-Template-View),比较 MTV和MVC的异同。

11-11 配置。Django开发者在哪里创建数据库设置?

11-12 配置,Django可以在下面的数据上运行:

a)关系数据库;

b)非关系数据库;

c)a和b;

d)两者都不是。

11-13 配置。在http://djangoproject.com中下载并安装Django Web框架(如果使用的不是Windows系统,还要下载SQLite,因为针对Windows的Python 2.5+版本自带SQLite)。

a)执行“django-admin.py startproject helloworld”,启动项目,接着通过“cd helloworld; python ./manage.py startapp hello”启动应用。

b)编辑helloworld/hello/views.py,它包含下面的代码。

from django.http import HttpResponse

def index(request):

return HttpResponse('Hello world!')

c)在helloworld/settings.py中,向INSTALLED_APPS变量元组中添加“hello”。

d)在helloworld/urls.py,将下面注释掉的行

(r'helloworld/', include('helloworld.foo.urls')),

(替换成这一行)。

(r'^$', 'hello.views.index'),

e)执行“python ./manage.py runserver”,访问http://localhost:8000,来确认代码正常工作,在浏览器中显示了“Hello world!”。并尝试将输出改为其他字符串。

11-14 配置,URLconf是什么?一般在哪里找到?

11-15 教程。学习Django教程的四个章节,参见http:docs.djangoproject.com/en/devintro/tutorial01。警告:不要只复制该网页的代码。希望读者能够对应用进行修改。添加原先不存在的功能。

11-16 工具。Django admin应用是什么?如何启用它?为什么admin很有用?

11-17 工具。能否在不使用admin或Web浏览器的情况下测试应用的代码?

11-18 术语。CSRF是什么意思?为什么Django含有安全机制来阻止这种尝试?

11-19 模型。列举出你可能用到的5个模型类型,以及这些模型一般会使用到什么类型的数据。

11-20 模板。在 Django 模板中,什么是标签?另外,块标签和变量标签之间有什么区别?如何区分这两种类型的标签?

11-21 模板。描述如何通过Django实现模板继承。

11-22 模板。在Django模板中,过滤器是什么?

11-23 视图。什么是通用视图?为什么需要使用通用视图?是否在有些情况下不需要使用通用视图?

11-24 表单。描述 Django 中的表单,包括工作方式、位于代码中的位置(从数据模型到HTML模板)。

11-25 表单。讨论模型表单,以及其用途。

Django博客应用

11-26 模板。在BlogPost应用中的archive.html模板里遍历每篇博文,并显示给用户。添加一个针对特殊情况的测试,即没有发布任何一篇博文,此时显示一条特殊的消息。

11-27 模型。在这个应用中,对时间戳做了太多的工作。有一种方式能让 Django 在创建 BlogPost 对象时自动添加时间戳。找到并实现这种方式,移除blog.views.create_blogpost()和 blog.tests.BlogPostTest.test_obj_create()中显式设置时间戳的代码。此时是否同样要改变blog.tests.BlogPostTest.test_post_create()中的代码?提示:可以在下一章中看Google App Engine是如何完成的。

11-28 通用视图。抛弃archive()视图函数,该函数使用render_to_response(),将应用转换成使用通用视图。只须完全移除 blog/views.py 中的 archive()函数,同时也将blog/templates/archive.html移动到blog/templates/blogpost/blogpost_list.html中。研读list_detail.object_list()通用视图,然后直接在应用的URLconf中调用它。这里需要创建“queryset”和“extra_context”字典,用来将自动生成的BlogPostForm()对象和所有博客项通过通用视图传给模板。

11-29 模板。前面介绍了Django模板过滤器(并在例子中使用了upper())。在BlogPost应用中的archive.html(或blogpost_list.html)模板中添加另一行用于显示数据库博文总数的代码,在显示之前,使用过滤器只显示最近的10篇。

11-30 表单。通过使用ModelForm自动创建的表单对象,就无法指定正文文本区域(rows = 3, cols= 60)的行和列属性,正如同前面对Form和forms.CharField的HTML部件做的那样。其默认的是rows = 10,cols = 40,如图11-20所示。如何指定3行和60列呢?提示,可以参考这篇文档http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#overr iding-the-default-field-types-orwidgets

11-31 模板。为博客应用创建一个基模板,修改所有已有模板,使用模板继承。

11-32 模板。阅读 Django 文档中关于静态文件(HTML、CSS、JS 等)的内容,改善博客应用的外观。如果这样着手有些困难,可以先尝试一些小的设置,直到可以处理复杂的内容。

<style type="text/css">

body { color: #efd; background: #453; padding: 0 5em; margin: 0 }

h1 { padding: 2em 1em; background: #675 }

h2 { color: #bf8; border-top: 1px dotted #fff; margin-top: 2em }

p { margin: 1em 0 }

</style>

11-33 CRUD。让用户可以编辑或删除博文。可以考虑添加额外的时间戳字段来表示编辑时间,而用已有的时间戳表示创建时间。也可以在博文编辑或删除时直接修改已有的时间戳。

11-34 游标和页码。显示最新的 10篇博文很好,但让用户通过页码浏览较老的博文就更好了。在应用中使用游标并添加页码。

11-35 缓存。在下一章的Google App Engine博客中,部署Memcache来缓存对象。所以无须再次访问数据库进行相似的操作。那么是否在这个Django应用中需要用到缓存?为什么?

11-36 用户。让站点支持多个博客。每个用户应该获得一组博客页面。

11-37 交互。向应用添加另一个功能,使得每当创建了新的博客项时,Web站点的管理者和博客的作者都会收到一封含有详细信息的电子邮件。

11-38 业务逻辑。除了前面发送电子邮件的练习,在 Twitter 应用中让管理者在发布博文之前先对博文进行审核。

Django Twitter App

11-39 模板。build_absolute_uri()方法用来消除 URL 配置文件中的硬编码 URL。但在HTML模板中依然有一些硬编码URL路径。这些路径在哪里?如何移除这些硬编码URL?提示:阅读这篇文章:http://docs.djangoproject.com/en/dev/ref/templates/builtins/#std: templatetag-url。

11-40 模板。通过添加 CSS 文件并将其引入到 HTML 模板中,以此来美化TweetApprover。

11-41 用户。目前,任何用户不用登录就可以发布新推文。修改应用,让未登录用户或没有添加推文权限的用户无法发布新推文。

11-42 用户。在强迫用户登录后才能发布新推文后,如果用户的个人资料中有电子邮件,预先用登录用户的电子邮件地址填充Author email字段。提示:可以阅读这篇文档:http://docs.djangoproject.com/en/1.2/topics/auth。

11-43 缓存。当用户访问/approve时,缓存推文列表。当用户批准或拒绝了一篇推文时,他会回到这个页面。确保当用户回到这个页面时,看到的是刷新过的非缓存页面。

11-44 登录和提交报告。当推文改变状态时,向其添加新的Comments来创建审核流程。例如,当拒绝一条推文后,向其添加一个Comment指出其已被拒绝,以及拒绝时间。推文内容更新后,添加另一个 Comment。发布后,添加另一个Comments来表示发布时间和审核者。

11-45 CRUD。向推文的审核页面添加第三个选项,除了接受或拒绝,还让审核者可以删除提交的推文。可以通过对数据库中的对象调用delete()方法来删除这个对象,例如,reviewed_tweet.delete()。

11-46 交互。当员工提交了一条新推文时,则向管理者发送一封电子邮件。但电子邮件仅仅说明了有一条推文需要审核。向电子邮件中添加新推文本身,还有一个让管理者可以直接单击以跳转到推文审核页面的链接,以便批准和拒绝这条推文。可以与myproject/approver/views.py中如何发送电子邮件进行比较。