Django 各種關係欄位詳解
參考資料如下
1. ForeignKey
ForeignKey
用於多對一關係,直接對應到資料庫外來鍵的概念。使用ForeignKey
需要指定引用的目標表,會自動關聯到目標表的主鍵(一般是id
欄位)。
例子如下。
from django.db import models class Child(models.Model): parent = models.ForeignKey('Parent', on_delete=models.CASCADE, ) # ... class Parent(models.Model): # ... pass
對比之 sqlalchemy,一行parent=models.ForeignKey(...)
包含了 sqlalchemy 中的ForeignKey
和relationship
兩部分內容。
1.1 引數:on_delete
on_delete
意為當ForeignKey
引用的物件被刪除時進行的操作。
有幾個可以考慮的選項。
1.1.1 models.CASCADE
CASCADE
意為級聯,on_delete
設定為CASCADE
時意為執行級聯刪除。依據文件,Django 會模仿 SQL 的ON DELETE CASCADE
,對包含了ForeignKey
的物件執行刪除。
需要注意的是不會呼叫被級聯刪除物件上的model.delete()
pre_delete
和post_delete
訊號。
1.1.1.2 models.PROTECT
PROTECT
意為保護,on_delete
設定為PROTECT
意味著要阻止刪除操作發生。刪除關聯的物件時,ForeignKey
的on_delete
設定為PROTECT
會觸發ProtectedError
。
1.1.1.3 models.SET_NULL
如其名所述,如果這個ForeignKey
是 nullable 的,則關聯的物件刪除時將外來鍵設定為 null。
1.1.1.4 models.SET_DEFAULT
如其名所述,如果這個ForeignKey
設定了DEFAULT
,則關聯的物件刪除時設定這個外來鍵為DEFAULT
1.1.1.5 models.SET
在關聯的物件刪除時,設定為一個指定的值。這個引數可以接受一個可以賦值給這個 ForeignKey 的物件或者一個可呼叫物件。
官方例子如下。
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='deleted')[0]
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
1.1.1.6 models.DO_NOTHING
應該不用多說了吧。Django 不會做多餘的事情,但是如果後端的資料庫服務有強制完整性約束,除非你在資料庫一端自己定義了ON DELETE
,否則會觸發IntegrityError
。
1.2 引數:limited_choice_to
強制約束為 django.admin 或者 ModelForm 渲染時提供有限的可選項。
接受引數為dict
或者Q
物件、返回Q
物件的可呼叫物件。
官方例子。
staff_member = models.ForeignKey(
User,
on_delete=models.CASCADE,
limit_choices_to={'is_staff': True},
)
Q 物件是什麼玩意兒這個我搞明白了再說...
1.3 引數:related_name
設定反向關聯的欄位名,和sqlalchemy
的backref
類似。
舉例來說。
class Child(models.Model):
parent = models.ForeignKey('Parent')
class Parent(models.Model):
pass
Parent.child_set.all() # 未設定 related_name
Parent.children.all() # 設定 related_name=children
1.4 引數:related_query_name
related_query_name 和 related_name 類似,設定反向引用查詢時條件的字首名。舉例來說。
class Child(models.Model):
parent = models.ForeignKey('Parent')
name = models.CharField(max_length=4)
class Parent(models.Model):
pass
Parent.objects.filter(Child__name='沙雕網友') # 未設定 related_query_name
Parent.objects.filter(myboy__name='沙雕網友') # 設定 related_query_name=myboy
1.5 引數:to_field
得到ForeignKey
關聯的模型的欄位,預設是主鍵,如果指定的不是主鍵那麼必須有unique
約束才行。
1.6 引數:db_constraint
要不要建立資料庫層級的約束,也就是通過後端資料庫服務確保資料完整性不受破壞。如果設定為 False 那麼訪問不存在的物件時會觸發 DoesNotExists 異常。
1.7 引數:swappable
用於處理“我有一個抽象類模型但是這個模型有一個外來鍵”的情況,典型就是AUTH_USER_MODEL
。
一般不用改到,這個屬性控制了資料庫遷移時如何處理這個外來鍵關聯的表,總之保持預設值就行了。
這個功能支援了使用自定義的使用者模型替代 django.auth.models.User
之類的玩意兒。
2. OneToOneField
OneToOneField
基本就是一個加了unique
約束的ForeignKey
。使用上與 ForeignKey 略有不同。
首先是訪問 OneToOneField
時,得到的不是 QuerySet
而是一個物件例項。
# 優生優育政策(
class Parent(models.Model):
child = OneToOneField('Child')
class Child(models.Model):
pass
parent.child # => 得到一個 Child 例項
其次是反向引用的名字是模型名字小寫。
child.parent # => 得到一個 Parent 例項
如果指定 related_name
那就和 ForeignKey
一個表現。
3. ManyToManyField
基本和ForeignKey
相同。
3.1 和 ForeignKey
相同的引數
- related_name
- related_query_name
- limited_choices_to
- db_constraint
- swappable
limited_choices_to 在指定自定義中間表的情況下無效。
3.2 引數:symmetrical
用於處理一個表自己對自己的多對多引用對稱性。
Django 的預設行為是,我是你的朋友,那麼你就是我的朋友。
設定了這個引數則強迫 Django 改變這個行為,允許“被朋友”。
3.3 引數:through
預設情況下,Django 會自行建立中間表,這個引數強制指定中間表。
預設中間表模型裡包含三個欄位。
- id
- <containing_model>_id
- <other_model>_id
如果是自己和自己的多對多關係,則
- id
- from_<model>_id
- to_<model>_id
3.4 引數:through_fields
當自行指定中間表,中間表又包含了多個外來鍵時,指定關聯的外來鍵用。
舉例。
class ModelA(models.Model):
b = models.ManyToManyField(ModelB, through='ModelC')
class ModelB(models.Model):
pass
class ModelC(models.Model):
a=models.ForeignKey('ModelA')
b=models.ForeignKey('ModelB')
c=models.ForeignKey('ModelA')
此時在中間表中a
和c
都是對ModelA
的外來鍵,產生了歧義,Django 無法自行決定用哪個外來鍵來關聯 AB 兩個表。
這時提供引數。
b = models.ManyToManyField('ModelB', through='ModelC', through_fields=(a, b))
ManyToManyField
關聯兩個表總是不對稱的關係(指我把你當兄弟,你卻想當我爸爸這樣的關係。此時“我”對“你”的“兄弟”關係就是單向的。),這就形成了來源和目標的概念。
through_fields
的第一個元素總被認為是來源欄位,第二個元素是目標欄位。
3.5 引數:db_table
指定 Django 建立的中間表的名字,預設根據兩個表表名和 ManyToManyField