文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库python
博客最主要的功能就是展现咱们写的文章,它须要从某个地方获取博客文章数据才能把文章展现出来,一般来讲这个地方就是数据库。咱们把写好的文章永久地保存在数据库里,当用户访问咱们的博客时,django 就去数据库里把这些数据取出来展示给用户。git
博客的文章应该含有标题、正文、做者、发表时间等数据。一个更加现代化的博客文章还但愿它有分类、标签、评论等。为了更好地存储这些数据,咱们须要合理地组织数据库的表结构。github
咱们的博客初级版本主要包含博客文章,文章会有分类以及标签。一篇文章只能有一个分类,但能够打上不少标签。数据库
数据库存储的数据其实就是表格的形式,例如存储博客文章的数据库表长这个样子:django
文章 id | 标题 | 正文 | 发表时间 | 分类 | 标签 |
---|---|---|---|---|---|
1 | title 1 | text 1 | 2019-7-1 | django | django 学习 |
2 | title 2 | text 2 | 2019-7-2 | django | django 学习 |
3 | title 3 | text 3 | 2019-7-3 | Python | Python 学习 |
其中文章 ID 是一个数字,惟一对应着一篇文章。固然还能够有更多的列以存储更多相关数据,这只是一个最基本的示例。编程
数据库表设计成这样其实已经能够了,可是稍微分析一下咱们就会发现一个问题,这 3 篇文章的分类和标签都是相同的,这会产生不少重复数据,当数据量很大时就浪费了存储空间。app
不一样的文章可能它们对应的分类或者标签是相同的,因此咱们把分类和标签提取出来,作成单独的数据库表,再把文章和分类、标签关联起来。下面分别是分类和标签的数据库表:编程语言
分类 id | 分类名 |
---|---|
1 | Django |
2 | Python |
标签 id | 标签名 |
---|---|
1 | Django 学习 |
2 | Python 学习 |
以上是天然语言描述的表格,数据库也和编程语言同样,有它本身的一套规定的语法来生成上述的表结构,这样咱们才能把数据存进去。通常来讲这时候咱们应该先去学习数据库建立表格的语法,再回来写咱们的 django 博客代码了。可是 django 告诉咱们不用这么麻烦,它已经帮咱们作了一些事情。django 把那一套数据库的语法转换成了 Python 的语法形式,咱们只要写 Python 代码就能够了,django 会把 Python 代码翻译成对应的数据库操做语言。用更加专业一点的说法,就是 django 为咱们提供了一套 ORM(Object Relational Mapping)系统。学习
例如咱们的分类数据库表,django 只要求咱们这样写:网站
blog/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
复制代码
Category
就是一个标准的 Python 类,它继承了 models.Model
类,类名为 Category
。Category
类有一个属性 name
,它是 models.CharField
的一个实例。
这样,django 就能够把这个类翻译成数据库的操做语言,在数据库里建立一个名为 category 的表格,这个表格的一个列名为 name。还有一个列 id,虽然没有显示定义,但 django 会为咱们自动建立。能够看出从 Python 代码翻译成数据库语言时其规则就是一个 Python 类对应一个数据库表格,类名即表名,类的属性对应着表格的列,属性名即列名。
咱们须要 3 个表格:文章(Post)、分类(Category)以及标签(Tag),下面就来分别编写它们对应的 Python 类。模型的代码一般写在相关应用的 models.py 文件里。已经在代码中作了详细的注释,说明每一句代码的含义。但若是你在移动端下阅读不便的话,也能够跳到代码后面看正文的里的讲解。
blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
""" django 要求模型必须继承 models.Model 类。 Category 只须要一个简单的分类名 name 就能够了。 CharField 指定了分类名 name 的数据类型,CharField 是字符型, CharField 的 max_length 参数指定其最大长度,超过这个长度的分类名就不能被存入数据库。 固然 django 还为咱们提供了多种其它的数据类型,如日期时间类型 DateTimeField、整数类型 IntegerField 等等。 django 内置的所有类型可查看文档: https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-types """
name = models.CharField(max_length=100)
class Tag(models.Model):
""" 标签 Tag 也比较简单,和 Category 同样。 再次强调必定要继承 models.Model 类! """
name = models.CharField(max_length=100)
class Post(models.Model):
""" 文章的数据库表稍微复杂一点,主要是涉及的字段更多。 """
# 文章标题
title = models.CharField(max_length=70)
# 文章正文,咱们使用了 TextField。
# 存储比较短的字符串可使用 CharField,但对于文章的正文来讲可能会是一大段文本,所以使用 TextField 来存储大段文本。
body = models.TextField()
# 这两个列分别表示文章的建立时间和最后一次修改时间,存储时间的字段用 DateTimeField 类型。
created_time = models.DateTimeField()
modified_time = models.DateTimeField()
# 文章摘要,能够没有文章摘要,但默认状况下 CharField 要求咱们必须存入数据,不然就会报错。
# 指定 CharField 的 blank=True 参数值后就能够容许空值了。
excerpt = models.CharField(max_length=200, blank=True)
# 这是分类与标签,分类与标签的模型咱们已经定义在上面。
# 咱们在这里把文章对应的数据库表和分类、标签对应的数据库表关联了起来,可是关联形式稍微有点不一样。
# 咱们规定一篇文章只能对应一个分类,可是一个分类下能够有多篇文章,因此咱们使用的是 ForeignKey,即一
# 对多的关联关系。且自 django 2.0 之后,ForeignKey 必须传入一个 on_delete 参数用来指定当关联的
# 数据被删除时,被关联的数据的行为,咱们这里假定当某个分类被删除时,该分类下所有文章也同时被删除,所以 # 使用 models.CASCADE 参数,意为级联删除。
# 而对于标签来讲,一篇文章能够有多个标签,同一个标签下也可能有多篇文章,因此咱们使用
# ManyToManyField,代表这是多对多的关联关系。
# 同时咱们规定文章能够没有标签,所以为标签 tags 指定了 blank=True。
# 若是你对 ForeignKey、ManyToManyField 不了解,请看教程中的解释,亦可参考官方文档:
# https://docs.djangoproject.com/en/2.2/topics/db/models/#relationships
category = models.ForeignKey(Category, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, blank=True)
# 文章做者,这里 User 是从 django.contrib.auth.models 导入的。
# django.contrib.auth 是 django 内置的应用,专门用于处理网站用户的注册、登陆等流程,User 是
# django 为咱们已经写好的用户模型。
# 这里咱们经过 ForeignKey 把文章和 User 关联了起来。
# 由于咱们规定一篇文章只能有一个做者,而一个做者可能会写多篇文章,所以这是一对多的关联关系,和
# Category 相似。
author = models.ForeignKey(User, on_delete=models.CASCADE)
复制代码
首先是 Category
和 Tag
类,它们均继承自 models.Model
类,这是 django 规定的。Category
和 Tag
类均有一个name
属性,用来存储它们的名称。因为分类名和标签名通常都是用字符串表示,所以咱们使用了 CharField
来指定 name
的数据类型,同时 max_length
参数则指定 name
容许的最大长度,超过该长度的字符串将不容许存入数据库。除了 CharField
,django 还为咱们提供了更多内置的数据类型,好比时间类型 DateTimeField
、整数类型 IntegerField
等等。
提示:
在本教程中咱们会教你这些类型的使用方法,但之后你开发本身的项目时,你就须要经过阅读 django 官方文档 关于字段类型的介绍 来了解有哪些数据类型可使用以及如何使用它们。
Post
类也同样,必须继承自 modesl.Model
类。文章的数据库表稍微复杂一点,主要是列更多,咱们指定了这些列:
title
:文章的标题,数据类型是 CharField
,容许的最大长度 max_length = 70
。
body
:文章正文,咱们使用了 TextField
。比较短的字符串存储可使用 CharField
,但对于文章的正文来讲可能会是一大段文本,所以使用 TextField
来存储大段文本。
created_time
和 modified_time
:这两个列分别表示文章的建立时间和最后一次修改时间,存储时间的列用 DateTimeField
数据类型。
excerpt
:文章摘要,能够没有文章摘要,但默认状况下 CharField
要求咱们必须存入数据,不然就会报错。指定 CharField
的 blank=True
参数值后就能够容许空值了。
category
和 tags
:分类与标签,分类与标签的模型咱们已经定义在上面。咱们把文章对应的数据库表和分类、标签对应的数据库表关联了起来,可是关联形式稍微有点不一样。咱们规定一篇文章只能对应一个分类,可是一个分类下能够有多篇文章,因此咱们使用的是 ForeignKey
,即一对多的关联关系。且自 django 2.0 之后,ForeignKey 必须传入一个 on_delete 参数用来指定当关联的数据被删除时,被关联的数据的行为,咱们这里假定当某个分类被删除时,该分类下所有文章也同时被删除,所以使用 models.CASCADE 参数,意为级联删除。
而对于标签来讲,一篇文章能够有多个标签,同一个标签下也可能有多篇文章,因此咱们使用 ManyToManyField
,代表这是多对多的关联关系。同时咱们规定文章能够没有标签,所以为标签 tags 指定了 blank=True
。
author
:文章做者,这里 User
是从 django.contrib.auth.models 导入的。django.contrib.auth 是 django 内置的应用,专门用于处理网站用户的注册、登陆等流程。其中 User
是 django 为咱们已经写好的用户模型,和咱们本身编写的 Category
等类是同样的。这里咱们经过 ForeignKey
把文章和 User
关联了起来,由于咱们规定一篇文章只能有一个做者,而一个做者可能会写多篇文章,所以这是一对多的关联关系,和 Category
相似。
咱们分别使用了两种关联数据库表的形式:ForeignKey
和 ManyToManyField
。
ForeignKey
代表一种一对多的关联关系。好比这里咱们的文章和分类的关系,一篇文章只能对应一个分类,而一个分类下能够有多篇文章。反应到数据库表格中,它们的实际存储状况是这样的:
文章 ID | 标题 | 正文 | 分类 ID |
---|---|---|---|
1 | title 1 | body 1 | 1 |
2 | title 2 | body 2 | 1 |
3 | title 3 | body 3 | 1 |
4 | title 4 | body 4 | 2 |
分类 ID | 分类名 |
---|---|
1 | Django |
2 | Python |
能够看到文章和分类其实是经过文章数据库表中 分类 ID 这一列关联的。当要查询文章属于哪个分类时,只须要查看其对应的分类 ID 是多少,而后根据这个分类 ID 就能够从分类数据库表中找到该分类的数据。例如这里文章 一、二、3 对应的分类 ID 均为 1,而分类 ID 为 1 的分类名为 django,因此文章 一、二、3 属于分类 django。同理文章 4 属于分类 Python。
反之,要查询某个分类下有哪些文章,只须要查看对应该分类 ID 的文章有哪些便可。例如这里 django 的分类 ID 为 1,而对应分类 ID 为 1 的文章有文章 一、二、3,因此分类 django 下有 3 篇文章。
但愿这个例子能帮助你加深对多对一关系,以及它们在数据库中是如何被关联的理解,更多的例子请看文末给出的 django 官方参考资料。
ManyToManyField
代表一种多对多的关联关系,好比这里的文章和标签,一篇文章能够有多个标签,而一个标签下也能够有多篇文章。反应到数据库表格中,它们的实际存储状况是这样的:
文章 ID | 标题 | 正文 |
---|---|---|
1 | title 1 | body 1 |
2 | title 2 | body 2 |
3 | title 3 | body 3 |
4 | title 4 | body 4 |
标签 ID | 标签名 |
---|---|
1 | Django 学习 |
2 | Python 学习 |
文章 ID | 标签 ID |
---|---|
1 | 1 |
1 | 2 |
2 | 1 |
3 | 2 |
多对多的关系没法再像一对多的关系中的例子同样在文章数据库表加一列 分类 ID 来关联了,所以须要额外建一张表来记录文章和标签之间的关联。例如文章 ID 为 1 的文章,既对应着 标签 ID 为 1 的标签,也对应着 标签 ID 为 2 的标签,即文章 1 既属于标签 1:django 学习,也属于标签 2:Python 学习。
反之,标签 ID 为 1 的标签,既对应着 文章 ID 为 1 的文章,也对应着 文章 ID 为 2 的文章,即标签 1:django 学习下有两篇文章。
但愿这个例子能帮助你加深对多对多关系,以及它们在数据库中是如何被关联的理解,更多的例子请看文末给出的 django 官方参考资料。
假如你对多对一关系和多对多关系还存在一些困惑,强烈建议阅读官方文档对这两种关系的说明以及更多官方的例子以加深理解: