登录        注册
 本站基于Django1.11开发,源码已共享在 Github 欢迎 Fork、Star.近期站点微调频繁,如遇访问异常希望见谅,若能在评论区留言,或者发送邮件指出BUG,更是万分感谢

Django个人博客开发六 | 数据库结构设计

Django stormsha 3626浏览 0评论
本渣渣不专注技术,只专注使用技术,不是一个资深的coder,是一个不折不扣的copier

1、功能分析

对于初学者数据库设计是建站比较难的点,数据库表的产生是有网站功能决定的,但是不能有一个功能就创建一张表吧,所以这里需要一点数据库的基础知识

推荐阅读:Mysql数据库基础知识   数据库表设计

我们先从功能上分析,看看这个博客网站需要建立哪些表,每个表中都需要什么字段

首先,最主要的是我们的博文表,名字可以直接叫做 article,这个表中,肯定要包括以下几点:

博文的标题、博文的内容、博文的发表时间、博文的修改时间、博文的分类、博文的阅读量、博文喜欢量、博文作者 等

针对博文的分类,我们可以参考csdn博客系统,一篇博文只能有一个分类,但是可以有多个标签,比如我现在写的这篇博文,可以分类到 django 下,但是它可以有多个标签:django、博客、数据库、开发……

考虑到每一篇博文都只能有一个分类,而一个分类下是可以包含很多博文的,因此分类与博文是一对多的关系,此时应当使用外键来进行关联。而一篇博文可以有多个标签, 每个标签也可以包含多个博文,因此,标签与博文是多对多的关系。关于一对多与多对多的知识话题,这里就不再展开了,不了解的查看 Django文档 与相关资料。

推荐阅读:Django官方文档   Django中文文档

针对网站优化,那么一个网站最基本的SEO就是设置TDK

    T 网站页面 title

    D 网站页面描述

    K 网站页面Keywords,也即网站涵盖的主题

则需要一个页面关键字表 Keyword,一个页面可能包含多个主题,一个主题可能在多个页面出现,所以应该是多对多的关系 暂且称作 Keyword

崔庆才博客导航栏菜单存在下拉菜单,下拉菜单即是博文分类,这里的导航栏菜单也需要一个表,因为这个表就是为了给博文分类归类的 暂且称作 Bigcategory

观察崔庆才博客导航栏下边还一个公告,公告也需要一个表,它和任何表都没关系 暂且称作 Activate

公告下边是幻灯片,思考了一下,这个幻灯片应该和别的表也没啥关系,暂且称作 Carousel

再看看,右侧还有一个友情链接功能,需要一个表,友链和其它表也没啥关系 暂且称作 FriendLink

因此,通过上述分析,我们可以确定出这些数据表,博客(Article)、分类(Category)与标签(Tag)、导航(Bigcategory)、文章关键词 (Keyword)、公告(Activate)、幻灯片(Carousel)、友链(FriendLink)

2、编写 storm 应用模型

blog -> storm -> models.py

头文件

    from django.db import models
    from django.conf import settings
    from django.shortcuts import reverse

    import markdown
    import re

由于 Article 表包含外键与多对多关系,因此首先应当建立另外两个表:

分类(Category)表的创建:

由于 Category 分类表包含外键,因此首先需要创建导航菜单表 Bigcategory

    # 网站导航菜单栏分类表
    class BigCategory(models.Model):
        # 导航名称
        name = models.CharField('文章大分类', max_length=20)
        # 用作文章的访问路径,每篇文章有独一无二的标识,下同
        slug = models.SlugField(unique=True)
        # 分类页描述
        description = models.TextField('描述', max_length=240, default=settings.SITE_DESCRIPTION,
                                     help_text='用来作为SEO中description,长度参考SEO标准')
        # 分类页Keywords
        keywords = models.TextField('关键字', max_length=240, default=settings.SITE_KEYWORDS,
                                  help_text='用来作为SEO中keywords,长度参考SEO标准')

        class Meta:
            verbose_name = '大分类'
            verbose_name_plural = verbose_name

        def __str__(self):
            return self.name


    # 导航栏,分类下的下拉擦菜单分类
    class Category(models.Model):

        # 分类名字
        name = models.CharField('文章分类', max_length=20)
        # slug 用作分类路径,对一无二
        slug = models.SlugField(unique=True)
        # 分类栏目页描述
        description = models.TextField('描述', max_length=240, default=settings.SITE_DESCRIPTION,
                                     help_text='用来作为SEO中description,长度参考SEO标准')

        # 对应导航菜单外键
        bigcategory = models.ForeignKey(BigCategory, verbose_name='大分类')

        class Meta:
            verbose_name = '分类'
            verbose_name_plural = verbose_name
            ordering = ['name']

        def __str__(self):
            return self.name

        def get_absolute_url(self):
            return reverse('blog:category', kwargs={'slug': self.slug, 'bigslug': self.bigcategory.slug})

        def get_article_list(self):
            return Article.objects.filter(category=self)

