第 14 章 轻松使用 Django
基于 Python 开发的 Web 应用框架有很多,例如 Pyramid、Flask、Bottle、Tornado 等。本章我们将目光放在 Django 上,首先对其进行简单学习,然后了解一些在实际开发过程中对 Django 有辅助作用的库。
14.1 Django 简介
Django 是一款基于 Python 开发的全栈式一体化 Web 应用框架。2003 年问世之初,它只是美国一家报社的内部工具,2005 年 7 月使用 BSD 许可证完成了开源。其目的是削减代码量,简单且迅速地搭建以数据库为主体的复杂 Web 站点。它是全栈式框架,因此安装起来很简单,而且使用者众多。这使得 Django 除具有完备的官方文档之外,还有大量的关联文档、丰富的第三方库可供使用。与其他框架相比,Django 用起来要轻松得多。
14.1.1 Django 的安装
Django 的安装与其他 Python 程序包一样,需要通过 pip 进行。
$ pip install django
本书使用了 Django 的 1.7.1 版。
14.1.2 Django 的架构
Django 继承并简化了 MVC 架构。MVC 中的 Controller 部分基本全由 Django 完成。View 部分则被分割为两部分,即负责 HTML 渲染的模板和负责显示逻辑的视图。所以 Django 又被称为 MTV(Model-Template-View)框架。这个 Django 框架除了 MTV 框架的核心部分,即 O/R 映射工具、URL 分配器(Dispatcher)、视图、模板系统之外,还有管理界面、缓存系统、国际化支持、表单处理等机制和功能。
使用 Django 开发 Web 应用站点时,需准备一个承载着 Django 实例及数据库设置等内容的工程,然后通过在该工程中新建几个应用或者调用外部应用,或者将二者结合起来进行开发。由于每个应用的本质都是 Python 程序包,所以只要按功能(模型、视图、模板等)对这些包进行分离,完全可以拿到其他工程中重复利用。
图 14.1 是 Django 架构处理请求的流程。
图 14.1 Django 的架构
① 客户端发来的 HTTP 请求被视为 Django 的请求对象
② URL 分配器负责搜索并调用被请求的 URL 所对应的视图
③ 被调用的视图视情况使用模型或模板生成响应对象
④ 响应对象作为 HTTP 响应发回给用户
接下来我们按顺序了解一下 Django 的组成元素。
◉ 工程
工程是承载了 Django 实例的所有设置的 Python 程序包。大部分情况下,一个 Web 站点就是一个工程。工程内可以新建及存放该工程固有的应用,或者保存 Web 站点的设置(数据库设置、Django 的选项设置、各应用的设置等)。
如果完整安装了 Django,可以使用 Django 管理脚本 django-admin 的 startproject 命令创建工程目录 myprj 和初始文件。
$ django-admin startproject myprj
myprj 的布局如下所示。
myprj/
├── manage.py
└── myprj
├── _init_.py
├── settings.py
├── urls.py
└── wsgi.py
◉ 应用
对于 Django 而言,应用指的是表示单一功能的 Web 应用的 Python 程序包。由于其实质就是 Python 程序包,因此放在 PYTHONPATH 有效的任何位置都没有问题。这里最好尽量减少应用与工程、应用与其他应用之间的依赖关系,做到功能独立,以便在其他工程中重复利用。
比如我们要创建名为 polls 的应用,就可以使用刚刚在工程目录下生成的工程管理脚本 manage.py,执行 startapp
命令。
$ cd myprj
$ python manage.py startapp polls
随后会按照下列布局生成 polls 目录及初始文件。
polls/
├── _init_.py
├── admin.py
├── migrations
│ └── _init_.py
├── models.py
├── tests.py
└── views.py
◉ 模型
Django 提供了 O/R 映射工具,因此可以用 Python 代码来描述数据库布局。
每个模型都是继承了 django.db.models.Model 类的 Python 的类,分别对应数据库中的一个表格。通过将数据库的字段、关系、行为定义为模型类的属性或方法,我们可以使用丰富且灵活的数据库访问 API。
◉ URL 分配器
URL 分配器机制使得 URL 信息不再受框架及扩展名的制约,从而让 Web 应用的 URL 设计保持简洁。
URL 在 URLconf 模块中进行描述,URLconf 模块中包括使用正则表达式书写的 URL 和 Python 函数的映象。URLconf 能够以应用为单位进行分割,因此提高了应用的可重复利用性。另外,我们可以利用给 URL 设置名称并定义的方式让代码和目标直接通过该名称调用 URL,从而将 URL 设计与代码分离。
◉ 视图
Django 的视图是一类函数,它能够生成指定页面的 HttpResponse 对象或像 Http 404 这样的异常情况,返回 HTTP 请求。典型的视图函数的处理流程通常是先从请求参数中获取数据,读取模板,然后根据获取到的数据渲染模板。
◉ 模板系统
在 Django 的概念中,模板系统只负责显示,并不是编写逻辑代码的环境。因此 Django 的模板系统将设计与内容、代码分离开来了,是一种功能强、扩展性高、对设计者很友好的模板语言。
模板基于文本而不是 XML,因此它不但能生成 XML 和 HTML,还能生成 E-mail、JavaScript、CSV 等任意文本格式。
另外,如果使用模板继承功能,子模板只需要将父模板中预留的空位填满即可。我们在编写模板时只需要描述各个模板独有的部分,因此可以省去重复冗余的编码过程。
◉ 管理界面
大多 Web 应用在运行过程中,都需要一个专供拥有管理员权限的用户添加、编辑、删除数据的界面,但是实际制作这个界面并不容易。
Django 只需将已完工的模型添加到管理站点,就能根据模型定义动态地生成面,为我们提供一个功能齐全的管理界面(图 14.2、图 14.3)。
图 14.2 登录管理界面后的首页
图 14.3 管理界面的模型编辑界面
◉ 缓存系统
Django 可以使用 memcached 等缓存后端(Cache Backend)轻松地缓存数据。比如可以将动态页面的渲染结果(部分或全部)缓存下来,等到下次需要时直接读取缓存,从而不必每次都对动态页面进行处理。
缓存的后端可以从 memcached、数据库、文件系统、本地内存等位置中进行选择。缓存对象也支持整个网站、特定的整个视图、部分模板、特定数据等。
14.1.3 Django 的文档
在下述网站上可以阅览 Django 1.7 ~ 1.10 以及 dev 版的英文文档。
Django documentation
从下一节开始,本章的内容均面向有 Django 使用经验的读者,因此,在进入下一节的学习之前,请先阅读文档内的教程部分。
Django 教程
14.2 数据库的迁移
14.2.1 什么是数据库的迁移
数据库的迁移(Migration)是对数据库中的模式定义以及数据转移进行管理的功能。它的主要作用是版本管理以及版本升级、回滚等,类似于 Ruby 的 Ruby on Rails 框架。
那么,在什么场合下才能体会到它的便捷之处呢?我们经过亲身实践,认为它在以下场合能发挥极大作用。
◉ 多人持续开发时
多人共同开发的时候,应用程序经常要反映别人的修改,所以模式升级和回滚的机会也相对较多。
◉ 需要在运行过程中修改数据表时
如果需要在运行过程中修改数据表,就必须修改模式以防止现有数据受损。另外,一旦修改后出现问题,还需要立刻进行恢复。这种时候就必须做好版本管理,保证随时可以回滚。
14.2.2 Django 的迁移功能
◉ Django 数据库迁移
Django 的迁移功能支持给模型添加新的字段、向数据库的列添加 null=Ture 等。在版本管理方面,Django 以模型为基准,通过生成并执行迁移文件来完成数据库版本的更新,从而实现版本管理。
迁移文件同时也是从零新建数据库的方法之一。第一个迁移文件的执行结果是从空模式迁移为初始状态的数据表。等到所有迁移文件执行完毕之后,我们就会得到应用了最新版本的数据库模式。另外,如果有上一个版本的数据库,那么只需执行最后生成的迁移文件即可得到最新的数据库模式。
◉ 示例(对新建的应用套用迁移时)
接下来我们以新建应用的情况为例来学习一下迁移功能。首先新建一个应用(LIST 14.1)。
LIST 14.1 新建 polls 应用
$ python manage.py startapp polls
新建 polls 应用之后,首先在 settings.py 的 INSTALLED_APPS 中添加 polls,然后在 polls/models.py 中创建模型(LIST 14.2)。
LIST 14.2 myprj/settings.py
INSTALLED_APPS = (
...
'polls',
)
polls 应用需要投票项目(poll)和选项(choice)两个模型,这里我们只创建 poll。poll 中要包含题目(question_test)的信息(LIST 14.3)。
LIST 14.3 polls/models.py
# -*- coding: utf-8 -*-
from django.db import models
class Poll(models.Model):
question_text = models.CharField(max_length=200)
接下来要做的是生成迁移文件。迁移文件通过 makemigrations
命令生成(LIST 14.4)。
LIST 14.4 生成第一个迁移文件
$ python manage.py makemigrations polls
Migrations for 'polls':
0001_initial.py:
- Create model Poll
执行上述命令后,会生成 polls/migrations/0001_initial.py 迁移文件。此时数据库中还没有 polls,所以这里我们需要通过 migrate
命令执行迁移文件,从而生成 polls(LIST 14.5)。
LIST 14.5 执行 migrate
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying polls.0001_initial... OK
Applying sessions.0001_initial... OK
此时会发现 poll 还缺少公布日期(Publication Date),于是我们还需要为它添加公布日期(LIST 14.6)。
LIST 14.6 polls/models.py
# -*- coding: utf-8 -*-
from django.db import models
class Poll(models.Model):
question_text = models.CharField(max_length=200)
# 添加公布日期
pub_date = models.DateTimeField('date published')
此类细微修改也能通过迁移功能完成。做修改时也要用 makemigrations
命令生成迁移文件(LIST 14.7)。
LIST 14.7 生成用于修改的迁移文件
$ python manage.py makemigrations polls
You are trying to add a non-nullable field 'pub_date' to poll without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
此时,计算机表示“pub_date 中没有设置 default”,询问如何处理已有数据。选择 1 可以向已有数据输入要设置的值,选择 2 则可以中断迁移文件的生成。这里我们选择 1。计算机告诉我们可以使用 datetime module 和 Django 的 timezone mudule,这里我们选择 timezone.now()
(有时区概念的当前时间。向数据库中添加的值为 UTC)(LIST 14.8)。
LIST 14.8 设置 default 数据
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>> timezone.now()
Migrations for 'polls':
0002_poll_pub_date.py:
- Add field pub_date to poll
到此,用于修改的迁移文件生成完毕。只生成迁移文件并不能将修改反映到数据库中,因此别忘了执行 migrate
(LIST 14.9)。
LIST 14.9 执行 migrate
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Applying polls.0002_poll_pub_date... OK
这样一来,修改就反映到数据库中了。
以上就是迁移的基本使用方法。只要记住 makemigrations
和 migrate
这两个命令,就能够应付绝大部分情况。不过例外总还是有的,为此我们来简单了解一下各个命令。
◉ 命令简介
○ migrate
用于执行迁移文件的命令(LIST 14.10、LIST 14.11)。
LIST 14.10 指定应用执行
$ python manage.py migrate application
LIST 14.11 指定文件名或编号执行
$ python manage.py migrate application file_name_or_number
该命令可以指定应用或文件名来执行。如果不指定,则会执行所有应用尚未执行过的迁移文件。
- —fake
该选项可以将未执行的迁移文件标记为已执行,但不实际执行该文件。比如存在编号很旧的未执行的迁移文件,但当前的数据库状态下执行该文件会出现错误时,就可以使用这个选项。
○ makemigrations
用于生成迁移文件的命令。
- —empty
用于生成空迁移文件的选项。在我们想手动编写迁移文件的内容,或者需要生成用于数据迁移的文件时,可以使用该选项。关于数据迁移的详细内容,我们将在“数据迁移”部分学习。
○ sqlmigrate
该命令可以查看套用迁移文件时执行的 SQL(LIST 14.12)。
LIST 14.12 查看执行的 SQL
$ python manage.py sqlmigrate application file_name_or_number
◉ 数据迁移
学习 makemigrations
的 —empty 选项时我们了解到,Django 可以借助迁移功能做数据迁移。数据迁移是伴随着值的变化的数据库模式变更,为与一般的模式迁移作区分,才被称为数据迁移。下面我们试着做一个让 poll 的公布日期延后一天的数据迁移(LIST 14.13、LIST 14.14)。
LIST 14.13 生成用于数据迁移的文件
$ python manage.py makemigrations --empty polls
Migrations for 'polls':
0003_auto_20141104_0236.py:
LIST 14.14 0003_auto_20141104_0236.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('polls', '0002_poll_pub_date'),
]
operations = [
]
生成的迁移文件中只有类的定义和空的方法,并没有描述实际的处理。我们只需添加数据迁移所需的处理,即可用它来做数据迁移(LIST 14.15)。
LIST 14.15 添加让公布日期延后一天的数据迁移
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
from django.db import models, migrations
def forward_pub_date_by_one_day(apps, schema_editor):
Poll = apps.get_model('polls', 'Poll')
for poll in Poll.objects.all():
poll.pub_date += datetime.timedelta(days=1)
poll.save()
def backward_pub_date_by_one_day(apps, schema_editor):
Poll = apps.get_model('polls', 'Poll')
for poll in Poll.objects.all():
poll.pub_date -= datetime.timedelta(days=1)
poll.save()
class Migration(migrations.Migration):
dependencies = [
('polls', '0002_poll_pub_date'),
]
operations = [
migrations.RunPython(forward_pub_date_by_one_day,
backward_pub_date_by_one_day)
]
添加完处理之后,执行migrate
,将其反映到数据库中(LIST 14.16)。
LIST 14.16 执行 migrate
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Applying polls.0003_auto_20141104_0236... OK
想了解数据迁移的更多内容可以参考 Django 的迁移文档 1。
1https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations
◉ 迁移文件的 squash
随着迁移的频繁使用,迁移文件会越积越多,执行时间自然越来越长。如果想缩短时间,就需要将多个迁移文件合并成一个,这时可以使用 squashmigrations
命令(LIST 14.17)。比如在发布 Web 应用时将所有迁移文件合并成一个,这样一来我们在搭建环境时就只需要执行一个迁移。
LIST 14.17 迁移文件的 squash
$ python manage.py squashmigrations polls 0003
Will squash the following migrations:
- 0001_initial
- 0002_poll_pub_date
- 0003_auto_20141104_0236
Do you wish to proceed? [yN]
计算机询问是否执行,这里键入 y 并执行(LIST 14.18)。
LIST 14.18 迁移文件的 squash
Do you wish to proceed? [yN] y
Optimizing...
Optimized from 3 operations to 2 operations.
Created new squashed migration pathto/myprj/polls/migrations/0001_squashed_0003_auto_20141104_0236.py
You should commit this migration but leave the old ones in place;
the new migration will be used for new installs. Once you are sure
all instances of the codebase have applied the migrations you squashed,
you can delete them.
Manual porting required
Your migrations contained functions that must be manually copied over,
as we could not safely copy their implementation.
See the comment at the top of the squashed migration for details
执行后生成了名为 0001_squashed_0003_auto_20141104_0236.py
的新文件。这个迁移文件相当于之前生成的 0001、0002、0003 的总和。往后在新建的数据库中执行 migrate 时会使用这个新的迁移文件,而在已有数据库中则使用原来的迁移文件。
不过,从执行 squashmigrations
时输出的信息来看,我们需要手动修改新文件。这是因为之前做数据迁移时,0003_auto_201141104_0236 是手动添加的。新建数据库进行迁移时不需要进行数据迁移,所以我们可以将数据迁移的相关处理从 0001_squashed_0003_auto_20141104_0236 中删除。打开文件,删除 operations 内的 migrations.RunPython(…)。另外,用于添加 pub_date 的迁移文件中设置的当前时间也要一并删除(LIST 14.19)。
LIST 14.19 0001_squashed_0003_auto_20141104_0236.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.utils.timezone import utc
# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
# polls.migrations.0003_auto_20141104_0236
class Migration(migrations.Migration):
replaces = [(b'polls', '0001_initial'), (b'polls', '0002_poll_pub_date'), (b'polls', '0003_auto_20141104_0236')]
dependencies = [
]
operations = [
migrations.CreateModel(
name='Poll',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('question_text', models.CharField(max_length=200)),
# 删除下面第一行,添加下面第二行
# ('pub_date', models.DateTimeField(default=datetime.datetime (2014, 11, 4, 1, 22, 22, 729029, tzinfo=utc), verbose_name=b'date published')),
('pub_date', models.DateTimeField(verbose_name=b'date published')),
],
options={
},
bases=(models.Model,),
),
# 删除以下被注释的部分
# migrations.RunPython(
# code=polls.migrations.0003_auto_20141104_0236.forward_pub_date_by_one_day,
# reverse_code=polls.migrations.0003_auto_20141104_0236.backward_pub_date_by_one_day,
# atomic=True,
# ),
]
新建数据库进行迁移时,只需要执行 polls 的一个迁移文件。
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying polls.0001_squashed_0003_auto_20141104_0236... OK
Applying sessions.0001_initial... OK
无论是多人持续开发时还是需要在运行过程中修改数据表时,迁移都能大幅减少劳动力的付出以及出现操作失误的风险。各位请务必试一试,亲自体验其效果。
14.3 fixture replacement
14.3.1 什么是测试配置器
在第 8 章中,我们学习了测试的相关知识,但在实际编写测试代码的过程中,有时必须依赖于模块和数据,这种时候就要事先准备测试数据。
Django 默认能够将测试数据事先读入数据库。Django 的测试配置器是一种专门用来让 Django 读入数据库的特定格式数据文件。除测试之外,测试配置器还可以用来导入开发初期阶段需要用到的数据。
◉ 用 loaddata 命令读取配置器
下面我们手动创建一个用于配置器的数据文件,然后读取它。这里需要利用前面生成的 Poll 模型。我们首先在工程目录下创建名为 polls.json 的文件,文件内容如 LIST 14.20 所示。
LIST 14.20 polls.json
[
{
"model": "polls.poll",
"pk": 1,
"fields": {
"question_text": "Why not use the fixture",
"pub_date": "2011-11-01 00:00:00Z"
}
},
{
"model": "polls.poll",
"pk": 2,
"fields": {
"question_text": "Why not use the fixture2",
"pub_date": "2011-11-02 00:00:00Z"
}
}
]
pk 指模型的 primary key 的 ID。接下来将这个数据文件加载到数据库中(LIST 14.21)。
LIST 14.21 执行 loaddata
$ python manage.py loaddata polls.json
执行完毕后,可以在数据库的 polls_poll 表中看到上述配置器提供的数据。各位可以使用 SQL 等进行查看。
◉ 配置器文件的存放位置
在工程开始阶段,将配置器文件放在工程根目录下并无大碍,但是随着应用规模越来越大,文件自然也越来越多,放在工程根目录下将很难维护。这种时候就需要修改配置器文件的存放位置并进行整理。整理有以下 2 个方法。
存放在对应应用的 fixtures 目录下
存放在 settings.py 的 FIXTURE_DIRS 指定的目录下
比如我们在应用目录下创建了 fixtures 目录(例如 polls/fixtures/polls.json),我们就可以将该应用需要读取的配置器文件放在这里。这样一来,只需要执行 python manage.py loaddata polls.json 命令即可读取配置器文件。另一种就是设置 settings.py 中的 FIXTURE_DIRS,该变量所指目录下存放的配置器文件将被视为读取对象。如果想把任意目录作为读取对象,建议使用这种方法。LIST 14.22 是添加与 manage.py 同级的 fixtures 目录的方法。
LIST 14.22 FIXTURE_DIRS 的设置
FIXTURE_DIRS = [
os.path.join(BASE_DIR, 'fixtures'),
]
◉ 在 TestCase 中使用配置器
Django 的 TestCase 能够自动读取配置器的信息。只要在 TestCase 的属性里设置了 fixtures,所指定的配置器文件就会被自动读取。在 LIST 14.23 中,我们读取了名为 polls.json 的配置器,使得测试用例可以使用配置器内记录的数据。
LIST 14.23 使用配置器的 TestCase
from django.test import TestCase
from polls.models import Poll
class PollsTestCase(TestCase):
fixtures = ['polls.json']
def setUp(self):
# 一般的测试定义
def testPoll(self):
# 使用配置器的测试
◉ 用dumpdata 生成配置器文件
很多时候,纯手动编写配置器文件并不现实(比如需要大量档案或者多种不同情况的数据时)。Django 有生成 fixutre 文件的辅助功能,那就是 dumpdata
命令。dumpdata
命令可以用数据库里的数据生成配置器文件(LIST 14.24)。
LIST 14.24 执行 dumpdata
$ python manage.py dumpdata polls > polls.json
执行完毕后生成了名为 polls.json 的配置器文件。内容与之前用 loaddata 加载的内容相同。
14.3.2 几种不便使用默认配置器的情况
配置器用起来很方便,但编写测试代码时我们也会遇到配置器碍事的情况,下面就举几个例子来说明一下。
◉ 日期字段会变成特定日期
我们再来看看上面提到的 polls.json,会发现 pub_data 列的日期是固定的(LIST 14.25)。
LIST 14.25 polls.json
[
{
"model": "polls.poll",
"pk": 1,
"fields": {
"question_text": "Why not use the fixture",
"pub_date": "2011-11-01 00:00:00Z"
}
},
{
"model": "polls.poll",
"pk": 2,
"fields": {
"question_text": "Why not use the fixture2",
"pub_date": "2011-11-02 00:00:00Z"
}
}
]
比如我们的测试代码希望用到包含“今天的日期”的 Poll 模型时,就需要将配置器文件中的 pub_data 修改成“今天的日期”才行。在这种需要用到“今天的日期”或类似数据的情况下,就不适合用配置器来准备测试数据。
◉ 配置器不容易维护
举个例子,我们给 Poll 模型添加新的属性(列)时,需要对 polls.json 的所有数据添加属性。这将是一个非常费劲的工作。特别是在开发新的应用时,模型的修改往往很频繁,要想维护配置器文件使之能跟上模型的变化,成本通常不低。
可见,配置器在事先准备数据方面显得十分便捷,但它准备的数据缺乏灵活性和可维护性。
接下来我们将学习一个能解决上述这些问题的工具——factory_boy。
14.3.3 如何使用 factory_boy
factory_boy2 可以帮助我们生成更加灵活、更易维护的配置器。从名字可以看出来,它是 Ruby 经典工具 factory_girl3 的 Python 版。
2https://github.com/rbarrois/factory_boy
3https://github.com/thoughtbot/factory_girl
Django 的配置器是基于文件来准备数据,而 factory_boy 是生成对应于数据模型的 Factory 类,因此它能够动态且灵活地为我们准备测试数据。这种不以文件形式提供配置器,而是提供能带来同样效果的数据模型的工具称为 fixture replacement。除支持 Django 外,factory_boy 还支持其他 O/R 映射工具。
这里我们用 pip 安装 factory_boy(LIST 14.26)。
LIST 14.26 安装 factory_boy
$ pip install factory-boy
本书使用了 factory_boy 的 2.4.1 版本。
◉ Factory
Factory 对于将目标对象实例化时所需的一系列属性进行了定义。在命名 Factory 类时,需要让使用者能够通过我们赋予的名称推测出其目标对象的类(LIST 14.27)。
LIST 14.27 PollFactory 类
import factory
from django.utils import timezone
from polls.models import Poll
class PollFactory(factory.django.DjangoModelFactory):
question_text = 'factory question'
pub_date = timezone.now()
class Meta:
model = Poll
◉ 构建策略
factory_boy 支持多种不同的构建策略。构建策略是确定数据模型生成状态的方法。在测试用例中,如果需要使用不同状态的目标对象,就会用到它们(LIST 14.28)。
LIST 14.28 支持的构建策略
# 返回没有save 的Poll 实例
poll = PollFactory.build()
# 返回已save 的Poll 实例
poll = PollFactory.create()
# 以字典形式返回生成Poll 实例时所用的属性
attributes = PollFactory.attributes()
# 返回所有属性桩代码化后的对象
stub = PollFactory.stub()
直接实例化 Factory 类时,其运行效果与 create()
相同(LIST 14.29)。
LIST 14.29 Factory 类的实例化
poll = PollFactory() #=> 与PollFactory.create() 效果相同
生成实例时可以通过传递关键字传值参数的方式覆盖原有属性值,而无需关心使用的构建策略(LIST 14.30)。
LIST 14.30 覆盖属性
poll = PollFactory.create(question_text='changed question')
◉ 延迟计算属性
虽然我们在定义 Factory 的时候已经添加了静态的属性,但有些属性(关联的数据模型、需要动态生成的属性等)需要在每次生成实例时进行设置。准备这类属性就需要用到 LazyAttribute 了(LIST 14.31)。
LIST 14.31 LazyAttribute 的示例
class UserFactory(factory.django.DjangoModelFactory):
first_name = 'Beproud'
last_name = 'Taro'
email = factory.LazyAttribute(lambda a:
'{0}.{1}@example.com'.format(a.first_name,
a.last_name).lower())
class Meta:
model = User
UserFactory().email #=> beproud.taro@example.com
◉ 序列
根据特定格式需要用到连续的属性值时,可以使用序列(Sequence)(LIST 14.32)。
LIST 14.32 Sequence
class UserFactory(factory.django.DjangoModelFactory):
email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n))
class Meta:
model = User
UserFactory().email # => 'person0@example.com'
UserFactory().email # => 'person1@example.com'
基本的内容到这里就了解清楚了。接下来我们看看如何用 factory_boy 解决 Django 中的配置器不好用的问题。
14.3.4 消除“不便使用默认配置器的情况”
◉ 解决“日期字段会变成特定日期”的问题
按照 LIST 14.33 所示定义 PollFactory 类,然后为测试用例的属性配上合适的日期,这样我们就能得到想要的数据模型了。
LIST 14.33 设置灵活的日期字段
class PollFactory(factory.django.DjangoModelFactory):
question_text = 'factory question'
# 配上与测试匹配的日期
pub_date = timezone.now()
class Meta:
madel = Poll
# 还可以在生成实例时设置
poll = PollFactory.create(pub_date=timezone.now())
◉ 解决“配置器不容易维护”的问题
配置器不易维护的问题怎么解决呢? Django 的配置器在给模型添加列时,需要对配置器的所有数据进行修改,但如果使用 factory_boy,则只需要给 Factory 类添加列即可。
只需进行下述修改,所有通过 PollFactory 生成的 Poll 模型中就都添加了 user_name 列(LIST 14.34)。
LIST 14.34 添加列
class PollFactory(factory.django.DjangoModelFactory):
question_text = 'factory question'
# 添加的列
user_name = 'factory san'
pub_date = timezone.now()
class Meta:
model = Poll
通过上面的内容可以看出,Django 的配置器对于简单的测试、事先准备数据等用途而言十分便捷,但要想在实际开发中持续地实施测试,还是建议使用更加灵活且易于维护的 factory_boy。
14.4 Django Debug Toolbar
接下来了解一下在用 Django 开发应用的过程中辅助调试的 Django Debug Toolbar。
Django Debug Toolbar 的简介
用 Django 进行开发时,各位是否想过“显示某个页面的过程中总共发送了哪些 SQL”呢?其实只要使用 Django Debug Toolbar4,我们就可以在开发 Web 页面的同时查看“发送了哪些 SQL”“处理花费了多少时间”等信息了。
4https://github.com/django-debug-toolbar/django-debug-toolbar
除了 SQL 之外,使用 Django Debug Toolbar 还可以查看许多在开发过程中想要查看的信息。下面是除 SQL 之外可以查看的项目。
Versions | Django 的版本 |
---|---|
Time | 显示视图所用的时间 |
Settings | settings.py 中描述的设置值 |
Headers | HTTP 请求 / 响应的头信息 |
Request | GET/POST/Cookie/Session 的变量信息 |
StaticFiles | 静态文件的相关信息 |
Templates | 模板的相关信息 |
Cache | 缓存框架的访问信息 |
Signals | Django 的内置 signal 信息 |
Logging | 被记录的日志信息 |
这里我们来了解一下几个可查看的信息。另外,本书使用了 Django Debug Toolbar 的 1.2.2 版本。现在来看看它的具体使用方法,先从安装开始。
◉ 安装
$ pip install django-debug-toolbar
安装完成之后在 settings.py 中添加如 LIST 14.35 所示的内容。
LIST 14.35 INSTALLED_APPS 内添加的内容
INSTALLED_APPS = (
.
.
'django.contrib.staticfiles' # 在'debug_toolbar' 之前设置
.
.
'debug_toolbar' # <- 添加
)
这里的 'django.contrib.staticfiles'
要设置在 'debug_toolbar'
之前。另外,Django 1.7 默认设置了 'django.contrib.staticfiles'
。
◉ 查看 Debug Toolbar
设置完 settings.py 之后重启 Django,随后浏览器上将显示图 14.4 所示的调试用工具栏。
图 14.4 Debug Toolbar
不需要显示 Debug Toolbar 时,点击右上角的 Hide 即可隐藏工具栏。
◉ SQL
如图 14.5 所示,我们能够查看当前视图内发送的 SQL 语句。除此之外还有 SQL 的 Explain 的执行结果以及发送 SQL 前的 Stacktrace 等信息。
图 14.5 SQL
◉ Versions
Versions 会显示当前视图使用的模块的版本信息(图 14.6)。
图 14.6 Versions
◉ 时间
如图 14.7 所示,“时间”能查看显示当前视图所消耗的时间。我们来简单看一下其中的主要项目。
User CPU time:从接到请求到渲染完页面的时间
System CPU time:从渲染完毕到服务器生成 HTTP 响应并返回给客户端所用的时间
Total CPU time:User CPU time + System CPU time 的总和,即从接到请求到返回响应所用的时间
图 14.7 时间
◉ Settings
Settings 可以列表查看 Django 的 settings.py 内设置的值(常量)。这里能查看的只有常量,因此不会显示 settings.py 中定义的函数等内容(图 14.8)。
图 14.8 Settings
◉ Headers
Headers 里可以查看 HTTP 请求 / 响应的头信息(图 14.9)。
图 14.9 Headers
◉ Request
Request 可以查看 HTTP 请求发送的 GET/POST/Cookie 的内容、Session 的内容,以及被调用的视图的信息(图 14.10)。
图 14.10 Request
◉ Static files
Static files 可以查看通过 django.contrib.staticfiles 获取的静态文件列表,以及 staticfiles 获取文件时的对象目录列表(图 14.11)。
图 14.11 StaticFiles
◉ Templates
Templates 里可以查看当前视图使用的模板(包含继承关系)以及传给模板的 Context、Context processor 的列表(图 14.12)。
图 14.12 Templates
◉ Cache
Cache 可以查看当前视图使用的缓存后端的列表(图 14.13)。
图 14.13 Cache
◉ Logging
假设 polls 应用的视图中输出了如 LIST 14.36 所示的调试日志,那么浏览器上就会显示如图 14.14 所示的日志。
LIST 14.36 Logging 的设置示例
import logging
logger = logging.getLogger(__name__) # __name__ 处代入模块路径'polls.views'
def detail(request, poll_id):
poll = get_object_or_404(Poll, id=poll_id)
logger.info('Poll: %s', poll.id)
return TemplateResponse(request, 'polls/detail.html',
{'poll': poll})
图 14.14 Logging
◉ Intercept redirects
勾选工具栏下方的“Intercept redirects”选项,网页的重定向将被拦截。这项功能可以帮助我们确认重定向的时间点、查看返回重定向响应的视图内都执行了哪些处理。
图 14.15 Intercept redirects
◉ 自定义显示
工具栏的显示可以根据开发应用的情况与阶段进行自定义。自定义的方法很简单,只要在 settings.py 中添加 DEBUG_TOOLBAR_PANELS 项,按照自己的需要对其中内容进行重排或改为注释,即可修改工具栏的显示项目(LIST 14.37)。
LIST 14.37 DEBUG_TOOLBAR_PANELS 的设置
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.versions.VersionsPanel',
'debug_toolbar.panels.timer.TimerPanel',
'debug_toolbar.panels.settings.SettingsPanel',
'debug_toolbar.panels.headers.HeadersPanel',
'debug_toolbar.panels.request.RequestPanel',
'debug_toolbar.panels.sql.SQLPanel',
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
'debug_toolbar.panels.templates.TemplatesPanel',
'debug_toolbar.panels.cache.CachePanel',
'debug_toolbar.panels.signals.SignalsPanel',
'debug_toolbar.panels.logging.LoggingPanel',
'debug_toolbar.panels.redirects.RedirectsPanel',
]
14.5 小结
本章我们聚焦基于 Python 开发的 Web 应用框架 Django,对其架构进行了学习,并了解了一些有助于开发的库。
如果要开发的 Web 应用很简单,那么默认设置的 Django 也就够我们用了。不过,一旦我们希望让开发或运营的 Web 应用更加健壮和安全(业务方面尤为多见),“这里做的还不够”“那里再改善一点就好了”之类的需求也就多了起来。正如本章所讲的那样,很多时候,可重复利用的 Django 应用和 Python 库可以帮助我们解决或者一定程度上改善这些问题。
当然,实际开发中方便好用的库远不止本章介绍的这些。下面我们再简单了解几个。
- django-extensions5
管理命令、数据库字段等的结合体,能便捷地为Django框架做功能扩展。
- sorl-thumbnail6
Django应用,通过专用模板标签和管理命令简化缩略图的生成与显示。
- bpmappers7
简化模块映射的Python库。第15章将对其作详细介绍。
5https://github.com/django-extensions/django-extensions