Part2 数据库设置
第一部分结束以后,我们来学习第二部分的数据库设置。这一章我们来学习怎么创建数据库模块(model)以及Django自动生成的管理站点。
数据库设置
我们先打开mysite/settings.py
,这个模块就是Django中其他模块的配置参数所在的模块。默认情况下,Django使用SQLite数据库。如果你对数据库不熟悉,就只是想尝试一下Django,使用默认设置就可以了。如果你想用其他的生产数据库,例如MySQL、PostgreSQL等,可以自己去查找Django的官方文档。
编辑mysite/settings.py
文件的时候,将TIME_ZONE
设置为你自己的时间区域(中国默认的设置是Asia/Shanghai
)。在文件的上面有一个INSTALLED_APPS
设置,这个地方保存的是当前Django项目中所有需要使用到的应用名称。前面我们说到过,一个应用可以放在多个项目里,只需要将应用的目录放到对应的项目目录下,然后在这个参数里将应用的名称保存下来即可。这这个参数里还默认自带了6个应用,其作用分别是:
-
django.contrib.admin
:Django自带的管理站点,后面马上就要用到 -
django.contrib.auth
:Django自带的认证系统。 -
django.contrib.contenttypes
:目录类型框架 -
django.contrib.sessions
:Django会话框架 -
django.contrib.messages
:Django消息框架 -
django.contrib.staticfiles
:Django消息静态文件框架
这些应用都是作为一个通用组件包含在每一个Django项目中,在后面我们学习了更多的Django知识以后,就可以来尝试自己定制或者修改这些组件,目前还是先用默认的组件。
上面这些组件中的部分组件要使用的话,需要在数据库中创建表,因此我们首先需要在数据库中将对应的表创建出来。然后才能使用它们。创建表的命令如下:
$ python manage.py migrate
migrate
命令查看INSTALLED_APPS
配置,然后根据你在mysite/settings.py
里的设置以及应用里的迁移配置来创建需要的数据库(我们后面来介绍数据库表)。这个命令运行过程中,你会看到很多创建表的信息,如果你对这些表感兴趣的话,可以自己登录数据库查看创建的表(需要你自己对数据库有了解)。
创建模型(model)
现在来创建你的数据库模型——简单的说,就是你的数据库布局(或者说表结构),还有一些额外的信息。
在我们的投票应用中,我们会创建两个模型,Question
和Choice
。模型Question
会有一个代表问题的字段和一个表示发布日期的字段。Choice
会有,一个表示选择的文本字段和一个表示投票数量的字段。每个Choice
都是和一个Question
进行关联的。
上面的这些概念都是通过Python类来表示,编辑polls/modes.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.modesl.Model
。每个模型都有一堆类属性,模型里的每一种属性都代表一个数据库字段。
每一个字段都是由一个Field
类实例来表示的,例如CharField
表示字符串字段,DateTimeField
表示日期时间。通过这种方式来告知Django每种字段在数据库中的数据类型。每个Field实例的名称(例如question_text
或pub_date
)就是数据库中字段的名称,且通常是一个对机器友好的名称(英文表示)。你可以在你的Python代码中使用这个名称,而在数据库中会作为表字段的名称。
你可以使用一个Field实例的第一个可选位置参数,来作为用户阅读起来比较友好的名称(自己所使用的语言,例如一个中文名)。如果没有给出这个参数,默认就会使用前面的表字段名。在上面的例子里, 我们只给pub_date
定义了这样一个参数(date published
)。对于这个模型中的其他字段,都会使用默认的字段名称。
一些Field
类有一些必须的参数,例如CharField
,需要你给出一个max_length
参数,用来表示这个字段的长度,这个值不光用在数据库表里面,也会用在验证上,后面我们会看到这部分的内容。
一个Field
也可以拥有多个可选参数,在上面的例子里,我们看到votes
字段的默认值设置为0(default=0
)。最后,我们看到使用Foreign Key
定义了一个关联关系,这告诉Django,每一个Choice
和一个Question
进行关联。Django支持所有的通用数据库关联:多对一、多对多、一对一。
激活模块
上面那一小部分代码给出了大量的信息,根据这些代码,Django可以做:
- 为这个应用创建数据库表(CREATE TABLE部分)
- 创建一个Python数据库访问API,用来访问
Question
和Choice
对象。
但是我们首先需要告诉我们的项目,我们将polls应用安装到那哪里了。
Django的应用是“可插拔式”的,你可以在多个项目中使用你的应用,还可以分发应用。因为应用并不是和某个Django项目绑定的。
要在我们的项目中包含应用,我们需要添加他的配置类到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',
]
现在你的项目就知道已经包含了这个应用,下面来运行另外一个命令:
$ 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迁移程序的时候使用。
有一个命令用来运行迁移过程,并且自动管理你的数据库,叫做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
和choice
)的组合自动生成的(你可以覆盖这种特性,后面学习数据库的定制时再看这个内容) - 主键(ID)是自动添加的(你也可以覆盖这个特性,即自定义主键)
- 根据惯例,Django会添加
_id
到外键字段名上面去(当然,你也可以修改这个) - 外键关联是通过
FOREIGN KEY
约束显式定义的,不需要担心DEFERRABLE
部分。这只是告诉PostgreSQL在结束事务之前不要执行外键。 - 它是为你正在使用的数据库定制的,所以数据库特定的字段类型,例如
auto_increment(MySQL)
,serial(PostgreSQL)
,或者integer primary key autoincrement(SQLite)
都会为你自动处理。也适用于引号括起来的字段名称,例如双引号或单引号括起来的。 -
sqlmigrate
命令不会在你的数据库上实际运行迁移操作。只是把SQL语句打印到屏幕上面,让你看到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
),然后在你的数据库上运行这些命令。实际上就是将你在模型上的修改同步到你的数据库里。
迁移这个功能是非常强大的,让你在开发项目的时候随着时间的改变来修改你的模型,而不需要删除你的数据库、表或者新建一个新的数据库或者表。它专门用来升级你的数据库而不会丢失数据。我们会在这个教程后面部分更深入讲解这部分功能,但是现在,我们只需要记住下面这3步来修改模型:
- 修改你的模型(在models.py里面);
- 运行
python manage.py makemigrations appname
创建这些用于修改的迁移命令; - 运行
python manage.py migrate
来应用这些修改到你的数据库里面。
使用分开的命令来执行迁移过程是因为,你需要提交迁移过程到你的版本控制系统里面,然后同步到你的应用上。这不光是方便你开发,更是方便项目中的其他开发者。
使用API
现在,让我们在交互式的Python shell里面使用一下Django提供给我们的免费API。要调用Python shell,使用下面的命令:
$python manage.py shell
我们使用这个命令来替代简单的python,因为manage.py
设置了DJANGO_SETTINGS_MODULE
环境变量,这个变量保存的是你的mysite/settings.py
文件在Python中的导入路径。
当你在shel里的时候,我们来尝试一下数据库API:
>>> from polls.models import Question, Choice #插入我们刚才写的类名
# 系统里现在还没有Question
>>> Question.objects.all()
<QuerySet []>
# 创建一个新的Question.
# 在配置文件里对时区的支持默认是开启的,所以Django希望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
>>> 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 (1)>]>
稍等一下,<Question: Question object (1)>并不是这个对象一个有效的表示。让我们来编辑Question模型(在polls/models.py)来修复这个问题。添加一个str()方法到Question和Choice里面。
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自动生成的管理站点里面 。
要注意有很多普通的Python方法, 我们添加一个常用的方法用来展示:
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 datetime
和from django.utils import timezone
,分别用来引用Python的标准datetime
模块,和Django在django.utils.timezone
里的时区关联工具。如果你对Python中的时区处理不是很熟悉,建议你去学习一下相关知识。
保存这些更改,然后重新启动一个python shell窗口:
>>> from polls.models import Question, Choice
# Make sure our __str__() addition worked.
>>> 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?>]>
# 取出今年发布的question
>>> 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. INSERT声明中的create调用,会创建一个新的Choice
# 对象,将这个choice添加到可用的choices集合中,并且返回这个新的Choice对象
# Django创建一个集合来保存外键关联的“另外一边” (例如question的choice),也
# 可以通过API来访问
>>> q = Question.objects.get(pk=1)
# 显示关联的对象集合中的任何choices——目前为止还一个都没有
>>> q.choice_set.all()
<QuerySet []>
# 创建3个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
# API 会自动跟踪你需要的关系,使用双下划线来分开关联。这个可以查到你想要
# 的任何深度,没有任何限制。
# 找出pub_date在今年的任何Choices (重新使用我们上面创建的 'current_year' 变量).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# 删除其中一个choices. 使用delete()方法。
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
数据库API的更多知识,建议查询Django的官方文档
介绍Django Admin
创建一个管理员用户
首先我们需要创建一个可以登录管理站点的用户,运行下面的命令:
$ python manage.py createsuperuser
输入你想到的用户名然后按enter:
Username: admin
然后会要求你输入email地址:
Email address: admin@example.com
最后一步会要求你输入admin的密码,会要求你输入两次。
Password: **********
Password (again): *********
Superuser created successfully.
启动开发服务器
Django的管理站点默认已经启用了,我们启动开发服务器来看一下,打开浏览器,输入管理站点的地址:http://localhost:8000/admin,或者将localhost替换成你的虚拟机IP地址,会看到下面的管理页面:
如果你在
mysite/settings.py
文件里将translation设置打开了,并且你自己的浏览器设置为你自己的语言,那么显示的管理站点页面可能就是你本地语言。
进入管理站点
输入刚才第一步配置好的账号密码,然后点击登录,就能进入到管理站点,会看到下面的内容:
image.png
可以看到几个能够编辑的内容,例如groups和users,这些是django.contrib.auth提供的,就是前面说的由Django提供的认证模块。
让投票应用可以在admin里面修改
但是我们的投票应用在哪呢,并没有在admin的主页上面显示出来。这是因为我们还需要再做一件事,告诉admin,Question
由一个admin接口。要做这个,我们打开polls/admin.py
文件。然后编辑它,内容如下所示:
from django.contrib import admin
from .models import Question
admin.site.register(Question)
探索免费的管理功能
上面我们将Question
类注册到了admin站点,Django就知道怎么在站点上显示这个内容,如下所示:
点击
Questions
,然后可以看到Question
的修改列表。这个页面显示出数据库中所有的Question
实例,让你可以选择其中的Question
,并对它们进行操作。我们可以看到刚才创建的"What's new" Question:image.png
点击进去就可以看到这个问题的编辑页面:
image.png
这里要注意的一些事情:
- 表单是由
Question
模型自动创建的 - 不同的模型字段类型(
DateTimeField
,CharField
)和不同的HTML插件对应。每种类型的字段都知道怎么在Django admin里展示自己。 - 每一个
DateTimeField
获得一个免费的JS图标,Dates
有一个Today
图标和日历弹出菜单。Times
有一个Now
图标和一个显示通用时间的弹出式菜单。
页面的顶部给了很多选项:
-
Save
- 保存变更并且返回到对象类型的列表页面 -
Save and continue editing
- 保存变更,然后重新加载这个对象的管理页面 -
Save and another
- 保存变更,然后重新加载相同对象的一个新的、空白的表单; -
Delete
- 显示一个删除确认页面。
如果你在第一jie创建question
的时候Date published
的值和时间不匹配,意味着你可能忘了正确设置TIME_ZONE
的值。修改它,然后重新加载页面,正确的值就会出现。
通过点击Today和Now图标来修改发布日期,然后点“Save and continue editing”,再点击右上方的“History”,就会看到这个对象的编辑历史。可以看到编辑的用户和编辑的时间:
image.png
上面就是设置数据库、创建数据库模型、使用数据库对象、使用管理站点的所有内容。如果你对上面的这些内容熟悉以后,就可以查看第三部分,为应用添加更多的视图。