标签(Tag)表的创建:

# 文章标签
class Tag(models.Model):
    name = models.CharField('文章标签', max_length=20)
    slug = models.SlugField(unique=True)
    description = models.TextField('描述', max_length=240, default=settings.SITE_DESCRIPTION,
                                 help_text='用来作为SEO中description,长度参考SEO标准')

    class Meta:
        verbose_name = '标签'
        verbose_name_plural = verbose_name
        ordering = ['id']

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('blog:tag', kwargs={'tag': self.name})

    def get_article_list(self):
        """返回当前标签下所有发表的文章列表"""
        return Article.objects.filter(tags=self)

标签(Keyword)表的创建:

    # 文章关键词,用来作为 SEO 中 keywords
    class Keyword(models.Model):
        name = models.CharField('文章关键词', max_length=20)

        class Meta:
            verbose_name = '关键词'
            verbose_name_plural = verbose_name
            ordering = ['name']

        def __str__(self):
            return self.name

博客(Article)表的创建:

    # 文章
    class Article(models.Model):
        # 文章默认缩略图
        IMG_LINK = '/static/images/summary.jpg'
        # 文章作者
        author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者')
        title = models.CharField(max_length=150, verbose_name='文章标题')
        summary = models.TextField('文章摘要', max_length=230, default='文章摘要等同于网页description内容,请务必填写...')
        # 文章内容
        body = models.TextField(verbose_name='文章内容')
        img_link = models.CharField('图片地址', default=IMG_LINK, max_length=255)
        create_date = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
        update_date = models.DateTimeField(verbose_name='修改时间', auto_now=True)
        views = models.IntegerField('阅览量', default=0)
        loves = models.IntegerField('喜爱量', default=0)
        # 文章唯一标识符
        slug = models.SlugField(unique=True)
        category = models.ForeignKey(Category, verbose_name='文章分类')
        tags = models.ManyToManyField(Tag, verbose_name='标签')
        keywords = models.ManyToManyField(Keyword, verbose_name='文章关键词',
                                        help_text='文章关键词,用来作为SEO中keywords,最好使用长尾词,3-4个足够')

        class Meta:
            verbose_name = '文章'
            verbose_name_plural = verbose_name
            ordering = ['-create_date']

        def __str__(self):
            return self.title[:20]

        def get_absolute_url(self):
            return reverse('blog:article', kwargs={'slug': self.slug})

        def body_to_markdown(self):
            return markdown.markdown(self.body, extensions=[
                'markdown.extensions.extra',
                'markdown.extensions.codehilite',
            ])

        def update_views(self):
            self.views += 1
            self.save(update_fields=['views'])

        def get_pre(self):
            return Article.objects.filter(id__lt=self.id).order_by('-id').first()

        def get_next(self):
            return Article.objects.filter(id__gt=self.id).order_by('id').first()

为什么要用slug给文章起别名呢?这个可能暂且就认为只是为了提升逼格吧

公告(Activate)表的创建:

    # 公告
    class Activate(models.Model):
        text = models.TextField('公告', null=True)
        is_active = models.BooleanField('是否开启', default=False)
        add_date = models.DateTimeField('提交日期', auto_now_add=True)

        class Meta:
            verbose_name = '公告'
            verbose_name_plural = verbose_name

        def __str__(self):
            return self.id

幻灯片(Carousel)表的创建:

    # 幻灯片
    class Carousel(models.Model):
        number = models.IntegerField('编号', help_text='编号决定图片播放的顺序,图片不要多于5张')
        title = models.CharField('标题', max_length=20, blank=True, null=True, help_text='标题可以为空')
        content = models.CharField('描述', max_length=80)
        img_url = models.CharField('图片地址', max_length=200)
        url = models.CharField('跳转链接', max_length=200, default='#', help_text='图片跳转的超链接,默认#表示不跳转')

        class Meta:
            verbose_name = '图片轮播'
            verbose_name_plural = verbose_name
            # 编号越小越靠前,添加的时间约晚约靠前
            ordering = ['number', '-id']

        def __str__(self):
            return self.content[:25]

