编写你的第一个Django应用程序,第2部分
数据库设置
现在,打开 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作为数据库,则必须添加其他设置,如 USER
、 PASSWORD
和 HOST
。
更多的细节,请参见 DATABASES
的参考文档。
对于SQLite以外的数据库
如果使用SQLite之外的数据库,请确保你已经创建了一个数据库。
可以在你的数据库的交互式提示命令下,使用“ `CREATE
DATABASE database_name;` ”创建它。
还要确保 mysite/settings.py
中提供的数据库用户具有“创建数据库”权限。
这允许自动创建 test database ,这将在以后的教程中进行。
如果你使用SQLite,你不需要事先创建任何东西 —— 数据库文件将会在需要的时候自动创建。
当你编辑 mysite/settings.py
时,请设置 TIME_ZONE
为你自己的时区。
另外,请注意文件顶端的 INSTALLED_APPS
设置。它保存这个Django实例中激活的所有的Django应用的名字。应用可以在多个项目中使用,而且你可以将这些应用打包和分发给其他人在他们的项目中使用。
默认情况下, INSTALLED_APPS
包含下面的应用,它们都是Django 与生俱来的:
django.contrib.admin
—— 管理站点。 你会很快使用它。django.contrib.auth
—— 认证系统。django.contrib.contenttypes
—— 用于内容类型的框架。django.contrib.sessions
—— 会话框架。django.contrib.messages
—— 消息框架。django.contrib.staticfiles
—— 管理静态文件的框架。
这些应用,默认包含在Django中,以方便通用场合下使用。
其中一些应用使用至少一个数据库表,因此我们需要在数据库中创建表,然后才能使用它们。 要做到这一点,请运行以下命令:
$ python manage.py migrate
migrate
查看 INSTALLED_APPS
设置并根据 mysite/settings.py
文件中的数据库设置创建任何必要的数据库表,数据库的迁移还会跟踪应用的变化(我们稍后会讲到)。
你会看到对每次迁移有一条信息。如果你感兴趣,可以运行你数据库的命令行工具来查看Django创建了哪些表: \dt
(PostgreSQL), SHOWTABLES
(MySQL),
.schema
(SQLite), or SELECT TABLE_NAME FROM USER_TABLES
(Oracle)
创建模型
现在,我们将使用额外的元数据来定义你的模型 — 主要是数据库的布局。
在这个简单的投票应用中,我们将创建两个模型: Question和Choice。 Question对象具有一个question_text(问题)属性和一个publish_date(发布时间)属性。 Choice有两个字段:选择的内容和选择的得票统计。 每个Choice与一个Question关联。
这些概念通过简单的Python类来表示。 编辑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_date
或 question_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应用已经安装。
要在我们的项目中包含该应用程序,我们需要在 INSTALLED_APPS
设置中添加对其配置类的引用。 PollsConfig
类位于 polls/apps.py
文件中,因此其点分路径为 'polls.apps.PollsConfig'
。编辑 mysite/settings.py
文件,并将点分路径添加到 INSTALLED_APPS
设置。
看起来像这样:
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" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" RENAME TO "polls_choice__old";
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id"));
INSERT INTO "polls_choice" ("id", "choice_text", "votes", "question_id") SELECT "id", "choice_text", "votes", NULL FROM "polls_choice__old";
DROP TABLE "polls_choice__old";
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
请注意以下几点:
输出的具体内容会依据你使用的数据库而不同。 以上例子使用的数据库是PostgreSQL。
表名是自动生成的,由app的名字(
polls
)和模型名字的小写字母组合而成 ——question
和choice
。 (你可以重写这个行为。)主键(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:
Applying polls.0001_initial... OK
migrate
命令会找出所有还没有被应用的迁移文件(Django使用数据库中一个叫做 django_migrations
的特殊表来追踪哪些迁移文件已经被应用过),并且在你的数据库上运行它们 —— 本质上来讲,就是使你的数据库模式和你改动后的模型进行同步。
迁移功能非常强大,可以让你在开发过程中不断修改你的模型而不用删除数据库或者表然后再重新生成一个新的 —— 它专注于升级你的数据库且不丢失数据。
我们将在本教程的后续章节对迁移进行深入地讲解,但是现在,请记住实现模型变更的三个步骤:
- 修改你的模型(在
models.py
文件中)。 - 运行
python manage.py makemigrations
,为这些修改创建迁移文件 - 运行
python manage.py migrate
,将这些改变更新到数据库中。
有单独的命令来制作和应用迁移的原因是因为您将提交迁移到您的版本控制系统并将其发送到您的应用程序;它们不仅可以使您的开发更容易,而且还可以被其他开发人员和生产中使用。
玩转API
现在,让我们进入Python的交互式shell,玩转这些Django提供给你的API。 使用如下命令来调用Python shell:
$ python manage.py shell
一旦你进入shell,请浏览 database API :
In [1]: from polls.models import Question, Choice # Import the model classes we just wrote.
# No questions are in the system yet.# 系统中还没有question。
In [2]: Question.objects.all()
Out[2]: <QuerySet []>
# 创建一个新的Question。
# 默认的settings文件中启用对时区的支持,所以
# Django期待pub_date为一个带有tzinfo的datetime。 使用timezone.now()
# 而不是datetime.datetime.now(),它将完成正确的事情。
In [3]: # Create a new Question.^M
...: # Support for time zones is enabled in the default settings file, so^M
...: # Django expects a datetime with tzinfo for pub_date. Use timezone.now()^M
...: # instead of datetime.datetime.now() and it will do the right thing.
...: from django.utils import timezone
In [4]: q = Question(question_text="What's new?", pub_date=timezone.now())
# 保存这个对象到数据库中。 你必须显示地调用save()。
# Save the object into the database. You have to call save() explicitly.
In [5]: q.save()
# 现在它有一个ID。 注意,它可能为"1L"而不是"1",取决于
# 你使用的数据库。 这不是什么事;它只是表示你的
# 数据库后端返回的是Python长整形
# 对象。
# Now it has an ID. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
In [6]: q.id
Out[6]: 1
# 通过Python属性访问模型字段的值。
# Access model field values via Python attributes.
>>> q.question_text
In [7]: q.question_text
Out[7]: "What's new?"
In [8]: q.pub_date
Out[8]: datetime.datetime(2017, 11, 30, 13, 30, 20, 705812, tzinfo=<UTC>)
# 通过改变属性来改变字段的值,然后调用save()。
# Change values by changing the attributes, then calling save().
In [9]: q.question_text = "What's up?"
In [10]: q.save()
# objects.all()显示数据库中所有的question。
# objects.all() displays all the questions in the database.
In [11]: Question.objects.all()
Out[11]: <QuerySet [<Question: Question object>]>
先等一下.。 <Questionobject>
完全是这个对象无意义的表示。让我们来修复这个问题,编辑 Question
模型(在 polls/models.py
文件中)并添加一个 __str__()
方法给 Question
和 Choice
:
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)
给你的模型添加 __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)
保存这些改动,然后通过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 通过创建
# 一个集合来控制“在另一端的”可以通过 API 访问的外键。
# (例如,一个“问题”的“选项”)
>>> 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()
Django后台管理简介
创建管理员用户
$ python manage.py createsuperuser
Username (leave blank to use 'xc'): admin
Email address: 986284130@qq.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Password:
Password (again):
Superuser created successfully.
启动开发服务器
Django的管理后台站点是默认启用的。 让我们启动开发服务器,然后探索它。
如果服务器没有运行,像下面这样启动它:
$ python manage.py runserver
现在,打开一个浏览器访问你本地域名中的 “/admin/” — 例如http://127.0.0.1:8000/admin/。你应该会看到管理后台站点的登录界面:
进入管理员后台站点
用你在前面创建的超级用户账号来登录这个站点。 你应该会看到Django管理后台站点的首页面:
让投票应用在管理后台站点中可编辑
但我们的投票应用在哪儿? 它没有显示在管理后台站点的首页面上。
只需要做一件事:我们需要告诉管理站点Question 对象要有一个管理界面。 要做这件事,需要打开polls/admin.py
文件,把它编辑成这样:
from django.contrib import admin
from .models import Question
admin.site.register(Question)
探索管理后台的功能
现在,我们已经在管理站点中注册了Question对象,Django应该把它显示在管理站点的首页面上:
点击“Questions”。 现在,你会进入Question的“变更列表”。 这个界面显示了数据库中的所有question,你可以选择一个来更改它。 我们之前创建的“What's up?”问题
单击“What’s up?”来编辑它:
注意事项:
这个表单是根据
Question
模型文件自动生成的。模型中不同类型的字段(
DateTimeField
、CharField
)会对应相应的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管理界面对此对象所做的全部更改的清单,包含有时间戳和修改人的姓名等信息: