contenttypes 框架¶
Django 包含一个 contenttypes
应用程序,它可以跟踪在由 Django 提供支持的项目中安装的所有模型,为使用模型提供高级通用接口。
概览¶
contenttypes 应用程序的核心是 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 的
authentication framework
使用它将用户权限绑定到特定模型。
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
(**kwargs)¶ 获取
ContentType
表示的模型的有效 查找参数 的集合,并在该模型上执行a get() 查找
,返回相应的对象。
-
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
允许获取代理模型的ContentType
。
-
get_for_models
(*models, for_concrete_models=True)¶ 接受可变数量的模型类,并返回一个字典,将模型类映射到表示它们的
ContentType
实例。for_concrete_models=False
允许获取代理模型的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
有三个部分- 为模型提供一个
ForeignKey
至ContentType
。此字段的常用名称为“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://www.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
模块提供
-
class
BaseGenericInlineFormSet
¶
-
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
。
-
-
类
GenericTabularInline
¶
-
类
GenericStackedInline
¶ 类
GenericInlineModelAdmin
的子类,分别具有堆叠和表格布局。
GenericPrefetch()
¶
-
类
GenericPrefetch
(lookup, querysets, to_attr=None)¶
此查找类似于 Prefetch()
,它只应在 GenericForeignKey
上使用。 querysets
参数接受一个查询集列表,每个列表对应一个不同的 ContentType
。这对于具有非同质结果集的 GenericForeignKey
很有用。
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://www.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>]>