友链(FriendLink)表的创建:

    # 友情链接表
    class FriendLink(models.Model):
        name = models.CharField('网站名称', max_length=50)
        description = models.CharField('网站描述', max_length=100, blank=True)
        link = models.URLField('友链地址', help_text='请填写http或https开头的完整形式地址')
        logo = models.URLField('网站LOGO', help_text='请填写http或https开头的完整形式地址', blank=True)
        create_date = models.DateTimeField('创建时间', auto_now_add=True)
        is_active = models.BooleanField('是否有效', default=True)
        is_show = models.BooleanField('是否首页展示', default=False)

        class Meta:
            verbose_name = '友情链接'
            verbose_name_plural = verbose_name
            ordering = ['create_date']

        def __str__(self):
            return self.name

        def get_home_url(self):
            """提取友链的主页"""
            u = re.findall(r'(http|https://.*?)/.*?', self.link)
            home_url = u[0] if u else self.link
            return home_url

        def active_to_false(self):
            self.is_active=False
            self.save(update_fields=['is_active'])

        def show_to_false(self):
            self.is_show = True
            self.save(update_fields=['is_show'])

【提示】—— storm -> models.py 中出现的 AUTH_USER_MODEL、SITE_DESCRIPTION、AUTH_USER_MODEL、SITE_KEYWORDS,把它们当作默认字段放在 setting.py 中,便于管理

blog -> blog -> settings.py

    # 网站描述,用于SEO
    SITE_DESCRIPTION = "StormSha的个人网站,记录生活的瞬间,分享学习的心得,感悟生活,留住感动,静静寻觅生活的美好"

    # 网站关键词,用于SEO
    SITE_KEYWORDS = "StormSha,静觅,网络,IT,技术,博客,Python"

在文件首部 # 引入 setings.py 文件,即可使用这些字段

from django.conf import settings

以上的类名代表了数据库表名,且继承了models.Model,类里面的字段代表数据表中的字段(name),数据类型则由CharField(相当于varchar)、DateField(相当于datetime),max_length 参数限定长度。

3、几点注意

关于博客表有以下几点需要注意的:

标题应当限定长度,我们设定最大值为100

内容不用限定长度,因此用的是TextField字段

修改时间直接设定成,auto_now=True,在你修改时,会自动变成当前时间。

关于ForeignKey与ManyToManyField,请自行查看相关文档资料

Django官方文档   Django中文文档

4、编写 user 用户应用模型

另外 文章有一个作者外键

author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者')

这个外键是网站注册用户,这样用户也可以有发文权限,考虑到以后会扩展用户功能,所以这里还是创建一个用户app比较好

创建app可以参考:Django个人博客开发五 | 创建第一个APP 不要忘了创建app后去配置文件里注册用户app,这里不再赘述,创建好 user 应用后,编写用户模型

blog -> user -> models.py

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    from imagekit.models import ProcessedImageField
    from imagekit.processors import ResizeToFill

    # 继承 AbstractUser ,django 自带用户类,扩展用户个人网站字段,用户头像字段
    class Ouser(AbstractUser):
        # 扩展用户个人网站字段
        link = models.URLField('个人网址', blank=True, help_text='提示:网址必须填写以http开头的完整形式')
        # 扩展用户头像字段
        avatar = ProcessedImageField(upload_to='avatar/%Y/%m/%d',
                                     default='avatar/default.png',
                                     verbose_name='头像',
                                     processors=[ResizeToFill(80, 80)]
                                     )

        class Meta:
            verbose_name = '用户' # 定义网站管理后台表名
            verbose_name_plural = verbose_name
            ordering = ['-id']


        def __str__(self):
            return self.username

5、编写 user 用户应用模型

Tools -> run manage.py task 下依次执行命令:

    & makemigrations
    & migrate

即可将新建的这些表添加到我们的数据库 blog 中:

    auth_group, 
    auth_group_permissions,
    auth_permission,
    auth_user_groups,
    auth_user_user_permissions, 
    django_admin_log, 
    django_content_type, 
    django_migrations, 
    django_session,
    storm_activate,         # 公告表 
    storm_article,          # 新增的博文表
    storm_article_keywords, # 这个是博文与关键词的多对多关系表
    storm_article_tags,     # 这个是博文与标签的多对多关系表
    storm_bigcategory,      # 导航菜单大分类表
    storm_carousel,         # 幻灯片
    storm_category,         # 新增的分类表
    storm_friendlink,       # 友情链接
    storm_keyword,          # 关键词表
    storm_tag              # 新增的标签表
    user_ouser,             # 新增扩展用户表
    user_ouser_groups,      # 自动产生
    user_ouser_user_permissions # 自动产生

