编写你的第一个Django应用程序,第2部分

本教程上接教程1 我们将设置数据库,创建你的第一个模型,并快速介绍Django自动生成的管理后台站点。

数据库设置

现在,打开mysite/settings.py 它是一个用模块级别变量来表示 Django 配置的普通 Python 模块。

默认情况下,该配置使用SQLite。 如果你是数据库初学者,或者你只是想要试用一下Django,它是最简单的选择。 SQLite包含在Python中,所以你不需要另外安装其他任何东西来支持你的数据库。 然而,当你开始第一个真正的项目时,可能希望使用更可扩展的数据库(如PostgreSQL),以避免后面令人头疼的数据库切换。

如果你希望使用其他数据库,请安装相应的数据库绑定,并更改DATABASES 'default'条目中以下的键以匹配你的数据库连接设置:

  • ENGINE'django.db.backends.sqlite3''django.db.backends.postgresql''django.db.backends.mysql' 或者'django.db.backends.oracle' 其它的后台也是可以的
  • NAME – 数据库的名称。 如果你使用SQLite,数据库将是你计算机上的一个文件;在这种情况下,NAME应该是该文件的完整绝对路径,包括文件名。 默认值是os.path.join(BASE_DIR, 'db.sqlite3'),它将文件保存在你项目的目录中。

如果你不使用SQLite作为数据库,则必须添加其他设置,如USERPASSWORDHOST 更多的细节,请参见数据库的参考文档。

对于SQLite以外的数据库

如果使用SQLite之外的数据库,请确保你已经创建了一个数据库。 可以在你的数据库的交互式提示命令下,使用“CREATE DATABASE database_name;”创建它。

还要确保mysite/settings.py中提供的数据库用户具有“创建数据库”权限。 这允许自动创建一个测试数据库,这将在稍后的教程中需要。

如果你使用SQLite,你不需要事先创建任何东西 —— 数据库文件将会在需要的时候自动创建。

当你编辑mysite/settings.py时,请设置TIME_ZONE为你自己的时区。

另外,请注意文件顶端的INSTALLED_APPS设置。 它保存这个Django实例中激活的所有的Django应用的名字。 应用可以在多个项目中使用,而且你可以将这些应用打包和分发给其他人在他们的项目中使用。

默认情况下,INSTALLED_APPS包含下面的应用,它们都是Django 与生俱来的:

这些应用,默认包含在Django中,以方便通用场合下使用。

其中一些应用使用至少一个数据库表,因此我们需要在数据库中创建表,然后才能使用它们。 要做到这一点,请运行以下命令:

$ python manage.py migrate

migrate查看INSTALLED_APPS设置并根据mysite/settings.py文件中的数据库设置创建任何必要的数据库表,数据库的迁移还会跟踪应用的变化(我们稍后会讲到)。 你会看到对每次迁移有一条信息。 如果你感兴趣,可以运行你数据库的命令行工具来查看Django创建了哪些表: \dt (PostgreSQL), SHOW TABLES; (MySQL), .schema (SQLite), or SELECT TABLE_NAME FROM USER_TABLES; (Oracle)

对于极简主义者来说

就像我们上面说到的,以上包含的默认应用用于常见的场景,但并不是每个人都需要它们。 如果你不需要它们中的任何一个或所有应用,可以在运行migrate之前从INSTALLED_APPS中自由地注释或删除相应的行。 migrate 命令将只为INSTALLED_APPS中的应用运行数据库的迁移。

创建模型

现在,我们将使用额外的元数据来定义你的模型 — 主要是数据库的布局。

理念

模型指出了数据的唯一、明确的真实来源。 它包含了正在存储的数据的基本字段和行为。 Django遵循DRY(Do not Repeat Yourself) 原则 这个原则的目标是在一个地方定义你的数据模型,并自动从它获得需要的信息。

迁移工具也符合以上哲学 —— 这不同于Ruby On Rails中的迁移;例如,迁移完全依照于你的模型文件且本质上只是一个历史记录,Django通过这个历史记录更新你的数据库模式使它与你现在的模型文件保持一致。

