今天會實作資料庫的結構。我們總共需要實作三個 table 的 scheme,分別是 users
、posts
、comments
。
等等會提到一些資料庫的概念,我不會細講,因為要真的細講的話大概需要好幾天的文章,此處我們先知道怎麼用就好。在這篇文章的最後我會補充一下一些用法。
我們需要把這些 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
,此外,他還加上 unique
和 nullable
兩個參數,分別代表是否唯一及可否為空,那在此處當然是要唯一並不可為空,畢竟是使用者名稱,不這樣做也有點奇怪。password
是存密碼用的,當然,我們存的不是明文,而是 hash 過的結果,這個部分會在等等看到。他使用的一樣是 db.String
,而他也不能為空,但可以重複 (不設定就沒有限制)。email
是電子郵件信箱,基本上我們會在註冊的時候發一封信給註冊者,然後就再也不會用到了,這就是 Flask-Mail 在這系列唯一的戲份。沒什麼好懷疑地,他是字串,然後不能重複,也不能為空。introduction
是存使用者的自我介紹用的,畢竟我們的主題是部落格系統,讓作者可以自我介紹應該十分合理。這邊就不做限制,可以為空也可以跟別人一樣,雖然在正常情況下要跟別人一樣還蠻不容易的。is_admin
是用來說明此使用者是不是管理員的,所以他使用的是 db.Boolean
,也就是 True
或是 False
,他不能為空,然後當然可以重複,最後他有一個 default=False
,這代表沒有設定的時候他就會自動當他是 False
。register_time
是用來存註冊時間。他使用的是 db.Datetime
這個資料庫型別,這個型別是用來對付 python 的 datetime.datetime
,所以我們後面的 default
是 datetime.datetime.now
。如果要用 datetime.date
的話,他搭配的資料型別是 db.Date
。這裡要特別注意他傳入的是 datetime.datetime.now
這個函式,而非 datetime.datetime.now()
。posts
和 comments
用到了 db.relationship
,他分別是和在下兩個 table 裡面會看到的 posts.id
和 comments.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
會發生甚麼事。我們用剛剛的三個 table 舉例,但不會有範例程式碼。
假設現在有一個使用者叫做 user1
,他是一個從資料庫撈出來的實體,也就是說我們可以存取 user1.username
、user1.email
,當然,我們也可以存取 user1.posts
、user1.comments
。
再假設他有兩篇文章,標題分別是 article1
、article2
,又有兩則留言,內容分別是 comment1
、comment2
。
這時候我們再回到 user1
,他的 user1.posts
是一個長度為 2 的 list,內容是那兩個文章,我們可以使用 user1.posts[0]
來直接抓出第一篇文章的實體,所以我們就可以用這個實體來存取這篇文章的資料,user1.posts[0].title
就會是 article1
,同樣地,user1.comments[1].content
就會是 comment2
。
unable to create autoincrementing primary key with flask-sqlalchemy
Column and Data Types
SQLAlchemy default DateTime
flask-sqlalchemy Quickstart
外鍵 Foreign key (FK) 是什麼?
Day 32 資料庫正規化 (一 ~ 三)