内容类型框架¶
Django 包含一个 contenttypes
应用程序,可以跟踪在您的 Django 项目中安装的所有模型,并提供一个高级的通用接口来处理您的模型。
概述¶
内容类型应用程序的核心是 ContentType
模型,它位于 django.contrib.contenttypes.models.ContentType
。 ContentType
的实例表示并存储有关项目中安装的模型的信息,并且每当安装新的模型时,都会自动创建新的 ContentType
实例。
ContentType
的实例具有返回其表示的模型类以及从这些模型查询对象的方法。 ContentType
还具有一个 自定义管理器,该管理器添加了处理 ContentType
以及为特定模型获取 ContentType
实例的方法。
您模型与 ContentType
之间的关系还可以用于启用您模型实例与已安装的任何模型实例之间的“通用”关系。
安装内容类型框架¶
内容类型框架包含在 django-admin startproject
创建的默认 INSTALLED_APPS
列表中,但如果您已将其删除或手动设置了 INSTALLED_APPS
列表,则可以通过将 'django.contrib.contenttypes'
添加到您的 INSTALLED_APPS
设置中来启用它。
通常最好安装内容类型框架;Django 的其他几个捆绑应用程序需要它。
管理应用程序使用它来记录通过管理界面添加或更改的每个对象的记录。
Django 的
身份验证框架
使用它将用户权限绑定到特定模型。
ContentType
模型¶
- class ContentType[source]¶
每个
ContentType
实例都有两个字段,这两个字段一起唯一地描述了一个已安装的模型。- app_label¶
模型所属应用程序的名称。这取自模型的
app_label
属性,并且仅包含应用程序 Python 导入路径的最后一部分;例如,django.contrib.contenttypes
将变为contenttypes
的app_label
。
- model¶
模型类的名称。
此外,以下属性可用
- name[source]¶
内容类型的可读名称。这取自模型的
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)[source]¶
采用一组对
ContentType
所表示的模型有效的 查找参数,并在该模型上执行get() 查找
,返回相应的对象。using
参数可用于指定与默认数据库不同的数据库。Django 5.1 中的更改添加了
using
参数。
- ContentType.model_class()[source]¶
返回此
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 身份验证框架中的 the permissions system
使用具有指向 ContentType
的外键的 Permission
模型;这使得 Permission
可以表示诸如“可以添加博客条目”或“可以删除新闻故事”之类的概念。
ContentTypeManager
¶
- class ContentTypeManager[source]¶
ContentType
还有一个自定义管理器ContentTypeManager
,它添加了以下方法。- clear_cache()[source]¶
清除
ContentType
用于跟踪其已为其创建ContentType
实例的模型的内部缓存。您可能永远不需要自己调用此方法;Django 会在需要时自动调用它。
- get_for_id(id)[source]¶
按 ID 查找
ContentType
。由于此方法使用与get_for_model()
相同的共享缓存,因此建议使用此方法而不是通常的ContentType.objects.get(pk=id)
。
- get_for_model(model, for_concrete_model=True)[source]¶
接受模型类或模型实例,并返回表示该模型的
ContentType
实例。for_concrete_model=False
允许获取代理模型的ContentType
。
- get_for_models(*models, for_concrete_models=True)[source]¶
接受可变数量的模型类,并返回一个字典,该字典将模型类映射到表示它们的
ContentType
实例。for_concrete_models=False
允许获取代理模型的ContentType
。
- get_by_natural_key(app_label, model)[source]¶
返回由给定的应用程序标签和模型名称唯一标识的
ContentType
实例。此方法的主要目的是允许在反序列化期间通过 自然键 引用ContentType
对象。
get_for_model()
方法在您知道需要使用 ContentType
但不想费心获取模型的元数据以执行手动查找时特别有用。
>>> 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[source]¶
设置
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[source]¶
默认情况下,相关对象回指此对象的关联关系不存在。设置
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://django.ac.cn/")
>>> 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)[source]¶
使用
modelformset_factory()
返回一个GenericInlineFormSet
。如果它们与默认值
content_type
和object_id
分别不同,则必须提供ct_field
和fk_field
。其他参数类似于modelformset_factory()
和inlineformset_factory()
中记录的参数。for_concrete_model
参数对应于GenericForeignKey
上的for_concrete_model
参数。
管理站点中的泛型关系¶
django.contrib.contenttypes.admin
模块提供 GenericTabularInline
和 GenericStackedInline
(GenericInlineModelAdmin
的子类)。
这些类和函数支持在表单和管理站点中使用泛型关系。有关更多信息,请参阅 模型表单集 和 管理站点 文档。
- class GenericInlineModelAdmin[source]¶
GenericInlineModelAdmin
类继承了InlineModelAdmin
类中的所有属性。但是,它添加了一些用于处理泛型关系的属性。- ct_field¶
模型上
ContentType
外键字段的名称。默认为content_type
。
- ct_fk_field¶
表示相关对象 ID 的整型字段的名称。默认为
object_id
。
- class GenericStackedInline[source]¶
分别具有堆叠和表格布局的
GenericInlineModelAdmin
的子类。
GenericPrefetch()
¶
此查找类似于 Prefetch()
,并且只能用于 GenericForeignKey
。 querysets
参数接受一个查询集列表,每个查询集对应一个不同的 ContentType
。这对于结果集不均匀的 GenericForeignKey
很有用。
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://django.ac.cn/")
>>> 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>]>