创建 Django 博客的数据库模型

2017-04-0799808 阅读32 评论

设计博客的数据库表结构

博客最主要的功能就是展示我们写的文章,它需要从某个地方获取博客文章数据才能把文章展示出来,通常来说这个地方就是数据库。我们把写好的文章永久地保存在数据库里,当用户访问我们的博客时,Django 就去数据库里把这些数据取出来展现给用户。

博客的文章应该含有标题、正文、作者、发表时间等数据。一个更加现代化的博客文章还希望它有分类、标签、评论等。为了更好地存储这些数据,我们需要合理地组织数据库的表结构。

我们的博客初级版本主要包含博客文章,文章会有分类以及标签。一篇文章只能有一个分类,但可以打上很多标签。

数据库存储的数据其实就是表格的形式,例如存储博客文章的数据库表长这个样子:

文章 id 标题 正文 发表时间 分类 标签
1 title 1 text 1 2016-12-23 Django Django 学习
2 title 2 text 2 2016-12-24 Django Django 学习
3 title 3 text 3 2016-12-26 Python Python 学习

其中文章 ID 是一个数字,唯一对应着一篇文章。当然还可以有更多的列以存储更多相关数据,这只是一个最基本的示例。

数据库表设计成这样其实已经可以了,但是稍微分析一下我们就会发现一个问题,这 3 篇文章的分类和标签都是相同的,这会产生很多重复数据,当数据量很大时就浪费了存储空间。

不同的文章可能它们对应的分类或者标签是相同的,所以我们把分类和标签提取出来,做成单独的数据库表,再把文章和分类、标签关联起来。下面分别是分类和标签的数据库表:

分类 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 类,类名为 CategoryCategory 类有一个属性 name,它是 models.CharField 的一个实例。

这样,Django 就可以把这个类翻译成数据库的操作语言,在数据库里创建一个名为 category 的表格,这个表格的一个列名为 name。还有一个列 id,Django 则会自动创建。可以看出从 Python 代码翻译成数据库语言时其规则就是一个 Python 类对应一个数据库表格,类名即表名,类的属性对应着表格的列,属性名即列名。

我们需要 3 个表格:文章(Post)、分类(Category)以及标签(Tag),下面就来分别编写它们对应的 Python 类。模型的代码通常写在相关应用的 models.py 文件里。已经在代码中做了详细的注释,说明每一句代码的含义。但如果你在移动端下阅读不便的话,也可以跳到代码后面看正文的里的讲解。

注意:代码中含有中文注释,如果你直接 copy 代码到你的文本编辑器且使用了 Python 2 开发环境的话,会得到一个编码错误。因此请在文件最开始处加入编码声明:# coding: utf-8。

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/1.10/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,即一对多的关联关系。
    # 而对于标签来说,一篇文章可以有多个标签,同一个标签下也可能有多篇文章,所以我们使用 ManyToManyField,表明这是多对多的关联关系。
    # 同时我们规定文章可以没有标签,因此为标签 tags 指定了 blank=True。
    # 如果你对 ForeignKey、ManyToManyField 不了解,请看教程中的解释,亦可参考官方文档:
    # https://docs.djangoproject.com/en/1.10/topics/db/models/#relationships
    category = models.ForeignKey(Category)
    tags = models.ManyToManyField(Tag, blank=True)

    # 文章作者,这里 User 是从 django.contrib.auth.models 导入的。
    # django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程,User 是 Django 为我们已经写好的用户模型。
    # 这里我们通过 ForeignKey 把文章和 User 关联了起来。
    # 因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和 Category 类似。
    author = models.ForeignKey(User)

博客模型代码代码详解

首先是 CategoryTag 类,它们均继承自 model.Model 类,这是 Django 规定的。CategoryTag 类均有一个name 属性,用来存储它们的名称。由于分类名和标签名一般都是用字符串表示,因此我们使用了 CharField 来指定 name 的数据类型,同时 max_length 参数则指定 name 允许的最大长度,超过该长度的字符串将不允许存入数据库。除了 CharField ,Django 还为我们提供了更多内置的数据类型,比如时间类型 DateTimeField、整数类型 IntegerField 等等。

在本教程中我们会教你这些类型的使用方法,但以后你开发自己的项目时,你就需要通过阅读Django 官方文档 关于字段类型的介绍 来了解有哪些数据类型可以使用以及如何使用它们。

