Day 12 实作资料库

前言

今天会实作资料库的结构。我们总共需要实作三个 table 的 scheme,分别是 userspostscomments

等等会提到一些资料库的概念,我不会细讲,因为要真的细讲的话大概需要好几天的文章,此处我们先知道怎麽用就好。在这篇文章的最後我会补充一下一些用法。

资料库结构

我们需要把这些 model 都放在昨天提到的 app/database/models.py 里面。我们一个一个来看,先从 users 开始。先不要管最前面的引入,我们晚点再看。

from werkzeug.security import generate_password_hash, check_password_hash

class Users(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String, unique=True, nullable=False)
    password = db.Column(db.String, nullable=False)
    email = db.Column(db.String, unique=True, nullable=False)
    introduction = db.Column(db.String)
    is_admin = db.Column(db.Boolean, nullable=False, default=False)
    register_time = db.Column(
        db.DateTime, default=datetime.datetime.now, nullable=False
    )
    posts = db.relationship("Posts")
    comments = db.relationship("Comments")

    def __init__(self, username, password, email, introduction=None, is_admin=False):
        self.username = username
        self.password = generate_password_hash(password)
        self.email = email
        self.introduction = introduction
        self.is_admin = is_admin

    def check_password(self, password):
        return check_password_hash(self.password, password)

我们宣告了一个叫做 Users 的物件,而他是继承自 db.Model,这个 db 就是昨天用到的那个。我们在这里面放了很多变数,除了 __tablename__ 之外 (很明显他就代表这个 table 的名字),每一个变数都是一个资料库的 column,一样一个一个分开来看。

  • id 是这个 table 的主键 (primary key),如同他後面的参数 primary_key=True 所示。在他前面有一个 db.Integer,他表示了这个 column 的资料库型别。有了前面这两个参数,autoincrement (自动把 id 流水号) 就会自动存在。
  • username 是用来存使用者名称的,他使用的资料库型别是 db.String,此外,他还加上 uniquenullable 两个参数,分别代表是否唯一及可否为空,那在此处当然是要唯一并不可为空,毕竟是使用者名称,不这样做也有点奇怪。
  • password 是存密码用的,当然,我们存的不是明文,而是 hash 过的结果,这个部分会在等等看到。他使用的一样是 db.String,而他也不能为空,但可以重复 (不设定就没有限制)。
  • email 是电子邮件信箱,基本上我们会在注册的时候发一封信给注册者,然後就再也不会用到了,这就是 Flask-Mail 在这系列唯一的戏份。没什麽好怀疑地,他是字串,然後不能重复,也不能为空。
  • introduction 是存使用者的自我介绍用的,毕竟我们的主题是部落格系统,让作者可以自我介绍应该十分合理。这边就不做限制,可以为空也可以跟别人一样,虽然在正常情况下要跟别人一样还蛮不容易的。
  • is_admin 是用来说明此使用者是不是管理员的,所以他使用的是 db.Boolean,也就是 True 或是 False,他不能为空,然後当然可以重复,最後他有一个 default=False,这代表没有设定的时候他就会自动当他是 False
  • register_time 是用来存注册时间。他使用的是 db.Datetime 这个资料库型别,这个型别是用来对付 python 的 datetime.datetime,所以我们後面的 defaultdatetime.datetime.now。如果要用 datetime.date 的话,他搭配的资料型别是 db.Date。这里要特别注意他传入的是 datetime.datetime.now 这个函式,而非 datetime.datetime.now()
  • postscomments 用到了 db.relationship,他分别是和在下两个 table 里面会看到的 posts.idcomments.id 有关联,简单来说我们可以用他找到这个使用者底下全部的文章和留言。

看完有哪些栏位後,来看看 __init__ 这个函式。我们在新增一个使用者的时候,会先用这个 class 造出一个真实的使用者,然後再把造出来的使用者物件加入资料库,所以我们需要这个 __init__ 来做初始化,把传入的值一个一个对应到 self 里面。在这里我把 is_admin 直接预设成 False,所以这样上面的 is_admin 其实就不需要 default=False 了。这里比较特别的是我们用的 generate_password_hash 这个函式,他当然是需要引入的,也就是在最前面的部分。他会把使用者输入的明文密码杂凑,然後存入资料库,然後需要检查的时候就用一起引入的 check_password_hash 来检查,但这边我用另外一个函式 check_password 来包装一下,外面会用到的话就直接套用这个函式即可。