在这个简单的投票应用中,我们将创建两个模型: QuestionChoice Question对象具有一个question_text(问题)属性和一个publish_date(发布时间)属性。 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 实例的名字(例如pub_datequestion_text)就是字段的名字,并且是机器可读的格式。 你将在Python代码中使用到它的值,并且你的数据库将把它用作表的列名。

你可以使用Field的第一个可选的参数来指定一个人类可读的名字,这是可选的。 这用于Django的几个内省部分,它可以作为文档。 如果没有提供这个参数,Django将使用那个机器可读的名字(实例名)。 在这个例子中,我们只为Question.pub_date定义一个人类可读的名字。 对于这个模型中其他的字段,机器可读的名字(实例名)足以充分地表达出它的含义。

某些Field 类具有必选的参数。 例如,CharField要求你给它一个max_length 这个参数不仅用于数据库模式,而且数据验证中也会用到,我们稍后会看到。

一个 Field 可以有很多不同的可选参数; 比如在这里, 我们给votes 设置默认值(default)为0。

最后,注意我们使用ForeignKey定义了一个关联。 它告诉Django每个Choice都只关联一个Question Django支持所有常见的数据库关系:多对一,多对多和一一对应。

激活模型

上面那段简短的模型代码给了Django很多信息。 有了这些代码,Django就能够自动完成以下两个功能:

  • 为该应用创建数据库表(CREATE TABLE 语句)。
  • Question对象和Choice对象创建一个访问数据库的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储存模型(即你的数据库模式)的变化的方式 — 它们只是磁盘上的文件。 如果你喜欢,可以阅读你的新模型的迁移;它是文件polls/migrations/0001_initial.py 不用担心,Django不要求你在每次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。
  • 表名是自动生成的,由app的名字(polls)和模型名字的小写字母组合而成 —— questionchoice (你可以重写这个行为。)
  • 主键(id)是自动添加的。 (你也可以重写这个行为。)
  • 按照惯例,Django将"_id"附加到外键字段名称。 (是的,你也可以重写这个。)
  • 外键关系由FOREIGN KEY约束显式声明。 不要担心DEFERRABLE部分;这只是告诉PostgreSQL不执行外键直到事务结束。
  • 这些SQL语句是针对你所使用的数据库定制的,所以会为你自动处理某些数据库所特有的字段例如auto_increment (MySQL)、 serial (PostgreSQL)或integer primary key autoincrement (SQLite) 。 引用字段名称也是如此,例如使用双引号或单引号。
  • sqlmigrate命令并不会在你的数据库上真正运行迁移文件 —— 它只是把Django 认为需要的SQL打印在屏幕上以让你能够看到。 这对于检查Django将要进行的数据库操作或者你的数据库管理员需要这些SQL脚本是非常有用的。

如果有兴趣,还可以运行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的交互式shell,玩转这些Django提供给你的API。 使用如下命令来调用Python shell:

$ python manage.py shell

我们使用上述命令而不是简单地键入“python”进入python环境,是因为manage.py 设置了DJANGO_SETTINGS_MODULE 环境变量,该环境变量告诉Django导入mysite/settings.py文件的路径。

绕开 manage.py

如果你不想使用manage.py,也没问题。 只要设置DJANGO_SETTINGS_MODULE 环境变量为 mysite.settings,启动一个普通的Python shell,然后建立Django:

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

如果以上命令引发了一个AttributeError,可能是你使用了一个和本教程不匹配的Django版本。 你需要换一个合适的Django版本

你必须在与manage.py同一级目录中运行python,或确保该目录在Python Path中,以便import mysite能运行。

所有这些信息,请参见django-admin 文案

一旦你进入shell,请浏览database API

>>> from polls.models import Question, Choice   # Import the model classes we just wrote.(导入我们刚刚编写的那些模型类)

# No questions are in the system yet.(系统中还没有question。)
>>> Question.objects.all()
<QuerySet []>

# Create a new Question.(创建一个新的Question。)
# 默认的settings文件中启用对时区的支持,所以
# Django要求pub_date为一个带有tzinfo的datetime。 使用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"而不是"1",取决于
# 你使用的数据库。 这不是什么事;它只是表示你的
# 数据库后端返回的是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。
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>

