编写您的第一个Django应用, 第2部分

前面您看到 教程 1 。在本节,我们将准备数据库,创建您的 第一个模型,并对 Django 自动生成管理系统做一个快速的介绍。

准备数据库

现在,请打开 mysite/settings.py 。这是一个普通的 Python 模块,提供了 Django 配置 所需的模块级变量。

默认情况下,配置文件使用 SQLite 作为数据库引擎。如果您是第一次接触数据库,或您只是对 Django 感兴趣,希望通过体验获得进一步的了解,这是最简单的选择。 SQLite 已包含在 Python 的标准库中, 所以您不需要安装任何额外的东西来支持数据库。当您开始做真实的项目时,您需要使用更具备扩展能力的 如 PostgreSQL ,来避免未来头疼的数据库切换问题。

如果您希望使用其他数据库,安装合适的 数据库绑定 并改变 DATABASES 'default' 一项来配置您的数据库连接方式:

  • ENGINE – 您可以选择 'django.db.backends.sqlite3', 'django.db.backends.postgresql', 'django.db.backends.mysql', 或者 'django.db.backends.oracle'. 其他的 backend 请看 also available.
  • NAME – 数据库的名称。 如果您使用 SQLite ,数据库就是您电脑上的一个文件 在那种情况下, NAME 就是该文件的绝对路径。这里提供了一个默认值 os.path.join(BASE_DIR, 'db.sqlite3') ,会在您的项目目录中创建文件。

如果您没有使用 SQLite 作为数据库,一些额外的配置如 USERPASSWORD 、 以及 HOST 需要您填写。请查看关于 DATABASES 的文档获取进一步的信息。

使用 SQLite 以外的数据库

如果您正在使用 SQLite 以外的数据库,请确认您在此刻已经创建了对应的库,请在您数据库系统的 交互提示中使用 “CREATE DATABASE database_name;” 语句进行创建。

同样请确保 mysite/settings.py 中填写的数据库用户具备“创建数据库”的权限。这样 在稍后教程章节中可以进行 test database 的自动创建。

如果您正在使用 SQLite ,您不需要提前创建任何东西,数据库文件在需要时会自行创建。

当您正在编辑 mysite/settings.py 时,请将 TIME_ZONE 设置为您当前的时区。

同样地,注意这个文件顶部的 INSTALLED_APPS 配置项。这里将激活所有您填写的 Django 应用。 每个应用可以被用在多个项目中,您可以将应用打包并发布出去。

默认情况下, INSTALLED_APPS 包含下列应用,都是由 Django 提供的:

因为这些应用在大多数场合下都要被用到,所以默认就将它们包含进来了。

这些应用中的一部分需要配合至少一张数据库表才能使用,所以我们在用之前先创建好表。使用如下命令:

$ python manage.py migrate

migrate 命令会根据 INSTALLED_APPS 的设置,并结合 mysite/settings.py 文件的数据库配置,以及应用自身所提供的数据库迁移脚本(稍后介绍) 进行数据库表的初始化。初始化过程中您会看到每个迁移过程的消息。如果您感兴趣,可以在数据库客户端的 命令行中输入 \dt (PostgreSQL) 、 SHOW TABLES; (MySQL) 、 .schema (SQLite) 、或者 SELECT TABLE_NAME FROM USER_TABLES; (Oracle) 来查看 Django 创建的表。

写给极简主义者们

正如我们上面提到的,我们默认会提供一份大多数场合下都要用上的应用清单,但也并不是任何人 都需要用到它们。如果您不需要,在运行 migrate 前直接注释或删除 INSTALLED_APPS 中的对应行。 migrate 命令仅会对 INSTALLED_APPS 中生效的应用执行迁移。

创建模型

现在,我们即将定义您的模型,这本质上就是您的数据库层,再加上一些额外的元数据。

哲学

一个模型是您数据最简洁、明确的事实来源。它包含了您正在存储数据的核心字段和行为。 Django 遵循 DRY Principle 。 它的目标是仅在一处定义您的数据模型,并自动推导所有上下文所需 的信息。

这里也提供了迁移功能——不像 Ruby On Rails 框架, Django 迁移脚本的所有信息都由您的模型文件 推导而出,这本质上是一个能让 Django 将您数据库模式更新至能匹配当前模型版本的历史文件。

在我们的投票小应用中,我们创建了两个模型: QuestionChoice 。一个 Question 拥有有一个问题内容和一个发布日期。 一个 Choice 有两个字段:选项的文本内容和投票结果。每个 Choice 都关联到一个 Question 上。

这些概念都通过Python的类进行表述。编辑 polls/models.py 文件,定义如下模型:

polls/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

上述代码非常直观。每个模型都定义为 django.db.models.Model 的一个子类。每个模型 都有一些类变量,每个都表示为模型中的一个数据库字段。

每个字段都表示为一个 Field 类的实例 —— 例如,用于表示字符的 CharField 和用于表示时间的 DateTimeField 。 这告诉了 Django 应为字段存储什么类型的数据。

每个 Field 实例的名字就是字段的名字(例如 question_textpub_date ),并使用对机器友好的方式存储。您会在Python代码中使用这个值,在数据库中作为 列名使用。

您可以在实例化 Field 时设置首位的参数来指定一个对用户友好的名字。 这个名字会在 Django 的好几个内部环节被用到,它同时也起到了文档提示的作用。如果没有提供这个值, Django 会使用对机器友好的那个名字。在这个例子中,我们只为 Question.pub_date 字段设置了 对用户友好的值。至于模型中所有其他字段,使用对机器友好的名称就足够表示其含义了。

一些 Field 类在实例化时有必填的参数。例如, CharField 需要您提供一个 max_length 。一个 这个值不仅用在数据库模式中,在验证时也会被用到,我们稍后就会看到。

一个 Field 也拥有几个可选参数。在这个例子中,我们设置 votesdefault 为0。

最后,请注意我们定义了一个关系,使用 ForeignKey ,告诉 Django 每个 Choice 都有一个 Question 与之对应。 Django 支持所有的常见数据库关系: 多对一、多对多以及一对一。

激活模型

其实这么一小块关于模型定义的代码告诉了 Django 很多信息。通过这些信息, Django会帮您完成:

  • 为这个应用创建数据库模式(通过 CREATE TABLE 语句)
  • QuestionChoice 对象创建Python语言层面的数据库访问API

不过先告诉项目我们的 polls 应用已经可以使用了。

哲学

Django 的应用是“可插拔”的:您可以一个应用安装到不同的项目中,同样也可以将这些应用分发出去 因为它们并没有和一个已安装的 Django 紧紧绑在一起。

为了把应用加到我们项目中,需要在 INSTALLED_APPS 设置中加上对应配置类的引用。 PollsConfig 类定义在 polls/apps.py 文件中,所以它的模块路径是 'polls.apps.PollsConfig' 。编辑 mysite/settings.py 文件然后把这串路径 加到 INSTALLED_APPS 设置中。如下:

mysite/settings.py
INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

现在 Django 已经把 polls 应用加进来了。我们接着运行下面的命令:

$ python manage.py makemigrations polls

您会看到类似下面的输出:

Migrations for 'polls':
  polls/migrations/0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

通过运行 makemigrations ,告诉 Django 您已经对模型做了一些修改(在这个例子中, 您增加了一个新模型),希望 Django 把修改记录到迁移脚本中。

迁移脚本是 Django 存储对模型(即数据库模式)修改的地方,它们仅仅是硬盘上的几个文件。您随时 可以查阅您新模型的迁移脚本。这个文件在 polls/migrations/0001_initial.py 。您不必在 每次 Django 生成一个迁移脚本时都去看一下,仅仅在您希望对 Django 记录的修改做人为调整时才需要。

接着,有一个命令可以帮您自动把迁移脚本应用到数据库模式上——它叫 migrate ,我们 一会儿就要介绍它——在此之前,我们先来看看迁移脚本会运行哪些 SQL 语句。通过 sqlmigrate 命令并指定迁移脚本的名称,就会返回它们执行的 SQL :

