iT邦幫忙

2021 iThome 鐵人賽

DAY 12
0
Modern Web

Flask系列 第 12

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 資料庫正規化 (一 ~ 三)


上一篇
Day 11 實作 create_app
下一篇
Day 13 實作 manage.py
系列文
Flask30

尚未有邦友留言

立即登入留言