先等一下。 <Question object> 完全是这个对象无意义的表示。 让我们来修复这个问题,编辑Question模型(在polls/models.py文件中)并添加一个__str__()方法给QuestionChoice

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__()方法很重要,不仅会使你自己在使用交互式命令行时看得更加方便,而且会在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 交互式shell:

>>> 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 创建几个 Choices. 创建一个新的
# Choice 对象, 使用 INSERT 语句将选项添加到可用选项的集合并返回新的“Choice”对象。
# (合并至上句) Django 创建
# 一个集合来控制通过外键关联的“另一端”。
# (例如,一个“问题”的“选项”)
>>> q = Question.objects.get(pk=1)

# 显示任何有关的选项 ——目前还没有.
>>> q.choice_set.all()
<QuerySet []>

# 创建三个choices。
>>> 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)

# Choice 对象通过 API 访问与之关联的 Question 对象.
>>> c.question
<Question: What's up?>

# 反之亦然:Question对象可以访问Choice对象。
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# AIP 根据需要自动创建关系。
# 可以使用双下划线分隔关系。
# 它的工作机制是尽可能深的创建关系,而没有限制。
# 通过 pub_date 查找今年内创建的问题的所有选项
# (再次使用了之前创建的 '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中使用双下划线来执行字段查询的信息,请查看 Field lookups 更多关于数据库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

现在,打开一个浏览器访问你本地域名中的 “/admin/” — 例如http://127.0.0.1:8000/admin/ 你应该会看到管理后台站点的登录界面:

Django admin login screen

由于默认情况下打开翻译,因此登录屏幕可能会以你自己的语言显示,具体取决于浏览器的设置和Django是否有该语言的翻译。

进入管理后台站点

用你在前面创建的超级用户账号来登录这个站点。 你应该会看到Django管理后台站点的首页面:

Django admin index page

你将看到几类可编辑的内容:groups和users。 它们是由django.contrib.auth提供的,这个认证框架集成在Django中。

让投票应用在管理后台站点中可编辑

但我们的投票应用在哪儿? 它没有显示在管理后台站点的首页面上。

只需要做一件事:我们需要告诉管理站点Question 对象要有一个管理界面。 要做这件事,需要打开polls/admin.py文件,把它编辑成这样:

polls/admin.py
from django.contrib import admin

from .models import Question

admin.site.register(Question)

探索管理后台的功能

现在,我们已经在管理站点中注册了Question对象,Django应该把它显示在管理站点的首页面上:

Django admin index page, now with polls displayed

点击“Questions”。 现在,你会进入Question的“变更列表”。 这个界面显示了数据库中的所有question,你可以选择一个来更改它。 我们之前创建的“What's up?”问题

Polls change list page

单击“What’s up?”来编辑它:

Editing form for question object

注意事项:

  • 这个表单是根据Question模型文件自动生成的。
  • 模型中不同类型的字段(DateTimeFieldCharField)会对应相应的HTML输入控件。 每一种类型的字段,Django管理站点都知道如何显示它们。
  • 每个DateTimeField字段都会有个方便的JavaScript快捷方式。 Date有个“Today”的快捷方式和一个弹出式日历,time栏有个“Now”的快捷方式和一个列出常用时间选项的弹出式窗口。

界面的底部有几个按钮:

  • Save — 保存更改,并返回当前类型对象的变更列表页面。
  • Save and continue editing — 保存更改并且重新载入当前对象的管理界面。
  • Save and add another — 保存更改并且载入一个当前类型对象的新的、空白的表单。
  • Delete — 显示一个删除确认界面。

如果“Date published”的值与你在Tutorial 1中创建问题的时间不符,可能意味着你忘记为TIME_ZONE设置正确的值。 修改它,然后重新载入这个界面,检查一下正确的值是否出现。

通过“Today”和“Now”这两个快捷方式来更改“Date published”字段。 然后点击“保存并继续编辑”,然后点击右上角的“历史记录”。 你将看到一个页面,列出了通过Django管理界面对此对象所做的全部更改的清单,包含有时间戳和修改人的姓名等信息:

History page for question object

当你熟悉模型API并熟悉管理后台站点时,请阅读本教程的第三部分学习如何添加更多的视图到我们的投票应用中。