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)

现在来创建你的数据库模型——简单的说,就是你的数据库布局(或者说表结构),还有一些额外的信息。
在我们的投票应用中,我们会创建两个模型,QuestionChoice。模型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_textpub_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,用来访问QuestionChoice对象。

但是我们首先需要告诉我们的项目,我们将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)和模型名称的小写(questionchoice)的组合自动生成的(你可以覆盖这种特性,后面学习数据库的定制时再看这个内容)
  • 主键(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 datetimefrom 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地址,会看到下面的管理页面:

image.png

如果你在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就知道怎么在站点上显示这个内容,如下所示:

image.png

点击Questions,然后可以看到Question的修改列表。这个页面显示出数据库中所有的Question实例,让你可以选择其中的Question,并对它们进行操作。我们可以看到刚才创建的"What's new" Question:
image.png

点击进去就可以看到这个问题的编辑页面:
image.png

这里要注意的一些事情:

  • 表单是由Question模型自动创建的
  • 不同的模型字段类型(DateTimeFieldCharField)和不同的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

上面就是设置数据库、创建数据库模型、使用数据库对象、使用管理站点的所有内容。如果你对上面的这些内容熟悉以后,就可以查看第三部分,为应用添加更多的视图。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容