最后两个表示用户表继承了,Django自带的用户models产生的用户关系表

需要说明的是,这里我们只给出了最开始设计时考虑到的情况,在后续开发过程中,可以随时对其进行变更。当数据表信息变动时

Tools -> run manage.py task 下依次执行命令:

    & makemigrations
    & migrate

————————————————————————————————————————————————————————————————————————————————————

转载请注明: StormSha » Django个人博客开发六 | 数据库结构设计

喜欢 (315) or 分享 ( 0)

联系我请直接在公众号留言~

扫码或搜索:进击的Coder

进击的Coder

微信公众号 扫一扫关注

想结交更多的朋友吗?

来进击的Coder瞧瞧吧

进击的Coder

QQ群号 213037458 立即加入

进击的Coder灌水太多?

这里是纯粹的技术领地

激进的Coder

QQ群号 781587463 立即加入

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请狠狠点击下面的

发表我的评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(7)个小伙伴在吐槽
  1. 没看明白get_absolute_url方法中 reverse()的用处

    Ricool2019-04-25 16:51:27回复
  2. reverse(),这个就是django提供的一个反向解析路由的函数,其实它不仅仅可以反向解析路由,还可以解析视图,更多知识可以谷歌搜索,或者看官方文档,这里我说一下路由反向解析,先看一下,前端求其地址,以首页展示的文章为例

    # 方法一
    <a href="/article/{{ obj.slug }}"> {{ obj.title }}</a>
    # 方法二
    <a href="{% 'blog:article' obj.slug %}"> {{ obj.title }}</a>
    # 方法三
    <a href="{{ obj.get_absolute_url }}"> {{ obj.title }}</a>
    

    reverse()这个函数在反向解析中的作用我个人理解是:
    在视图中返回给前端模板的文章对象中,已经携带了get_absolute_url 这个字段, get_absolute_url 在 html 中生成url。reverse()的作用就是把方法二中的 {% 'blog:article' obj.slug %} 在models中以另一种方式定义,主路由 :'blog:article' 加参数:kwargs={'slug': self.slug},reverse 把这些信息解析为一个url去匹配路由请求视图实现业务逻辑,实现前端http请求软定义,尽量少地出现牵一发动全身的情况,这样你文章地址如果不想以slug为参数了,那么在models里修改即可,前端不需要改动。这也许就是 django 的设计艺术吧!
    个人理解,有不妥的地方欢迎留言交流:!:

    stormsha2019-04-25 20:53:25回复
    • 多些博主,等后面URL配置的时候再回来看应该会比较清晰吧

      Ricool2019-04-26 10:52:45回复
  3. 本章中,遗留了一个小问题
    在setting中必须配置AUTH_USER_MODEL文件
    配置方法:
    AUTH_USER_MODEL = '应用.类名'
    即 AUTH_USER_MODEL = 'user.Ouser'
    否则会报 auth.User.groups: (fields.E304)错误

    Ricool2019-04-26 10:51:32回复
  4. blog -> user -> models.py

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    from imagekit.models import ProcessedImageField
    from imagekit.processors import ResizeToFill
    
    
        在本章此处,导入imagekit时报错,pip install imagekit后,依然显示没有ProcessedImageField和ResizeToFill这两个类,请问up主该如何解决呀,有相关资料可以解决吗?
    
    niuxuliang9292019-04-28 11:52:17回复
    • 这样安装

      pip install django-imagekit
      
      stormsha2019-04-28 13:01:53回复
      • 谢谢啊

        niuxuliang9292019-04-28 20:43:59回复
      • 安装之后,生成迁移文件报错了
        PILKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.

        admin1112019-09-01 15:33:03回复
  5. 出入migrate命令后直接报这个错?django.db.migrations异常,值得什么问题?求解
    

    django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency user.0001_initial on database 'default'.

    yinww2019-05-08 14:42:02回复
    • 我也遇到同样的问题,django2.1.8+Python3.6,ubuntu18.04

      jingmi2019-05-16 14:40:09回复
    • 我也遇到同样的问题,django2.1.8+Python3.6,ubuntu18.04
      把mysql中的数据库删掉 然后重新 再运行即可

      jingmi2019-05-16 15:30:29回复