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

本教程从教程 1结束的地方开始。我们将设置数据库,创建你的第一个模型,并快速了解 Django 自动生成的管理站点。

在哪里获取帮助

如果你在学习本教程时遇到问题,请前往常见问题解答的获取帮助部分。

数据库设置

现在,打开mysite/settings.py。它是一个普通的 Python 模块,其中包含表示 Django 设置的模块级变量。

默认情况下,DATABASES 配置使用 SQLite。如果你不熟悉数据库,或者只是想尝试 Django,这是最简单的选择。SQLite 包含在 Python 中,因此你无需安装任何其他内容来支持你的数据库。但是,在开始你的第一个真实项目时,你可能希望使用更具可扩展性的数据库(如 PostgreSQL),以避免以后切换数据库带来的麻烦。

如果你希望使用其他数据库,请参阅自定义和运行数据库的详细信息

在编辑mysite/settings.py时,将TIME_ZONE 设置为你的时区。

此外,请注意文件顶部的INSTALLED_APPS 设置。它包含在此 Django 实例中激活的所有 Django 应用程序的名称。应用程序可以在多个项目中使用,并且你可以将其打包并分发给其他人以供其项目使用。

默认情况下,INSTALLED_APPS 包含以下应用程序,所有这些应用程序都随 Django 一起提供

出于方便起见,这些应用程序默认情况下包含在内,适用于常见情况。

但是,其中一些应用程序至少使用一个数据库表,因此我们需要在使用它们之前在数据库中创建这些表。为此,请运行以下命令

$ python manage.py migrate
...\> py manage.py migrate

migrate 命令查看INSTALLED_APPS 设置,并根据mysite/settings.py 文件中的数据库设置和应用程序附带的数据库迁移创建任何必要的数据库表(我们将在后面介绍这些内容)。你将看到它应用每个迁移的消息。如果你感兴趣,请运行数据库的命令行客户端并输入\dt(PostgreSQL)、SHOW TABLES;(MariaDB、MySQL)、.tables(SQLite)或SELECT TABLE_NAME FROM USER_TABLES;(Oracle)以显示 Django 创建的表。

适用于极简主义者

如上所述,默认应用程序包含在内以满足常见情况,但并非所有人都需要它们。如果你不需要任何或所有这些应用程序,请在运行migrate 之前,随意注释掉或删除INSTALLED_APPS 中相应的行。 migrate 命令只会对INSTALLED_APPS 中的应用程序运行迁移。

创建模型

现在我们将定义你的模型——本质上是你的数据库布局,以及其他元数据。

理念

模型是关于你的数据信息的唯一确定来源。它包含你正在存储的数据的基本字段和行为。Django 遵循DRY 原则。目标是在一个地方定义你的数据模型,并自动从中派生出其他内容。

这包括迁移——例如,与 Ruby On Rails 不同,迁移完全来自你的模型文件,并且本质上是 Django 可以遍历的历史记录,以更新你的数据库模式以匹配你当前的模型。

在我们的投票应用中,我们将创建两个模型:QuestionChoiceQuestion 包含问题和发布日期。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 语句)。

  • 创建一个 Python 数据库访问 API,用于访问QuestionChoice 对象。

但首先,我们需要告诉我们的项目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
...\> py manage.py makemigrations polls

你应该会看到类似以下内容

Migrations for 'polls':
  polls/migrations/0001_initial.py
    + Create model Question
    + Create model Choice

通过运行makemigrations,你告诉 Django 你对模型进行了一些更改(在本例中,你创建了新的模型),并且希望这些更改作为迁移存储。

迁移是 Django 如何存储对模型(以及数据库模式)的更改的方式——它们是磁盘上的文件。如果你愿意,可以阅读新模型的迁移;它是文件polls/migrations/0001_initial.py。不用担心,你不需要每次 Django 创建迁移时都阅读它们,但它们的设计可以进行人工编辑,以防你想要手动调整 Django 如何更改内容。

有一个命令可以为你运行迁移并自动管理数据库模式——称为migrate,我们稍后会介绍——但首先,让我们看看该迁移将运行哪些 SQL。sqlmigrate 命令接收迁移名称并返回其 SQL

$ python manage.py sqlmigrate polls 0001
...\> py manage.py sqlmigrate polls 0001

你应该会看到类似以下内容(我们已对其进行了重新格式化以提高可读性)

BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL,
    "question_id" bigint NOT NULL
);
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");

COMMIT;