$ python manage.py sqlmigrate polls 0001

您会看到类似下面的输出:

BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;

COMMIT;

请注意:

  • 输出的具体信息取决于您所使用的数据库。上述输出是通过 PostgreSQL 产生的。
  • 表名是根据应用名 ( polls )加模型名称( question )的小写形式自动生成的 (您可以修改这个行为)。
  • 主键(ID)是自动添加上去的(当然您也可以修改这个行为)。
  • 方便起见, Django 在外键的字段名后面自动加上了 "_id" 的后缀(是的,您也可以修改这个行为)。
  • 外键关系是通过 FOREIGN KEY 约束建立的。不用担心 DEFERRABLE 那部分;只是告诉 PostgreSQL 仅在事务结束时才建立外键。
  • 这些都是根据您所使用的数据库定制的,会自动帮您决定使用何种具体的字段如 auto_increment ( MySQL )、 serial ( PostgreSQL ),或者 integer primary key autoincrement ( SQLite )。同样地,也会帮您自动处理好字段名称使用单引号还是双引号。
  • sqlmigrate 命令并不会真的对您的数据库执行迁移——它只是将 Django 推导出的 SQL 语句打印到屏幕上。有助您了解 Django 会做哪些事情,也有助于告诉您的数据库管理员会发生什么变化。

如果您感兴趣,同样可以运行下 python manage.py check ;它会帮您检查您 项目中可能出现的错误,却不需要执行迁移或创建数据库。

现在,再次执行 migrate 以便在数据库中真正地创建那些模型。

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK

migrate 命令会对数据库应用所有那些未被执行过的迁移脚本( Django 通过一张特殊的表 django_migrations 来追踪哪些迁移脚本已经被执行过了)——本质上,就是将您对模型所做的任何 变化都映射到数据库模式中去。

迁移脚本非常强大,它允许您在项目开发过程中随时修改模型,而无须删除数据库或表然后再新建一个 ——它专注于在保证不丢失数据的同时升级数据库。我们在本教程的稍候章节会更深入地介绍这部分内容。 当下,您仅需记住在变更模型时做三件事:

把这些命令分开执行然后应用迁移脚本的原因是您需要将这些迁移脚本提交到版本管理系统上并随着应用 一起发布;它们不仅是简化您的开发过程,对其他开发者和生产环境也有作用。

阅读 django-admin 文档 来获取 manage.py 工具能做的 事情的所有信息。

与API共舞

现在,让我们跳进 Python 交互运行环境,玩转 Django 提供的 API 。使用下面命令进入 Python 交互式环境:

$ python manage.py shell

我们之所以使用这种方式而不是直接启动 python ,因为 manage.py 会帮我们设置 DJANGO_SETTINGS_MODULE 环境变量,告诉 Django 引用到 mysite/settings.py 文件的 Python 导入路径。

不通过 manage.py 启动

如果您不想通过 manage.py 完成这件事,没问题,只要将 DJANGO_SETTINGS_MODULE 环境变量的值设置为 mysite.settings ,然后启动一个“纯粹”的 Python 交互环境,并启动 Django :

>>> import django
>>> django.setup()

如果此处遇到 AttributeError 异常,您很有可能使用了一个和本教程不匹配 的 Django 版本,您最好切到早一些版本的教程,或使用更新的 Django 版本。

您必须从 manage.py 文件所在的文件夹发起 python 命令,或者确保该文件夹 在您的 Python 导入路径中, import mysite 可以正常工作。

如果想获取以上更进一步的信息,请阅读 django-admin 文档

当您处于交互环境中时,可以通过 数据库 API 一节获取当下能做的事情最丰富的信息

>>> from polls.models import Question, Choice   # 导入我们之前定义的模型

# 系统目前没有创建任何“问题”
>>> Question.objects.all()
<QuerySet []>

