# 第五節:外鍵和表關系
# 外鍵和表關系
## 外鍵:
在`MySQL`中,表有兩種引擎,一種是`InnoDB`,另外一種是`myisam`。如果使用的是`InnoDB`引擎,是支持外鍵約束的。外鍵的存在使得`ORM`框架在處理表關系的時候異常的強大。因此這里我們首先來介紹下外鍵在`Django`中的使用。
類定義為`class ForeignKey(to,on_delete,**options)`。第一個參數是引用的是哪個模型,第二個參數是在使用外鍵引用的模型數據被刪除了,這個字段該如何處理,比如有`CASCADE`、`SET_NULL`等。這里以一個實際案例來說明。比如有一個`User`和一個`Article`兩個模型。一個`User`可以發表多篇文章,一個`Article`只能有一個`Author`,并且通過外鍵進行引用。那么相關的示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.CASCADE)
```
```
以上使用`ForeignKey`來定義模型之間的關系。即在`article`的實例中可以通過`author`屬性來操作對應的`User`模型。這樣使用起來非常的方便。示例代碼如下:
```
<pre class="calibre12">```
article = Article(title=<span class="hljs-string">'abc'</span>,content=<span class="hljs-string">'123'</span>)
author = User(username=<span class="hljs-string">'張三'</span>,password=<span class="hljs-string">'111111'</span>)
article.author = author
article.save()
<span class="hljs-title"># 修改article.author上的值</span>
article.author.username = <span class="hljs-string">'李四'</span>
article.save()
```
```
為什么使用了`ForeignKey`后,就能通過`author`訪問到對應的`user`對象呢。因此在底層,`Django`為`Article`表添加了一個`屬性名_id`的字段(比如author的字段名稱是author\_id),這個字段是一個外鍵,記錄著對應的作者的主鍵。以后通過`article.author`訪問的時候,實際上是先通過`author_id`找到對應的數據,然后再提取`User`表中的這條數據,形成一個模型。
如果想要引用另外一個`app`的模型,那么應該在傳遞`to`參數的時候,使用`app.model_name`進行指定。以上例為例,如果`User`和`Article`不是在同一個`app`中,那么在引用的時候的示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-title"># User模型在user這個app中</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-title"># Article模型在article這個app中</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
author = models.ForeignKey(<span class="hljs-string">"user.User"</span>,on_delete=models.CASCADE)
```
```
如果模型的外鍵引用的是本身自己這個模型,那么`to`參數可以為`'self'`,或者是這個模型的名字。在論壇開發中,一般評論都可以進行二級評論,即可以針對另外一個評論進行評論,那么在定義模型的時候就需要使用外鍵來引用自身。示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Comment</span><span class="hljs-params">(models.Model)</span>:</span>
content = models.TextField()
origin_comment = models.ForeignKey(<span class="hljs-string">'self'</span>,on_delete=models.CASCADE,null=<span class="hljs-keyword">True</span>)
<span class="hljs-title"># 或者</span>
<span class="hljs-title"># origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True)</span>
```
```
### 外鍵刪除操作:
如果一個模型使用了外鍵。那么在對方那個模型被刪掉后,該進行什么樣的操作。可以通過`on_delete`來指定。可以指定的類型如下:
1. `CASCADE`:級聯操作。如果外鍵對應的那條數據被刪除了,那么這條數據也會被刪除。
2. `PROTECT`:受保護。即只要這條數據引用了外鍵的那條數據,那么就不能刪除外鍵的那條數據。
3. `SET_NULL`:設置為空。如果外鍵的那條數據被刪除了,那么在本條數據上就將這個字段設置為空。如果設置這個選項,前提是要指定這個字段可以為空。
4. `SET_DEFAULT`:設置默認值。如果外鍵的那條數據被刪除了,那么本條數據上就將這個字段設置為默認值。如果設置這個選項,前提是要指定這個字段一個默認值。
5. `SET()`:如果外鍵的那條數據被刪除了。那么將會獲取`SET`函數中的值來作為這個外鍵的值。`SET`函數可以接收一個可以調用的對象(比如函數或者方法),如果是可以調用的對象,那么會將這個對象調用后的結果作為值返回回去。
6. `DO_NOTHING`:不采取任何行為。一切全看數據庫級別的約束。
**以上這些選項只是Django級別的,數據級別依舊是RESTRICT!**
- - - - - -
## 表關系:
表之間的關系都是通過外鍵來進行關聯的。而表之間的關系,無非就是三種關系:一對一、一對多(多對一)、多對多等。以下將討論一下三種關系的應用場景及其實現方式。
### 一對多:
1. 應用場景:比如文章和作者之間的關系。一個文章只能由一個作者編寫,但是一個作者可以寫多篇文章。文章和作者之間的關系就是典型的多對一的關系。
2. 實現方式:一對多或者多對一,都是通過`ForeignKey`來實現的。還是以文章和作者的案例進行講解。
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.CASCADE)
```
```
那么以后在給`Article`對象指定`author`,就可以使用以下代碼來完成:
```
<pre class="calibre12">```
article = Article(title=<span class="hljs-string">'abc'</span>,content=<span class="hljs-string">'123'</span>)
author = User(username=<span class="hljs-string">'zhiliao'</span>,password=<span class="hljs-string">'111111'</span>)
<span class="hljs-title"># 要先保存到數據庫中</span>
author.save()
article.author = author
article.save()
```
```
并且以后如果想要獲取某個用戶下所有的文章,可以通過`article_set`來實現。示例代碼如下:
```
<pre class="calibre12">```
user = User.objects.first()
<span class="hljs-title"># 獲取第一個用戶寫的所有文章</span>
articles = user.article_set.all()
<span class="hljs-keyword">for</span> article <span class="hljs-keyword">in</span> articles:
print(article)
```
```
### 一對一:
1. 應用場景:比如一個用戶表和一個用戶信息表。在實際網站中,可能需要保存用戶的許多信息,但是有些信息是不經常用的。如果把所有信息都存放到一張表中可能會影響查詢效率,因此可以把用戶的一些不常用的信息存放到另外一張表中我們叫做`UserExtension`。但是用戶表`User`和用戶信息表`UserExtension`就是典型的一對一了。
2. 實現方式:`Django`為一對一提供了一個專門的`Field`叫做`OneToOneField`來實現一對一操作。示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserExtension</span><span class="hljs-params">(models.Model)</span>:</span>
birthday = models.DateTimeField(null=<span class="hljs-keyword">True</span>)
school = models.CharField(blank=<span class="hljs-keyword">True</span>,max_length=<span class="hljs-params">50</span>)
user = models.OneToOneField(<span class="hljs-string">"User"</span>, on_delete=models.CASCADE)
```
```
在`UserExtension`模型上增加了一個一對一的關系映射。其實底層是在`UserExtension`這個表上增加了一個`user_id`,來和`user`表進行關聯,并且這個外鍵數據在表中必須是唯一的,來保證一對一。
### 多對多:
1. 應用場景:比如文章和標簽的關系。一篇文章可以有多個標簽,一個標簽可以被多個文章所引用。因此標簽和文章的關系是典型的多對多的關系。
2. 實現方式:`Django`為這種多對多的實現提供了專門的`Field`。叫做`ManyToManyField`。還是拿文章和標簽為例進行講解。示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
tags = models.ManyToManyField(<span class="hljs-string">"Tag"</span>,related_name=<span class="hljs-string">"articles"</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Tag</span><span class="hljs-params">(models.Model)</span>:</span>
name = models.CharField(max_length=<span class="hljs-params">50</span>)
```
```
在數據庫層面,實際上`Django`是為這種多對多的關系建立了一個中間表。這個中間表分別定義了兩個外鍵,引用到`article`和`tag`兩張表的主鍵。
- - - - - -
### related\_name和related\_query\_name:
#### related\_name:
還是以`User`和`Article`為例來進行說明。如果一個`article`想要訪問對應的作者,那么可以通過`author`來進行訪問。但是如果有一個`user`對象,想要通過這個`user`對象獲取所有的文章,該如何做呢?這時候可以通過`user.article_set`來訪問,這個名字的規律是`模型名字小寫_set`。示例代碼如下:
```
<pre class="calibre12">```
user = User.objects.get(name=<span class="hljs-string">'張三'</span>)
user.article_set.all()
```
```
如果不想使用`模型名字小寫_set`的方式,想要使用其他的名字,那么可以在定義模型的時候指定`related_name`。示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
<span class="hljs-title"># 傳遞related_name參數,以后在方向引用的時候使用articles進行訪問</span>
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.SET_NULL,null=<span class="hljs-keyword">True</span>,related_name=<span class="hljs-string">'articles'</span>)
```
```
以后在方向引用的時候。使用`articles`可以訪問到這個作者的文章模型。示例代碼如下:
```
<pre class="calibre12">```
user = User.objects.get(name=<span class="hljs-string">'張三'</span>)
user.articles.all()
```
```
如果不想使用反向引用,那么可以指定`related_name='+'`。示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
<span class="hljs-title"># 傳遞related_name參數,以后在方向引用的時候使用articles進行訪問</span>
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.SET_NULL,null=<span class="hljs-keyword">True</span>,related_name=<span class="hljs-string">'+'</span>)
```
```
以后將不能通過`user.article_set`來訪問文章模型了。
#### related\_query\_name:
在查找數據的時候,可以使用`filter`進行過濾。使用`filter`過濾的時候,不僅僅可以指定本模型上的某個屬性要滿足什么條件,還可以指定相關聯的模型滿足什么屬性。比如現在想要獲取寫過標題為`abc`的所有用戶,那么可以這樣寫:
```
<pre class="calibre12">```
users = User.objects.filter(article__title=<span class="hljs-string">'abc'</span>)
```
```
如果你設置了`related_name`為`articles`,因為反轉的過濾器的名字將使用`related_name`的名字,那么上例代碼將改成如下:
```
<pre class="calibre12">```
users = User.objects.filter(articles__title=<span class="hljs-string">'abc'</span>)
```
```
可以通過`related_query_name`將查詢的反轉名字修改成其他的名字。比如`article`。示例代碼如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
<span class="hljs-title"># 傳遞related_name參數,以后在方向引用的時候使用articles進行訪問</span>
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.SET_NULL,null=<span class="hljs-keyword">True</span>,related_name=<span class="hljs-string">'articles'</span>,related_query_name=<span class="hljs-string">'article'</span>)
```
```
那么在做反向過濾查找的時候就可以使用以下代碼:
```
<pre class="calibre12">```
users = User.objects.filter(article__title=<span class="hljs-string">'abc'</span>)
```
```
- Introduction
- 第一章:學前準備
- 第一節:虛擬環境
- 第二節:準備工作
- 第三節:Django介紹
- 第四節:URL組成部分
- 第二章:URL與視圖
- 第一節:第一個Django項目
- 第二節:視圖與URL分發器
- 第三章:模板
- 第一節:模板介紹
- 第二節:模板變量
- 第三節:常用標簽
- 第四節:常用過濾器
- 第五節:自定義過濾器
- 第七節:模版結構優化
- 第八節:加載靜態文件
- 第四章:數據庫
- 第一節:MySQL相關軟件
- 第二節:數據庫操作
- 第三節:ORM模型
- 第四節:模型常用字段
- 第五節:外鍵和表關系
- 第六節:增刪改查操作
- 第七節:查詢操作
- 第八節:QuerySet API
- 第九節:ORM模型遷移
- 第十節:ORM作業
- 第十一節:ORM作業參考答案
- 第十二節:Pycharm連接數據庫
- 第五章:視圖高級
- 第一節:限制請求method
- 第二節:頁面重定向
- 第三節:HttpRequest對象
- 第四節:HttpResponse對象
- 第五節:生成CSV文件
- 第六節:類視圖
- 第七節:錯誤處理
- 第六章:表單
- 第一節:表單概述
- 第二節:用表單驗證數據
- 第三節:ModelForm
- 第四節:文件上傳
- 第七章:cookie和session
- 第八章:上下文處理器和中間件
- 第一節:上下文處理器
- 第二節:中間件
- 第九章:安全
- 第一節:CSRF攻擊
- 第二節:XSS攻擊
- 第三節:點擊劫持攻擊
- 第四節:SQL注入
- 第十章:信號
- 第一節:什么是信號
- 第十一章:驗證和授權
- 第一節:概述
- 第二節:用戶對象
- 第三節:權限和分組
- 第十二章:Admin系統
- 第十三章:Django的緩存
- 第十四章:memcached
- 第十五章:Redis