请注意以下几点

  • 确切的输出将根据你使用的数据库而有所不同。上面的示例是为 PostgreSQL 生成的。

  • 表名是通过组合应用的名称(polls)和模型的小写名称自动生成的——questionchoice。(你可以覆盖此行为。)

  • 主键(ID)是自动添加的。(你也可以覆盖此项。)

  • 按照惯例,Django 会在外部键字段名后面追加"_id"。(是的,你也可以覆盖此项。)

  • 外部键关系通过FOREIGN KEY 约束明确表示。不用担心DEFERRABLE 部分;它告诉 PostgreSQL 不要在事务结束之前执行外部键。

  • 它是针对你正在使用的数据库定制的,因此数据库特定的字段类型,如auto_increment(MySQL)、bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY(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
...\> py 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
...\> py manage.py shell

我们使用它而不是简单地键入“python”,因为manage.py 设置了DJANGO_SETTINGS_MODULE 环境变量,它为 Django 提供了到mysite/settings.py 文件的 Python 导入路径。

进入 shell 后,探索数据库 API

>>> from polls.models import Choice, Question  # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

等一下。<Question: Question object (1)> 并不是此对象的有效表示形式。让我们通过编辑Question 模型(在polls/models.py 文件中)并在QuestionChoice 中添加__str__() 方法来解决此问题

polls/models.py
from django.db import models


class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text


class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

在你的模型中添加__str__() 方法非常重要,这不仅在你使用交互式提示时方便你操作,而且因为对象的表示形式在 Django 自动生成的管理界面中被广泛使用。

让我们也为这个模型添加一个自定义方法。

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 Choice, Question

# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith="What")
<QuerySet [<Question: What's up?>]>

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set (defined as "choice_set") to hold the "other side" of a ForeignKey
# relation (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>

# Create three 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 objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith="Just hacking")
>>> c.delete()

有关模型关系的更多信息,请参阅 访问相关对象。有关如何使用双下划线通过 API 执行字段查找的更多信息,请参阅 字段查找。有关数据库 API 的完整详细信息,请参阅我们的 数据库 API 参考

介绍 Django 管理界面

理念

为你的员工或客户生成管理站点以添加、更改和删除内容是一项乏味的工作,不需要太多创造力。因此,Django 完全自动化了模型管理界面的创建。

Django 是在新闻编辑室环境中编写的,内容发布者和公共网站之间有非常清晰的区分。站点管理员使用系统添加新闻报道、事件、体育比分等,这些内容显示在公共网站上。Django 解决了为站点管理员创建统一界面以编辑内容的问题。

管理界面不打算供网站访问者使用。它是为网站管理员准备的。

创建管理用户

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

$ python manage.py createsuperuser
...\> py manage.py createsuperuser

输入你想要的用户名并按回车键。

Username: admin

然后,系统会提示你输入你想要的电子邮件地址。

Email address: admin@example.com

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

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

启动开发服务器

Django 管理站点默认处于激活状态。让我们启动开发服务器并探索它。

如果服务器未运行,请按如下方式启动它:

$ python manage.py runserver
...\> py manage.py runserver

现在,打开一个网络浏览器,并访问你本地域上的“/admin/” – 例如,http://127.0.0.1:8000/admin/。你应该会看到管理界面的登录屏幕。

Django admin login screen

由于 翻译 默认开启,如果你设置了 LANGUAGE_CODE,登录屏幕将以给定的语言显示(如果 Django 有相应的翻译)。

进入管理站点

现在,尝试使用你在上一步中创建的超级用户帐户登录。你应该会看到 Django 管理界面的索引页面。

Django admin index page

你应该会看到几种可编辑的内容类型:组和用户。它们由 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

点击“问题”。现在你位于问题的“更改列表”页面。此页面显示数据库中的所有问题,并允许你选择一个问题进行更改。这里有我们之前创建的“有什么事吗?”问题。

Polls change list page

点击“有什么事吗?”问题进行编辑。

Editing form for question object

这里需要注意以下几点:

  • 表单是根据 Question 模型自动生成的。

  • 不同的模型字段类型(DateTimeFieldCharField)对应于相应的 HTML 输入小部件。每种类型的字段都知道如何在 Django 管理界面中显示自身。

  • 每个 DateTimeField 都获得了免费的 JavaScript 快捷方式。日期获得“今天”快捷方式和日历弹出窗口,时间获得“现在”快捷方式和一个方便的弹出窗口,其中列出了常用输入时间。

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

  • 保存 – 保存更改并返回到此对象类型的更改列表页面。

  • 保存并继续编辑 – 保存更改并重新加载此对象的管理页面。

  • 保存并添加另一个 – 保存更改并加载此对象类型的新空白表单。

  • 删除 – 显示删除确认页面。

如果“发布日期”的值与你在 教程 1 中创建问题时的时刻不匹配,则可能意味着你忘记设置 TIME_ZONE 设置的正确值。更改它,重新加载页面并检查是否显示了正确的值。

通过点击“今天”和“现在”快捷方式更改“发布日期”。然后点击“保存并继续编辑”。然后点击右上角的“历史记录”。你将看到一个页面,其中列出了通过 Django 管理界面对该对象进行的所有更改,以及进行更改的人员的时间戳和用户名。

History page for question object

当你对模型 API 感到满意并熟悉了管理站点后,请阅读 本教程的第 3 部分,了解如何为我们的投票应用添加更多视图。

返回顶部