Django中表關係詳解
表之間的關係都是通過外來鍵來進行關聯的。而表之間的關係,無非就是三種關係:一對一、一對多(多對一)、多對多等。
一對多:
- 應用場景:比如文章和作者之間的關係。一個文章只能由一個作者編寫,但是一個作者可以寫多篇文章。文章和作者之間的關係就是典型的多對一的關係。作者和文章的關係就是一對多。
- 實現方式:一對多或者多對一,都是通過ForeignKey來實現的。還是以文章和作者的案例進行講解。
建立一個user的app,models下編寫
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=100)
建立一個article的app,使用外來鍵引用user中的User模型,和使用外來鍵引用當前app中的Category。
models編寫:
from django.db import models
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=100)
class Article(models.Model) :
title = models.CharField(max_length=100)
content = models.TextField()
# 是由Category影響Article
category = models.ForeignKey('Category',on_delete=models.CASCADE)
# 將user中的User這個模型作為外來鍵
author = models.ForeignKey('user.User',on_delete=models.CASCADE,null=True)
在article中的views中編寫
def one_to_many(request):
# 插入文章
# article = models.Article(title='lala',content=333)
# category = models.Category.objects.get(pk=1)
# author = User.objects.get(pk=1)
# article.category = category
# article.author = author
# article.save()
# 獲取某個分類下的所有文章
category = models.Category.objects.get(pk=1)
# article_set這個屬性在Category這張表被外來鍵引入時就會自動生成
# 屬性名為<引用模型的模型名轉化為小寫_set>
# 可以執行.all(), .first(), .filter()等操作
articles = category.article_set.all()
for article in articles:
print(article.title,article.content,article.category.name,article.author)
return HttpResponse('succes')
修改上面程式碼的相應註釋實現不同效果。要想實現上面的程式碼,我們首先需要手動在資料庫中新建資訊。
在User表中新增一條資訊,
在Category中也新增一條資訊,
然後在Article中新建兩至三條資訊,外來鍵category_id和user_id都指向User表和Category表中的id。自行配置url等,就能進行測試了。
在我們獲取某個分類下的所有文章時,我們使用的是<引用模型的模型名轉化為小寫_set>
(即上面的article_set
)來獲取,那麼如果我們不想使用這個屬性來獲取,而是自定義一個屬性來獲取呢?
這個時候就需要用到related_name
這個引數了,
在models中的Article中設定外來鍵的屬性中新增這個引數就行了,示例,修改Article下的category
屬性的引數
category = models.ForeignKey('Category', on_delete=models.CASCADE,related_name='articles')
這樣,我們以後獲取種類下的Artilces
時,就可以用下面這種方式了。
# 沒有使用related_name時獲取分類下的所有文章的使用方法
# articles = category.article_set.all()
# 使用related後獲取分類下的所有文章的使用方法
articles = category.articles.all()
注意: 我們使用related_name
後,就沒有<引用模型的模型名轉化為小寫_set>
(即上面的article_set
)這個屬性名字了,而是改為我們自己設定的屬性名字,所以也就不能使用原來的名字,否則會報錯。
在上面views中註釋的程式碼中,我們使用了一種最常見的方式新增資料到數句庫中去,那種方式也是Django所推介的方式,但是還有一些不常用的新增方式我們也可以瞭解一下
修改views中的程式碼
def one_to_many(request):
# 插入文章,Django推介的方式,也是最常用的
# article = models.Article(title='lala',content=333)
# category = models.Category.objects.get(pk=1)
# author = User.objects.get(pk=1)
# article.category = category
# article.author = author
# article.save()
# 獲取某個分類下的所有文章
category = models.Category.objects.get(pk=1)
# article_set這個屬性在Category這張表被外來鍵引入時就會自動生成
# 屬性名為<引用模型的模型名轉化為小寫_set>
# 可以執行.all(),.first(),.filter()等操作
# 沒有使用related_name時獲取分類下的所有文章的使用方法
# articles = category.article_set.all()
# 使用related後獲取分類下的所有文章的使用方法
# articles = category.articles.all()
# for article in articles:
# print(article.title,article.content,article.category.name,article.author)
# 不常用的插入資料方式
article = models.Article(title='111',content='222')
article.author = User.objects.first()
#article.save()
category.articles.add(article)
category.save()
return HttpResponse('succes')
當我們執行上面程式碼的時候,就會報錯,錯誤的大概意思就是artilce
這個這個例項還沒有被儲存,不能被category
新增,這是我們將上面程式碼中的article.save()
取消註釋,先儲存一下這個article
的例項。這裡又會分為兩種情況了,我們對article
這個例項只傳入了title
和content
這兩個屬性的值,而沒有傳入category
這個外來鍵的值:
- 如果我們設定了
category
這個值可以為空的話,那麼將不會報錯,能夠正常的新增資料進去。 - 如果設定了
category
這個屬性的值不能為空的話,那麼執行時就會報錯,說我們沒有傳入這個引數,但是我們如果不先儲存這個article
例項物件,下面我們也不能使用category.save()
所以這就陷入了死迴圈中。所以在我們使用這種方式的時候需要確保category
這個屬性可以為空才行
那麼我想使用這種方式,又不想設定category
這個屬性為空,又應該怎樣做呢?
這個時候我們就可以使用bulk
這個引數了
示例程式碼
def one_to_many(request):
# 插入文章,Django推介的方式,也是最常用的
# article = models.Article(title='lala',content=333)
# category = models.Category.objects.get(pk=1)
# author = User.objects.get(pk=1)
# article.category = category
# article.author = author
# article.save()
# 獲取某個分類下的所有文章
category = models.Category.objects.get(pk=1)
# article_set這個屬性在Category這張表被外來鍵引入時就會自動生成
# 屬性名為<引用模型的模型名轉化為小寫_set>
# 可以執行.all(),.first(),.filter()等操作
# 沒有使用related_name時獲取分類下的所有文章的使用方法
# articles = category.article_set.all()
# 使用related後獲取分類下的所有文章的使用方法
# articles = category.articles.all()
# for article in articles:
# print(article.title,article.content,article.category.name,article.author)
# 不常用的插入資料方式
article = models.Article(title='333',content='444')
article.author = User.objects.first()
#article.save()
category.articles.add(article,bulk=False)
#category.save()
return HttpResponse('succes')
這個時候我們也不需要使用.save()
來儲存資訊到資料庫了,使用這個引數後就會自動給我們儲存這些所有例項化的物件。
所以Django也是推介我們使用第一種方式進行儲存資訊到資料庫。
一對一:
-
應用場景:比如一個使用者表和一個使用者資訊表。在實際網站中,可能需要儲存使用者的許多資訊,但是有些資訊是不經常用的。如果把所有資訊都存放到一張表中可能會影響查詢效率,因此可以把使用者的一些不常用的資訊存放到另外一張表中我們叫做UserExtension。但是使用者表User和使用者資訊表UserExtension就是典型的一對一了。
-
實現方式:Django為一對一提供了一個專門的Field叫做OneToOneField來實現一對一操作。
在user
app中的models中加入兩個模型
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=200)
class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField('User',on_delete=models.CASCADE)
然後makemigrations後再migrate進行資料遷移
然後在user中的views中寫入程式碼
from django.shortcuts import render
from django.http import HttpResponse
from . import models
# Create your views here.
def one_to_one(request):
user = models.User.objects.first()
extension = models.UserExtension(school='城北中學')
extension.user = user
extension.save()
return HttpResponse('success')
然後配置url等,執行專案,訪問網址。
這樣,我們就對一對一的資料表添加了資料,使用的也還四Django官方所推介的方式,也是比較好用的一種方式。也就是和使用普通的外來鍵新增資料是沒有差別的。
注意: 當我們不做任何修改再次重新整理網頁時,就會報錯了,因為我們重新整理頁面時就會執行views中one_to_one
函式中的程式碼,而我們的user還是獲取的第一個user這個例項,這樣就相當與對user這個例項又添加了一條資訊,user這個例項就擁有了兩條資訊了,但是我們使用的是一對一模式,所以這個地方就是會報錯的,Django是不會允許我們這樣操作的。所以以後使用一對一要注意到這一點細節。
那麼當我們拿到user的拓展資訊後,我們也能拿到user,示例:
def one_to_one(request):
# user = models.User.objects.first()
#
# extension = models.UserExtension(school='城北中學')
# extension.user = user
# extension.save()
# 通過使用者拓展資訊獲取使用者
extension = models.UserExtension.objects.first()
print(extension.user.username)
return HttpResponse('success')
當我們拿到user時,也能拿到user的拓展資訊
def one_to_one(request):
# user = models.User.objects.first()
#
# extension = models.UserExtension(school='城北中學')
# extension.user = user
# extension.save()
# 通過使用者拓展資訊獲取使用者
# extension = models.UserExtension.objects.first()
# print(extension.user.username)
# 通過使用者獲取使用者的拓展資訊
user = models.User.objects.first()
print(user.userextension.school)
return HttpResponse('success')
那麼我們不想通過user.userextension
來獲取這個資訊,而是自定義一個user.extension
來獲取資訊,那麼這個時候我們就可以使用上面用過的related_name
這個引數來實現了
首先修改models中的UserExtension
的程式碼
class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField('User',on_delete=models.CASCADE,related_name='extension')
views中修改程式碼
def one_to_one(request):
# user = models.User.objects.first()
# extension = models.UserExtension(school='城北中學')
# extension.user = user
# extension.save()
# 通過使用者拓展資訊獲取使用者
# extension = models.UserExtension.objects.first()
# print(extension.user.username)
# 通過使用者獲取使用者的拓展資訊
user = models.User.objects.first()
# 沒有使用related_name前的方法
# print(user.userextension)
# 使用related_name之後的方法
print(user.extension.school)
return HttpResponse('success')
同上面的一樣,修改程式碼之後如果我們在使用user.userextension
這個屬性時就會報錯。
這樣,就實現了我們的需求了
多對多:
-
應用場景:比如文章和標籤的關係。一篇文章可以有多個標籤,一個標籤可以被多個文章所引用。因此標籤和文章的關係是典型的多對多的關係。
-
實現方式:Django為這種多對多的實現提供了專門的Field。叫做ManyToManyField。還是拿文章和標籤為例進行講解。
我們在Article中的models中新增一個模型:
class Tag(models.Model):
name = models.CharField(max_length=100)
articles = models.ManyToManyField('Article')
這樣,我們就給Tag模型添加了一個多對多的外來鍵。因為是多對多的關係,我們也可以不用在Tag中新增這個外來鍵,而是在Article模型中新增外來鍵:例如
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 是由Category影響Article
# category = models.ForeignKey('Category',on_delete=models.CASCADE)
# 設定related_name引數修改category中Article的預設名字
category = models.ForeignKey('Category', on_delete=models.CASCADE,related_name='articles')
# 將user中的User這個模型作為外來鍵
author = models.ForeignKey('user.User',on_delete=models.CASCADE,null=True)
# 也可以在此處新增標籤,因為在Tag中添加了,所以就不用在此處添加了
# tags = models.ManyToManyField('Tag')
我們只需要多對多的兩個模型中隨便一個模型新增外來鍵就行了,至於在哪個模型中新增,就看個人心情把。
新增好了之後就makemigrations
和migrate
一下,使模型對映到我們資料庫中去。
然後我們開啟navicat檢視對映進去的資料表,因為我們只建立了一個模型Tag
,那麼資料庫中因該也只會多一張表article_tag
.但是,我們發現還生成了一張表article_tag_articles
,然後我們右鍵點選這場表–>設計表
再點選外來鍵
我們發現這張表只有三個值,一個是id主鍵,另外兩個都是外來鍵,一個參考article_article
這個表,另外一個參考article_tag
這個表。
看到這裡可能你就會明白了,在資料庫層面,Django為了實現這種多對多的方式,就在兩個表之間新建了一箇中間表,這個中間表裡面分別定義了兩個外來鍵,引用到article
和tag
這兩張表,從而實現多對多的關係。
然後在views中新增一個函式many_to_many
def many_to_many(request):
# 給第一篇文章新增一個標籤
article = models.Article.objects.first()
tag = models.Tag(name='熱門文章')
article.tag_set.add(tag)
return HttpResponse('success')
然後新增對映,執行專案並進行訪問。
然後我們會得到一個報錯
大概意思就是說我們沒有辦法對Article新增這個標籤,因為Tag標籤裡面還沒有資訊,也就是我們還沒有對Tag中的資訊進行儲存。
這個時候我們是不是可以使用一下bluk
引數了呢
修改程式碼
def many_to_many(request):
# 給第一篇文章新增一個標籤
article = models.Article.objects.first()
tag = models.Tag(name='熱門文章')
article.tag_set.add(tag,bluk=False)
return HttpResponse('success')
然後繼續執行,然後它又給我們報出了另外一個錯誤
這個錯誤的大概意思就是add()這個函式得到一個意外的引數bulk
,也就是add()沒有這個引數。這就很奇怪了,為什麼我們在前面一對多的時候又可以使用bluk
呢,在這裡就沒有這個引數了。
原因是因為我們只要使用的ManyToManyField
這個多對多的欄位,在我們使用add這個函式的時候就沒有bluk
這個引數了。這個時候我們就應該先將tag的資訊儲存至資料庫中了,所以修改程式碼如下:
def many_to_many(request):
# 給第一篇文章新增一個標籤
article = models.Article.objects.first()
tag = models.Tag(name='熱門文章')
# 先儲存tag
tag.save()
# 再新增tag
article.tag_set.add(tag)
return HttpResponse('success')
然後在執行就沒有錯誤了
然後我們在navicat中檢視資料表,資訊也被我們新增進去了。
然後我們也可以使用得到的tag
,然後向tag裡面新增articles
def many_to_many(request):
# 給第一篇文章新增一個標籤
# article = models.Article.objects.first()
# tag = models.Tag(name='熱門文章')
# # 先儲存tag
# tag.save()
# # 再新增tag
# article.tag_set.add(tag)
# 通過標籤新增文章
tag = models.Tag.objects.get(pk=1)
article = models.Article.objects.get(pk=2)
tag.articles.add(article)
return HttpResponse('success')
這樣我們也成功的對多對多的表進行了操作了,而且,在我們操作的時候,我們是不是根本沒有感覺到第三章表(article_tag_articles
)的存在,這就是Django的神奇之處了,我們也不用管它,Django已將給我們封裝好了,我們只管使用就行了。
我們在給文章新增一個標籤的時候使用到了tag_set
這個屬性,我們也可以像一對多那樣,對在設定外來鍵的時候新增一個related_name
這個引數來進行設定。