Post 类也一样,必须继承自 model.Model 类。文章的数据库表稍微复杂一点,主要是列更多,我们指定了这些列:

  • title。这是文章的标题,数据类型是 CharField,允许的最大长度 max_length = 70

  • body。文章正文,我们使用了 TextField。比较短的字符串存储可以使用 CharField,但对于文章的正文来说可能会是一大段文本,因此使用 TextField 来存储大段文本。

  • created_timemodified_time。这两个列分别表示文章的创建时间和最后一次修改时间,存储时间的列用 DateTimeField 数据类型。

  • excerpt。文章摘要,可以没有文章摘要,但默认情况下 CharField 要求我们必须存入数据,否则就会报错。指定 CharFieldblank=True 参数值后就可以允许空值了。

  • categorytags。这是分类与标签,分类与标签的模型我们已经定义在上面。我们把文章对应的数据库表和分类、标签对应的数据库表关联了起来,但是关联形式稍微有点不同。我们规定一篇文章只能对应一个分类,但是一个分类下可以有多篇文章,所以我们使用的是 ForeignKey,即一对多的关联关系。而对于标签来说,一篇文章可以有多个标签,同一个标签下也可能有多篇文章,所以我们使用 ManyToManyField,表明这是多对多的关联关系。同时我们规定文章可以没有标签,因此为标签 tags 指定了 blank=True

  • author。文章作者,这里 User 是从 django.contrib.auth.models 导入的。django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程。其中 User 是 Django 为我们已经写好的用户模型,和我们自己编写的 Category 等类是一样的。这里我们通过 ForeignKey 把文章和 User关联了起来,因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和 Category 类似。

理解多对一和多对多两种关联关系

我们分别使用了两种关联数据库表的形式:ForeignKey 和 ManyToManyField。

ForeignKey

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 就可以从分类数据库表中找到该分类的数据。例如这里文章 1、2、3 对应的分类 ID 均为 1,而分类 ID 为 1 的分类名为 Django,所以文章 1、2、3 属于分类 Django。同理文章 4 属于分类 Python。

反之,要查询某个分类下有哪些文章,只需要查看对应该分类 ID 的文章有哪些即可。例如这里 Django 的分类 ID 为 1,而对应分类 ID 为 1 的文章有文章 1、2、3,所以分类 Django 下有 3 篇文章。

希望这个例子能帮助你加深对多对一关系,以及它们在数据库中是如何被关联的理解,更多的例子请看文末给出的 Django 官方参考资料。

ManyToManyField

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 官方参考资料。

假如你对多对一关系和多对多关系还存在一些困惑,强烈建议阅读官方文档对这两种关系的说明以及更多官方的例子以加深理解:

总结

本章节的代码位于:Step3: blog models

如果遇到问题,请通过下面的方式寻求帮助。

-- EOF --

32 评论
登录后回复
Stone
2020-07-23 19:40:32

打个卡, 看了博主写的,懂了

回复
Taoyuan
2018-04-25 16:59:00

Django关系类型字段

https://blog.csdn.net/lftaoyuan/article/details/79085052


class ForeignKey(to, on_delete, **options) 


to:关联的模型


 on_delete: 关联删除(2.0后必填)

回复
SamK6517433923 Taoyuan
2019-01-22 09:47:29

点赞

回复
+0
2018-01-20 16:54:08

教程很棒,思路清晰,适合新手。

根据文章,踩了个坑,建外键时,ForeignKey包含两个参数:

category = models.ForeignKey(Category, on_delete=models.CASCADE)

回复
奇楠之后 +0
2018-01-27 14:36:37

感谢,也碰到这个坑。我用的是Django2.0 不知道是不是版本的问题。另外,能解释下on_delete=models.CASXADE的意思吗

回复
吓得我捡起了我的萝卜 奇楠之后
2018-02-23 19:42:27

我也碰到这个坑了,最后找到了这个,

https://www.cnblogs.com/linxiyue/p/3667418.html

回复
Li Zhenhan +0
2018-03-02 00:05:29

版本问题。2.0需要on_delete参数,文中的不需要。

回复
Joseph
2017-12-19 11:26:38

文章对新手很友好~赞一个!

回复
FreedomMan0
2017-12-08 14:06:11

请问博主  必须要先运行本地服务器吗

回复
jing.wang
2017-12-03 15:29:41

foreignkey is many to one relationship. i sugguest u correct it 

回复
逐殇小彬
2017-09-27 11:52:23

楼主 您好  咨询一下  

这边model中post的定义:

#文章@python_2_unicode_compatibleclass Post(models.Model): #标题 title = models.CharField(max_length=70) #正文 body = models.TextField() #创建及修改时间 created_time = models.DateTimeField('保存日期',default = timezone.now) modified_time = models.DateTimeField('最后修改日期', auto_now = True) #文章简介 excerpt = models.CharField(max_length=200,blank=True) category = models.ForeignKey(Category) tags = models.ManyToManyField(Tag,blank=True) author = models.ForeignKey(User) def __str__(self): return self.title def get_absolute_url(self): return reverse('specfile:detail',kwargs={'pk':self.pk})

这边的需求是在前台做一个新增文章的页面   

这边view的编写如下:

