文中涉及的示例代碼,已同步更新到 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
類,它們均繼承自 models.Model
類,這是 django 規定的。Category
和 Tag
類均有一個 name
屬性,用來存儲它們的名稱。由於分類名和標簽名一般都是用字符串表示,因此我們使用了 CharField
來指定 name
的數據類型,同時 max_length
參數則指定 name
允許的最大長度,超過該長度的字符串將不允許存入數據庫。除了 CharField
,django 還為我們提供了更多內置的數據類型,比如時間類型 DateTimeField
、整數類型 IntegerField
等等。
提示:
在本教程中我們會教你這些類型的使用方法,但以后你開發自己的項目時,你就需要通過閱讀 django 官方文檔 關於字段類型的介紹 來了解有哪些數據類型可以使用以及如何使用它們。
Post
類也一樣,必須繼承自 models.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 公眾號,獲取更多開源項目的資料和內容