文中涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫
設計部落格的資料庫表結構
部落格最主要的功能就是展示我們寫的文章,它需要從某個地方獲取部落格文章資料才能把文章展示出來,通常來說這個地方就是資料庫。我們把寫好的文章永久地儲存在資料庫裡,當使用者訪問我們的部落格時,django 就去資料庫裡把這些資料取出來展現給使用者。
部落格的文章應該含有標題、正文、作者、發表時間等資料。一個更加現代化的部落格文章還希望它有分類、標籤、評論等。為了更好地儲存這些資料,我們需要合理地組織資料庫的表結構。
我們的部落格初級版本主要包含部落格文章,文章會有分類以及標籤。一篇文章只能有一個分類,但可以打上很多標籤。
資料庫儲存的資料其實就是表格的形式,例如儲存部落格文章的資料庫表長這個樣子:
文章 id | 標題 | 正文 | 發表時間 | 分類 | 標籤 |
---|---|---|---|---|---|
1 | title 1 | text 1 | 2019-7-1 | django | django 學習 |
2 | title 2 | text 2 | 2019-7-2 | django | django 學習 |
3 | title 3 | text 3 | 2019-7-3 | 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
類,類名為 Category
。Category
類有一個屬性 name
,它是 models.CharField
的一個例項。
這樣,django 就可以把這個類翻譯成資料庫的操作語言,在資料庫裡建立一個名為 category 的表格,這個表格的一個列名為 name。還有一個列 id,雖然沒有顯示定義,但 django 會為我們自動建立。可以看出從 Python 程式碼翻譯成資料庫語言時其規則就是一個 Python 類對應一個資料庫表格,類名即表名,類的屬性對應著表格的列,屬性名即列名。
我們需要 3 個表格:文章(Post)、分類(Category)以及標籤(Tag),下面就來分別編寫它們對應的 Python 類。模型的程式碼通常寫在相關應用的 models.py 檔案裡。已經在程式碼中做了詳細的註釋,說明每一句程式碼的含義。但如果你在移動端下閱讀不便的話,也可以跳到程式碼後面看正文的裡的講解。
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/2.2/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,即一
# 對多的關聯關係。且自 django 2.0 以後,ForeignKey 必須傳入一個 on_delete 引數用來指定當關聯的
# 資料被刪除時,被關聯的資料的行為,我們這裡假定當某個分類被刪除時,該分類下全部文章也同時被刪除,因此 # 使用 models.CASCADE 引數,意為級聯刪除。
# 而對於標籤來說,一篇文章可以有多個標籤,同一個標籤下也可能有多篇文章,所以我們使用
# ManyToManyField,表明這是多對多的關聯關係。
# 同時我們規定文章可以沒有標籤,因此為標籤 tags 指定了 blank=True。
# 如果你對 ForeignKey、ManyToManyField 不瞭解,請看教程中的解釋,亦可參考官方文件:
# https://docs.djangoproject.com/en/2.2/topics/db/models/#relationships
category = models.ForeignKey(Category, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, blank=True)
# 文章作者,這裡 User 是從 django.contrib.auth.models 匯入的。
# django.contrib.auth 是 django 內建的應用,專門用於處理網站使用者的註冊、登入等流程,User 是
# django 為我們已經寫好的使用者模型。
# 這裡我們通過 ForeignKey 把文章和 User 關聯了起來。
# 因為我們規定一篇文章只能有一個作者,而一個作者可能會寫多篇文章,因此這是一對多的關聯關係,和
# Category 類似。
author = models.ForeignKey(User, on_delete=models.CASCADE)
部落格模型程式碼程式碼詳解
首先是 Category
和 Tag
類,它們均繼承自 model.Model
類,這是 django 規定的。Category
和 Tag
類均有一個name
屬性,用來儲存它們的名稱。由於分類名和標籤名一般都是用字串表示,因此我們使用了 CharField
來指定 name
的資料型別,同時 max_length
引數則指定 name
允許的最大長度,超過該長度的字串將不允許存入資料庫。除了 CharField
,django 還為我們提供了更多內建的資料型別,比如時間型別 DateTimeField
、整數型別 IntegerField
等等。
提示:
在本教程中我們會教你這些型別的使用方法,但以後你開發自己的專案時,你就需要通過閱讀 django 官方文件 關於欄位型別的介紹 來了解有哪些資料型別可以使用以及如何使用它們。
Post
類也一樣,必須繼承自 model.Model
類。文章的資料庫表稍微複雜一點,主要是列更多,我們指定了這些列:
title
:文章的標題,資料型別是CharField
,允許的最大長度max_length = 70
。body
:文章正文,我們使用了TextField
。比較短的字串儲存可以使用CharField
,但對於文章的正文來說可能會是一大段文字,因此使用TextField
來儲存大段文字。created_time
和modified_time
:這兩個列分別表示文章的建立時間和最後一次修改時間,儲存時間的列用DateTimeField
資料型別。excerpt
:文章摘要,可以沒有文章摘要,但預設情況下CharField
要求我們必須存入資料,否則就會報錯。指定CharField
的blank=True
引數值後就可以允許空值了。category
和tags
:分類與標籤,分類與標籤的模型我們已經定義在上面。我們把文章對應的資料庫表和分類、標籤對應的資料庫表關聯了起來,但是關聯形式稍微有點不同。我們規定一篇文章只能對應一個分類,但是一個分類下可以有多篇文章,所以我們使用的是ForeignKey
,即一對多的關聯關係。且自 django 2.0 以後,ForeignKey 必須傳入一個 on_delete 引數用來指定當關聯的資料被刪除時,被關聯的資料的行為,我們這裡假定當某個分類被刪除時,該分類下全部文章也同時被刪除,因此使用 models.CASCADE 引數,意為級聯刪除。而對於標籤來說,一篇文章可以有多個標籤,同一個標籤下也可能有多篇文章,所以我們使用
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 官方參考資料。
假如你對多對一關係和多對多關係還存在一些困惑,強烈建議閱讀官方文件對這兩種關係的說明以及更多官方的例子以加深理解:
歡迎關注 HelloGitHub 公眾號,獲取更多開源專案的資料和內容