內容類型框架¶
Django 包含一個 contenttypes
應用程式,它可以追蹤您 Django 專案中安裝的所有模型,並提供一個高階、通用的介面來使用您的模型。
概觀¶
內容類型應用程式的核心是 ContentType
模型,它位於 django.contrib.contenttypes.models.ContentType
。 ContentType
的實例表示並儲存您專案中已安裝模型的資訊,並且每當安裝新模型時,都會自動建立 ContentType
的新實例。
ContentType
的實例具有方法可以傳回它們所代表的模型類別,以及查詢這些模型的物件。ContentType
還有一個 自訂管理器,它添加了用於處理 ContentType
和取得特定模型的 ContentType
實例的方法。
您的模型和 ContentType
之間的關聯性也可以用來啟用您的某個模型實例與您已安裝的任何模型實例之間的「通用」關係。
安裝 contenttypes 框架¶
contenttypes 框架包含在 django-admin startproject
建立的預設 INSTALLED_APPS
清單中,但是如果您已將其移除,或您手動設定了 INSTALLED_APPS
清單,您可以將 'django.contrib.contenttypes'
加入 INSTALLED_APPS
設定來啟用它。
通常建議安裝 contenttypes 框架; Django 的其他幾個綑綁應用程式需要它
管理應用程式使用它來記錄透過管理介面新增或變更的每個物件的歷史記錄。
Django 的
驗證 框架
使用它將使用者權限連結到特定模型。
ContentType
模型¶
- class ContentType[原始碼]¶
ContentType
的每個實例都有兩個欄位,它們結合起來可以唯一描述一個已安裝的模型- app_label¶
模型所屬應用程式的名稱。取自模型的
app_label
屬性,且僅包含應用程式 Python 匯入路徑的最後一部分;例如,django.contrib.contenttypes
會變成app_label
的contenttypes
。
- model¶
模型類別的名稱。
此外,還可以使用以下屬性
- name[原始碼]¶
內容類型的易讀名稱。取自模型的
verbose_name
屬性。
讓我們來看一個範例,以了解它是如何運作的。如果您已經安裝了 contenttypes
應用程式,然後將 sites 應用程式
加入您的 INSTALLED_APPS
設定並執行 manage.py migrate
以安裝它,模型 django.contrib.sites.models.Site
將會安裝到您的資料庫中。同時,也會建立一個新的 ContentType
實例,其值如下
ContentType
實例的方法¶
每個 ContentType
實例都有一些方法,可讓您從 ContentType
實例取得其所代表的模型,或從該模型檢索物件
- ContentType.get_object_for_this_type(using=None, **kwargs)[原始碼]¶
接受模型的一組有效的查詢參數,該模型由
ContentType
表示,並對該模型執行get() 查詢
,傳回對應的物件。using
參數可用於指定與預設資料庫不同的資料庫。在 Django 5.1 中變更新增了
using
參數。
- ContentType.model_class()[原始碼]¶
傳回此
ContentType
實例所表示的模型類別。
例如,我們可以查詢 ContentType
以取得 User
模型
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>
然後使用它來查詢特定的 User
,或取得 User
模型類別的存取權
>>> user_type.model_class()
<class 'django.contrib.auth.models.User'>
>>> user_type.get_object_for_this_type(username="Guido")
<User: Guido>
結合 get_object_for_this_type()
和 model_class()
可實現兩個非常重要的使用案例
使用這些方法,您可以撰寫高階的泛型程式碼,對任何已安裝的模型執行查詢 – 您可以將
app_label
和model
傳遞到執行階段的ContentType
查詢中,而不是匯入和使用單個特定的模型類別,然後使用模型類別或從中檢索物件。您可以將另一個模型與
ContentType
關聯,將其執行個體與特定模型類別連結,並使用這些方法取得這些模型類別的存取權。
Django 的幾個捆綁應用程式使用了後一種技術。例如,Django 驗證框架中的權限系統
使用具有指向 ContentType
外鍵的 Permission
模型;這讓 Permission
表示「可以新增部落格條目」或「可以刪除新聞報導」等概念。
ContentTypeManager
¶
- class ContentTypeManager[原始碼]¶
ContentType
也有一個自訂管理器,ContentTypeManager
,它新增了以下方法- clear_cache()[原始碼]¶
清除
ContentType
使用的內部快取,以追蹤已建立ContentType
執行個體的模型。您可能永遠不需要自己呼叫此方法;Django 會在需要時自動呼叫它。
- get_for_id(id)[原始碼]¶
依 ID 查詢
ContentType
。由於此方法使用與get_for_model()
相同的共用快取,因此最好使用此方法,而不是通常的ContentType.objects.get(pk=id)
- get_for_model(model, for_concrete_model=True)[原始碼]¶
接受模型類別或模型實例,並傳回表示該模型的
ContentType
執行個體。for_concrete_model=False
允許擷取 Proxy 模型的ContentType
。
- get_for_models(*models, for_concrete_models=True)[原始碼]¶
接受可變數量的模型類別,並傳回將模型類別對應至表示它們的
ContentType
執行個體的字典。for_concrete_models=False
允許擷取 Proxy 模型的ContentType
。
- get_by_natural_key(app_label, model)[原始碼]¶
傳回由給定的應用程式標籤和模型名稱唯一識別的
ContentType
執行個體。此方法的主要目的是允許在還原序列化期間,透過自然鍵來參照ContentType
物件。
當您知道需要使用 ContentType
,但不希望費力取得模型的元數據來執行手動查詢時,get_for_model()
方法特別有用
>>> from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)
<ContentType: user>
泛型關係¶
從您自己的模型之一新增一個指向 ContentType
的外鍵,可讓您的模型有效地將自身連結到另一個模型類別,如上述 Permission
模型的範例所示。但是,可以更進一步,並使用 ContentType
來啟用模型之間真正的泛型(有時稱為「多型」)關係。
例如,它可以像這樣用於標籤系統
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
def __str__(self):
return self.tag
class Meta:
indexes = [
models.Index(fields=["content_type", "object_id"]),
]
一般的 ForeignKey
只能「指向」一個其他的模型,這表示如果 TaggedItem
模型使用 ForeignKey
,它就必須選擇一個且僅有一個模型來儲存標籤。contenttypes 應用程式提供一種特殊的欄位類型 (GenericForeignKey
),可以解決這個問題,並允許關聯到任何模型。
- class GenericForeignKey[來源]¶
設定
GenericForeignKey
有三個步驟:為您的模型提供一個指向
ContentType
的ForeignKey
。此欄位的常用名稱是「content_type」。為您的模型提供一個欄位,可以儲存您將關聯的模型的主鍵值。對於大多數模型來說,這表示一個
PositiveIntegerField
。此欄位的常用名稱是「object_id」。為您的模型提供一個
GenericForeignKey
,並傳入上述兩個欄位的名稱。如果這些欄位命名為「content_type」和「object_id」,您可以省略此步驟,因為這些是GenericForeignKey
將尋找的預設欄位名稱。
與
ForeignKey
不同,資料庫索引並不會自動在GenericForeignKey
上建立,因此建議您使用Meta.indexes
來新增您自己的多欄索引。這個行為未來 可能會改變。- for_concrete_model¶
如果
False
,則此欄位可以參照代理模型。預設值為True
。這與get_for_model()
的for_concrete_model
引數相同。
主鍵類型相容性
「object_id」欄位不必與相關模型上的主鍵欄位類型相同,但是它們的主鍵值必須能夠透過其 get_db_prep_value()
方法強制轉換為與「object_id」欄位相同的類型。
例如,如果您想允許泛型關係指向具有 IntegerField
或 CharField
主鍵欄位的模型,您可以使用 CharField
作為模型上的「object_id」欄位,因為整數可以透過 get_db_prep_value()
強制轉換為字串。
為了達到最大的彈性,您可以使用沒有定義最大長度的 TextField
,但是這可能會根據您的資料庫後端造成顯著的效能損失。
沒有一種適用於所有情況的最佳欄位類型。您應該評估您預期指向的模型,並決定哪種解決方案最適合您的用例。
序列化對 ContentType
物件的參照
如果您正在序列化來自實作泛型關係模型的資料(例如,在產生 fixtures
時),您應該使用自然鍵來唯一識別相關的 ContentType
物件。請參閱 自然鍵 和 dumpdata --natural-foreign
以取得更多資訊。
這將啟用類似於一般 ForeignKey
的 API;每個 TaggedItem
都會有一個 content_object
欄位,該欄位會傳回它所關聯的物件,您也可以在建立 TaggedItem
時指定該欄位或使用它。
>>> from django.contrib.auth.models import User
>>> guido = User.objects.get(username="Guido")
>>> t = TaggedItem(content_object=guido, tag="bdfl")
>>> t.save()
>>> t.content_object
<User: Guido>
如果相關物件被刪除,則 content_type
和 object_id
欄位會保持設定為其原始值,而 GenericForeignKey
會傳回 None
。
>>> guido.delete()
>>> t.content_object # returns None
由於 GenericForeignKey
的實作方式,您無法直接透過資料庫 API 將此類欄位用於篩選器(例如,filter()
和 exclude()
)。因為 GenericForeignKey
不是一般的欄位物件,所以這些範例將 *無法* 運作。
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
同樣地,GenericForeignKey
不會出現在 ModelForm
中。
反向泛型關係¶
- class GenericRelation[來源]¶
預設情況下,相關物件上返回到此物件的關係不存在。設定
related_query_name
會從相關物件建立返回到此物件的關係。這允許從相關物件進行查詢和篩選。
如果您知道最常使用哪些模型,您也可以新增一個「反向」泛型關係來啟用額外的 API。例如:
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(TaggedItem)
Bookmark
實例將各自具有一個 tags
屬性,可用於檢索其相關的 TaggedItems
。
>>> b = Bookmark(url="https://djangoproject.dev.org.tw/")
>>> b.save()
>>> t1 = TaggedItem(content_object=b, tag="django")
>>> t1.save()
>>> t2 = TaggedItem(content_object=b, tag="python")
>>> t2.save()
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
您也可以使用 add()
、create()
或 set()
來建立關係。
>>> t3 = TaggedItem(tag="Web development")
>>> b.tags.add(t3, bulk=False)
>>> b.tags.create(tag="Web framework")
<TaggedItem: Web framework>
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>, <TaggedItem: Web development>, <TaggedItem: Web framework>]>
>>> b.tags.set([t1, t3])
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: Web development>]>
remove()
呼叫會批量刪除指定的模型物件。
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
clear()
方法可用於批量刪除實例的所有相關物件。
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
定義設定了 related_query_name
的 GenericRelation
允許從相關物件進行查詢。
tags = GenericRelation(TaggedItem, related_query_name="bookmark")
這使得可以從 TaggedItem
對 Bookmark
進行篩選、排序和其他查詢操作。
>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmark__url__contains="django")
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
如果您不加入 related_query_name
,您可以使用相同的方式手動進行查詢。
>>> bookmarks = Bookmark.objects.filter(url__contains="django")
>>> bookmark_type = ContentType.objects.get_for_model(Bookmark)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks)
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
如同 GenericForeignKey
接受內容類型和物件 ID 欄位的名稱作為引數一樣,GenericRelation
也是如此;如果具有通用外鍵的模型使用非預設的欄位名稱,您必須在設定指向它的 GenericRelation
時傳遞這些欄位的名稱。例如,如果上面提到的 TaggedItem
模型使用名為 content_type_fk
和 object_primary_key
的欄位來建立其通用外鍵,那麼返回到它的 GenericRelation
需要像這樣定義:
tags = GenericRelation(
TaggedItem,
content_type_field="content_type_fk",
object_id_field="object_primary_key",
)
另請注意,如果您刪除具有 GenericRelation
的物件,任何具有指向它的 GenericForeignKey
的物件也會被刪除。在上面的例子中,這表示如果刪除一個 Bookmark
物件,任何指向它的 TaggedItem
物件也會同時被刪除。
與 ForeignKey
不同,GenericForeignKey
不接受 on_delete
引數來自訂此行為;如果需要,您可以透過不使用 GenericRelation
來避免級聯刪除,並且可以透過 pre_delete
訊號提供替代行為。
通用關聯和聚合¶
Django 的資料庫聚合 API 可以與 GenericRelation
搭配使用。例如,您可以找出所有書籤有多少標籤。
>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}
表單中的通用關聯¶
django.contrib.contenttypes.forms
模組提供
- generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field='content_type', fk_field='object_id', fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False, absolute_max=None, can_delete_extra=True)[原始碼]¶
使用
modelformset_factory()
返回一個GenericInlineFormSet
。如果
ct_field
和fk_field
與預設值不同,則必須提供它們,分別為content_type
和object_id
。其他參數與modelformset_factory()
和inlineformset_factory()
中記錄的參數類似。for_concrete_model
引數對應於GenericForeignKey
上的for_concrete_model
引數。
管理介面中的通用關聯¶
django.contrib.contenttypes.admin
模組提供了 GenericTabularInline
和 GenericStackedInline
( GenericInlineModelAdmin
的子類別 )
這些類別和函數使得在表單和管理介面中使用通用關聯成為可能。有關更多資訊,請參閱模型表單集和管理介面文件。
- class GenericInlineModelAdmin[原始碼]¶
GenericInlineModelAdmin
類別繼承自InlineModelAdmin
類別的所有屬性。但是,它為處理通用關聯添加了一些自己的屬性- ct_field¶
模型上
ContentType
外鍵欄位的名稱。預設值為content_type
。
- ct_fk_field¶
表示相關物件 ID 的整數欄位的名稱。預設值為
object_id
。
- class GenericStackedInline[原始碼]¶
分別具有堆疊式和表格式佈局的
GenericInlineModelAdmin
子類別。
GenericPrefetch()
¶
此查詢方式與 Prefetch()
相似,且僅應使用於 GenericForeignKey
。 querysets
參數接受一個查詢集列表,每個查詢集對應一個不同的 ContentType
。這對於具有非同質結果集的 GenericForeignKey
非常有用。
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://djangoproject.dev.org.tw/")
>>> animal = Animal.objects.create(name="lion", weight=100)
>>> TaggedItem.objects.create(tag="great", content_object=bookmark)
>>> TaggedItem.objects.create(tag="awesome", content_object=animal)
>>> prefetch = GenericPrefetch(
... "content_object", [Bookmark.objects.all(), Animal.objects.only("name")]
... )
>>> TaggedItem.objects.prefetch_related(prefetch).all()
<QuerySet [<TaggedItem: Great>, <TaggedItem: Awesome>]>