def post_add(request):

 error = [] 

 title = None 

 body = None 

 intro = None 

 excerpt = None 

 created_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) 

 modified_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) 

 category = Category.objects.values_list('name',flat=True) 

 author = User.objects.values_list('username',flat=True) 

 tags =Tag.objects.values_list('name',flat=True)

 print author_id,category_id,tags_id if request.method == 'POST': title = request.POST.get('title') body = request.POST.get('intro') excerpt = request.POST.get('excerpt')

 post = Post.objects.create(title = title,body = body,created_time = created_time,modified_time = modified_time, excerpt = excerpt # category = category ,tags = tags,author = author ) messages.success(request,u'文章'+str(title)+'配置成功!!') 

return render(request,'specfile/post_add.html', {'error': error ,'category':category,'tags':tags,'author':author,})

--------这边想前台展示分类和标签的name (已实现)   如何实现 post 提交中带上 分类和标签的id   谢谢   

<label class="col-sm-2 control-label" for="ds_host"style="text-align: center;">分类</label><div class="col-sm-2"> <select type="text" id="disabledSelect" class="form-control" name="system_group_name" required> <option value="">请选择分类</option> {% for categorys in category %} <option value="{{ categorys }}">{{ categorys }}</option> {% endfor %} </select></div>

回复
逐殇小彬 逐殇小彬
2017-09-27 18:00:06

目前这边前段提交之后报错  "Column 'category_id' cannot be null"   

这个分类、标签、作者的id怎么获取?   

分类数据库 展示:  id name 1 规范文档  

文章数据库数据展示: 

id  title      body   created_time                 modified_time                   author_id         category_id

 7  测试1  测试1    2017-09-26 10:15:14   2017-09-26 10:15:34    2                       1

回复
zerowang
2017-08-30 13:36:13

感谢博主分享,非常详细实用,点赞!

回复
losetemp zerowang
2019-05-05 14:41:16

测试评论

回复
AngleMAXIN
2017-08-18 18:51:53

博主你好,在model里的我的 body = models.TextField()忘了写,modified_time写错了,以至于0001_initial.py里的内容也错了,所以在存数据的时候,报了很多错误,请问我该怎么做?可以直接直接在0001_initial.py改过来吗?如果不行,又该怎么做,而且我的Tag.objects.all()、Category.objects.all()都显示的是两个相同的结果,我要删除吗?

回复
追梦人物 AngleMAXIN
2017-08-18 19:31:30

你可以把 0001_initial.py 删掉,数据库删掉,重新生成一遍。

回复
AngleMAXIN 追梦人物
2017-08-18 19:40:47

只删掉0001_initial.py和db.sqlite3这两个文件吗?其他的还用改吗?

回复
AngleMAXIN AngleMAXIN
2017-08-18 19:57:35

好的,明白了

回复
格桑梅朵的嘘嘘石
2017-07-28 16:32:56

谢谢楼主大大的博客!

回复
smilexnan
2017-07-27 22:48:14

打卡打卡

回复
cnzaobao
2017-07-10 10:42:20

你好,如果数据库是这样,
一级目录: 中国, 美国,日本
二级目录: 城市,城镇,乡村 他们都属于中国美国日本的子目录。
如果按foreignkey建立,二级目录只能属于一个国家,按manytomany建立则通过二级目录无法找到一级目录的国家,这个问题有没有办法解决?
我不能因为按150国家建立150具不同的应用

回复
追梦人物 cnzaobao
2017-07-10 11:26:46

按manytomany建立则通过二级目录无法找到一级目录的国家,这是可以的。方法和 foreignkey 是类似的,可以正向查询二级目录也可以反向查询一级目录。django model 部分关于 foreignkey 和 manytomany 有如何查询数据的详细示例。

回复
cnzaobao 追梦人物
2017-07-10 14:15:16

是有详细示例,但是我无法找到二级目录,比如,城市 上面的一级目录,使用上面的方法,会把所有的含有城市的国家全部列出来。但我只是想显示这个城市是属于美国的,只把美国显示出来,做为面包屑导航用。
按foreignkey的方法
{{category.country.get_absolute_url }} {{category.country.name}},但这样得到的一个空值。无法定位到确切的country上面去。

回复
wenmingxing1
2017-07-06 19:15:48

博主你好,我把代码手动敲进了models.py可以通过编译判断有没有错误吗?

回复
追梦人物 wenmingxing1
2017-07-06 19:51:19

python 是一门脚本型语言,没有运行前的编译环节。

回复
hyangzz
2017-07-02 19:15:27

"Category" is not defined为什么会报错

回复
追梦人物 hyangzz
2017-07-03 12:09:08

说明你没有定义或者没有 import

回复
abulimity
2017-06-21 16:11:41

请问楼主,我的Post pk不知道为什么是从2开始的 ,然后导致跟Category关联的时候出现找不到的情况怎么办呢?

回复
追梦人物 abulimity
2017-06-21 17:16:03

删掉数据库重新创建一下试试,你可能因为某些操作破坏了数据库完整性。

回复
JustBreaking
2017-06-09 16:18:10

教程写的太好啦,感谢楼主!

回复
追梦人物 JustBreaking
2017-06-09 16:23:07

谢谢阅读ヾ(✿゚▽゚)ノ

回复
不会取名字的张摩叽
2017-05-17 16:51:40

不错,准备动手开始啦

回复