接下来就来看看剩下两个。

class Posts(db.Model):
    __tablename__ = "posts"
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    title = db.Column(db.String, nullable=False, unique=True)
    description = db.Column(db.String)
    content = db.Column(db.String, nullable=False)
    comments = db.relationship("Comments")
    time = db.Column(
        db.DateTime, default=datetime.datetime.now, nullable=False
    )

    def __init__(self, author_id, title, description, content):
        self.author_id = author_id
        self.title = title
        self.description = description
        self.content = content


class Comments(db.Model):
    __tablename__ = "comments"
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey("posts.id"), nullable=False)
    content = db.Column(db.String, nullable=False)
    time = db.Column(
        db.DateTime, default=datetime.datetime.now, nullable=False
    )

    def __init__(self, author_id, post_id, content):
        self.author_id = author_id
        self.post_id = post_id
        self.content = content

我们分别说明他们两个的各栏位用途,跟刚刚类似的会直接跳过,首先是 posts,他会储存全部的文章资讯,包括标题、内容、作者等等。

  • author_id 是这篇文章的作者的使用者 ID (users.id),他是把 users.id 当外键 (foreign key),然後让 users.posts 可以存取到 posts 这个 table 的资料。而他当然不能为空 (如果使用者遭到删除,文章和留言也会跟着删除)。
  • title 是这篇文章的标题,不能为空,也不能重复。
  • description 是这篇文章的小标,有没有都没差。
  • content 是内文,当然不能为空。
  • comments 又是一个 relationship,他和下面的 comments 有关连,所以可想而知,comments 里面一定也有一个跟这个 table 有关的外键。

最後看到 comments 这个 table,他是用来储存留言资讯的。

  • author_id 跟上面 posts 里面的一样,就是用 users.id 作为外键让 users.comments 可以存取这个 table。当然也不能为空。
  • post_id 就是用到刚刚 posts 那个 table 的 relationship,让 posts.comments 可以存取到其下的留言。

relationship

在文章的最後,我们来看一下这个 relationship 会发生甚麽事。我们用刚刚的三个 table 举例,但不会有范例程序码。

假设现在有一个使用者叫做 user1,他是一个从资料库捞出来的实体,也就是说我们可以存取 user1.usernameuser1.email,当然,我们也可以存取 user1.postsuser1.comments

再假设他有两篇文章,标题分别是 article1article2,又有两则留言,内容分别是 comment1comment2

这时候我们再回到 user1,他的 user1.posts 是一个长度为 2 的 list,内容是那两个文章,我们可以使用 user1.posts[0] 来直接抓出第一篇文章的实体,所以我们就可以用这个实体来存取这篇文章的资料,user1.posts[0].title 就会是 article1,同样地,user1.comments[1].content 就会是 comment2

References

unable to create autoincrementing primary key with flask-sqlalchemy
Column and Data Types
SQLAlchemy default DateTime
flask-sqlalchemy Quickstart
外键 Foreign key (FK) 是什麽?
Day 32 资料库正规化 (一 ~ 三)


<<:  Day12-救世主Promise

>>:  Day03基本架构(HTML)

Day04 - Python基本语法 Part 1

今天开始将进行Python基本语法练习,因大部分语法跟很多程序语言相似,故这个部分将主要以笔记方式注...

成为工具人应有的工具包-28 LastActivityView

LastActivityView 今天来认识这个看就知道是看这台电脑上一步做了啥动作的工具! 调查的...

Day30- 最後完成代办事项 To DO List 小工具

大家好,来到 IT 铁人赛最後一天了,终於要进入尾声了! 今天我们要整合这三十天来所学到的知识量,...

[DAY6]Channal access token是什麽?

对於网上可用的服务,通常使用通过ID和密码进行身份验证,以验证使用者是否有权使用该服务。LINE 开...