# 创建一个新的“问题”
# 时区功能在默认配置中就被弃用,因此 pub_date 字段希望填入一个带有 tzinfo 信息的
# 时间值。使用 timezone.now() 而非 datetime.datetime.now() 就是正确做法
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# 通过显式调用 save() 将对象保存到数据库中
>>> q.save()

# 现在这个“问题”就有一个 ID 了,注意这个值有可能是 `1L` ,这一行为取决于您使用的数据库。
# 这并不是什么大事;只是将数据库引擎将整数值返回为 Python 的长整数对象而已
>>> q.id
1

# 通过 Python 对象属性访问模型字段的值
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# 通过改变对象属性的值来改变字段值,随后调用 save()
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() 会打印所有存在数据库中的“问题”
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>

先别急, 在交互环境中打印的 <Question: Question object> 这种信息着实不是对“问题”具备可读性 的表述。我们先编辑 Question 模型来解决这个问题(在 polls/models.py 文件中),给 QuestionChoice 都添加一个 __str__() 方法:

polls/models.py
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible  # 只有在您需要支持 Python 2 时才添加
class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

@python_2_unicode_compatible  # 只有在您需要支持 Python 2 时才添加
class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

给您的模型添加 __str__() 方法很重要,因为这不仅使我们的 ORM 对象在交互环境中变得可读了,同时也会 Django 自动生成的管理后台中用到这个显式值。

注意这些仅是 Python 标准的对象方法。我们同样可以添加自定义方法,先演示一下:

polls/models.py
import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

请注意我们添加的 import datetimefrom django.utils.import timezone , 分别引用了 Python 标准库中的 datetime 模块和 Django 的时区工具库 django.utils.timezone 。如果您不太熟悉 Python 中的时区处理,可以在 时区支持文档 中了解到更多的信息。

保存上述变更,然后通过再次运行 python manage.py shell 启动一个新的 Python 交互环境

>>> from polls.models import Question, Choice

# 确保我们的 __str__() 已经工作了
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django 提供了一个通过关键字参数驱动的非常强大的数据库查询 API
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# 获取所有今年发布的“问题”
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# 如果请求查询一个 ID 并不存在的问题,会抛出一个异常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# 通过主键查找对象是最常见的场景,因此 Django 特地为此提供了一个快捷方法
# 下面这个调用等同于 Question.objects.get(id=1)
>>> Question.objects.get(pk=1)
<Question: What's up?>

# 确保我们的自定义方法能正常工作
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# 下面给 Question 添加几个 Choice 。调用方法 create 可以创建一个新的 Choice 对象,并
# 通过执行 INSERT 语句自动帮您保存到数据库,最后返回这个新的 Choice 对象。 Django
# 创建了一个可通过 API 访问的集合来保持对另外一端的外键关系 (例如一个“问题”的选项)
>>> q = Question.objects.get(pk=1)

# 显示和当前“问题”相关联的所有“选项”——目前什么都没有
>>> q.choice_set.all()
<QuerySet []>

# 创建三个“选项”
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# “选项”对象可以通过 API 查询到与之关联的“问题”对象
>>> c.question
<Question: What's up?>

# 反之亦然:“问题”对象可以访问到所有与之关联的“选项”对象
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# API 可以根据您的需要自动解析更深层次的关系
# 使用两个下划线来分隔多个关系
# 通过该方法可以按照您的需求解析任意深度的关系层析,没有任何限制
# 下面是查找所有其“问题”发布于今年的“选项”的方式(通过我们之前就创建好的 'current_year' 变量)
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# 我们可以通过调用对象的 delete() 方法来删除它
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

想了解模型间关系的更多信息,请阅读 访问关联的对象 一节。 想了解如何在 API 中使用双下划线来执行字段的查询,请阅读 字段查询 。 想了解关于数据库 API 的所有信息,请访问文档 数据库 API

介绍 Django 后台管理系统

哲学

为您的雇员或客户创建管理站点,以添加、变更或删除内容是一件乏味的工作,因为它并不需要多少创造力。 因此, Django 将构造模型管理接口这件事彻底自动化了!

Django 是在一家报社公司诞生的,在“内容提供者”与“公共站点区域”之间有非常清晰的划分。站点管理者 使用我们系统来添加新闻故事、事件、体育赛事比分等等。而这些内容会被呈现到公共站点区域进行展示。 Django 解决了为站点管理员创建一个统一的内容编辑界面的问题。

这个管理系统并不是为站点访客准备的,而是站点管理员。

创建一个管理员用户

首先我们需要创建一个可以登录到管理站点的用户。运行下面的命令:

$ python manage.py createsuperuser

输入您期望的用户名并回车。

Username: admin

接着您会被要求输入期望的邮件地址:

Email address: admin@example.com

最后一步是输入您的密码。您会被要求输入两次,第二次作为第一次的确认。

Password: **********
Password (again): *********
Superuser created successfully.

启动开发服务器

Django 的管理站点默认就被启用。让我们启动开发服务器来看看它长什么样。

通过下面的方式启动服务器:

$ python manage.py runserver

现在,打开一个 Web 浏览器,跳转到您本地域名的 “/admin/” 地址,例如: http://127.0.0.1:8000/admin/ 。您应该可以看到如下的管理登录界面:

Django 管理登录界面

因为 翻译 功能默认就被启用,所以登录界面会显示为 您的本地语言,取决于您浏览器的设置,以及 Django 也提供了该语言的翻译。

进入管理站点

现在,尝试使用您之前创建的超级用户账号进行登录,您应该会看到下面的管理首页:

Django 管理首页

您可以看到一些可编辑的内容类型:组和用户。它们都是由 Django 的授权验证系统 django.contrib.auth 提供的。

将投票应用纳入管理系统

但是我们的投票应用去哪里了?它并没有显示在管理首页上。

只需要做一件事:我们告诉管理系统 Question 有一个管理接口即可。 打开 polls/admin.py 文件,编辑如下:

polls/admin.py
from django.contrib import admin

from .models import Question

admin.site.register(Question)

探索管理功能

现在我们已经注册了 Question , Django 已经知道它应该显示到管理首页中:

Django 管理首页,已经显示了投票应用

点击 “Questions” ,您将进入“问题”对象的修改列表页。该页会显示所有存在数据库中的问题,并允许 您选中其中一个进行编辑,您在这边可以看到我们之前创建的 “What’s up?” 问题:

投票修改列表页

点击 “What’s up?” 问题进行编辑:

“问题”的编辑表单

此处需要注意的事情:

  • 该表单是通过 Question 模型自动创建的。
  • 不同类型的模型字段( DateTimeFieldCharField)会映射为最合适的 HTML 输入组件。 Django 管理系统中的每个字段都知道如何显示它们自身。
  • 每个 DateTimeField 都提供了 JavaScript 实现的快捷按钮。 日期部分会提供一个“今天”的快捷按钮和弹出式的日历,时间部分提供了一个“现在”的快捷按钮,以及 一个便捷的弹出式的时间输入列表。

页面的底部给您提供了一些选项:

  • 保存 —— 保存修改内容并返回至当前对象的修改列表页。
  • 保存并继续编辑 —— 保存修改内容同时刷新当前页面。
  • 保存并新增 —— 保存修改内容并跳转到添加新对象的编辑页面。
  • 删除 —— 进入删除当前对象的确认页面。

如果 “Date published” 的值并不等于您之前在 教程 1 中 输入的那个,很有可能是您没有正确设置 TIME_ZONE 的值。修改它然后重新刷新页面, 您就能看到正确的值了。

通过点击“今天”和“现在”快捷按钮修改 “Date published” 的值,然后点击“保存并继续编辑”,接着点击 上部右侧的“历史”按钮,您会看到 Django 管理系统罗列出的对当前对象所作的所有修改,并会显示每次修改 的时间以及操作用户的名称。

History page for question object

如果您已经熟悉这套模型 API 的操作和管理站点的使用,请阅读 本教程的第3部分 来学习如何为我们的投票应用添加更多的视图。