Django 包含了一个 contenttypes
应用程序,它可以跟踪所有安装在你的 Django 项目中的模型,为你的模型提供了一个高级的通用接口。
内容类型应用的核心是 ContentType
模型,它位于 django.contrib.contenttypes.models.ContentType
。ContentType
的实例代表和存储了你项目中安装的模型的信息,每当有新的模型安装时,就会自动创建 ContentType
的新实例。
ContentType
的实例有方法用于返回它们所代表的模型类和查询这些模型中的对象。 ContentType
也有一个 自定义管理器,它增加了一些方法,用于处理 ContentType
,以及为特定模型获取 ContentType
的实例。
你的模型和 ContentType
之间的关系也可以用来启用你的一个模型实例和你安装的任何模型实例之间的“通用 ”关系。
内容类型框架包含在由 django-admin startproject
创建的默认的 INSTALLED_APPS
列表中,但是如果你已经删除了它,或者你手动设置了 INSTALLED_APPS
列表,你可以通过在 INSTALLED_APPS
配置中添加 'django.contrib.contenttypes'
来启用它。
一般来说,安装内容类型框架是个不错的主意;Django 的其他一些捆绑的应用程序都需要它:
认证框架
使用它将用户权限与特定模型绑定。ContentType
模型¶ContentType
¶ContentType
的每个实例都有两个字段,这两个字段合在一起,唯一地描述了一个安装的模型。
app_label
¶模型所属应用程序的名称。这是从模型的 app_label
属性中提取的,并且只包括应用程序的 Python 导入路径的 最后 一部分;例如,django.contrib.contenttypes
就变成了 contenttypes
的 app_label
。
model
¶模型类的名称。
此外,还有以下属性:
name
¶内容类型的可读名称。这是从模型的 verbose_name
属性中提取的。
让我们看一个例子来了解它是如何工作的。如果你已经安装了 contenttypes
应用程序,然后添加 站点框架
到你的 INSTALLED_APPS
配置中,并运行 manage.py migrate
来安装它,模型 django.contrib.sites.models.Site
将被安装到你的数据库中。与它一起创建一个新的 ContentType
实例,其值如下:
ContentType
实例的方法¶每个 ContentType
实例都有一些方法,允许你从 ContentType
实例获得它所代表的模型,或者从该模型中检索对象。
ContentType.
get_object_for_this_type
(**kwargs)¶为 ContentType
所代表的模型获取一组有效的 查找参数,并对该模型进行 一个 get() 查找
,返回相应的对象。
ContentType.
model_class
()¶返回这个 ContentType
实例所代表的模型类。
For example, we could look up the
ContentType
for the
User
model:
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>
And then use it to query for a particular
User
, or to get access
to the User
model class:
>>> 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_model_class()
共同实现了两个极其重要的用例:
app_label
和 model
传递到一个 ContentType
的查找中,然后与模型类一起工作,或者从中检索对象。ContentType
相关联,以此将它的实例与特定的模型类绑定,并使用这些方法来获取对这些模型类的访问。Django 的几个捆绑应用都使用了后一种技术。例如,Django 的认证框架中的 :class:``权限系统 <django.contrib.auth.models.Permission>` 使用了一个 Permission
模型,该模型的外键为 ContentType
;这使得 Permission
可以表示“可以添加博客条目”或“可以删除新闻报道”等概念。
ContentTypeManager
¶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
对象在反序列化过程中通过 自然键 被引用。
The get_for_model()
method is especially
useful when you know you need to work with a
ContentType
but don't
want to go to the trouble of obtaining the model's metadata to perform a manual
lookup:
>>> 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
),它可以解决这个问题,并允许与任何模型建立关系:
GenericForeignKey
¶设置一个 GenericForeignKey
分为三步:
ForeignKey
到 ContentType
。这个字段的通常名称是 “content_type”。PositiveIntegerField
。这个字段的通常名称是 “object_id”。GenericForeignKey
,并把上面描述的两个字段的名字传给它。如果这些字段的名字是 “content_type” 和 “object_id”,你可以省略这一点 —— 这些是 GenericForeignKey
会查找的默认字段名。Unlike for the ForeignKey
, a database index is
not automatically created on the
GenericForeignKey
, so it's
recommended that you use
Meta.indexes
to add your own
multiple column index. This behavior may change in the
future.
for_concrete_model
¶如果 False
,该字段将能够引用代理模型。默认值是 True
。这与 get_for_model()
的 for_concrete_model
参数一致。
主键类型兼容性
“object_id” 字段不一定要和相关模型上的主键字段是同一类型,但它们的主键值必须通过其 get_db_prep_value()
方法与 “object_id” 字段的类型一致。
例如,如果你想允许通用关系到具有 CharField
主键字段的模型,你可以使用 CharField
作为你的模型上的 “object_id” 字段,因为整数可以通过 get_db_prep_value()
强制转换成字符串。
为了获得最大的灵活性,你可以使用一个 TextField
,它没有定义最大的长度,但是这可能会根据你的数据库后端产生显著的性能惩罚。
对于哪种字段类型最好,没有一个放之四海而皆准的解决方案。你应该评估你期望指向的模型,并确定哪种解决方案对你的用例最有效。
序列化对 ContentType
对象的引用
如果你正在从实现通用关系的模型中序列化数据(例如,在生成 fixtures
时),你可能应该使用自然键来唯一地识别相关的 ContentType
对象。参见 自然键 和 dumpdata --natural-foreign
了解更多信息。
This will enable an API similar to the one used for a normal
ForeignKey
;
each TaggedItem
will have a content_object
field that returns the
object it's related to, and you can also assign to that field or use it when
creating a 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>
If the related object is deleted, the content_type
and object_id
fields
remain set to their original values and the GenericForeignKey
returns
None
:
>>> guido.delete()
>>> t.content_object # returns None
Due to the way GenericForeignKey
is implemented, you cannot use such fields directly with filters (filter()
and exclude()
, for example) via the database API. Because a
GenericForeignKey
isn't a
normal field object, these examples will not work:
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
同样, GenericForeignKey
也没有出现在 ModelForm
中。
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
instances will each have a tags
attribute, which can
be used to retrieve their associated TaggedItems
:
>>> b = Bookmark(url="https://www.djangoproject.com/")
>>> 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>]>
You can also use add()
, create()
, or set()
to create
relationships:
>>> 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>]>
The remove()
call will bulk delete the specified model objects:
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
The clear()
method can be used to bulk delete all related objects for an
instance:
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
定义 GenericRelation
,并设置 related_query_name
允许从相关对象查询:
tags = GenericRelation(TaggedItem, related_query_name="bookmark")
This enables filtering, ordering, and other query operations on Bookmark
from TaggedItem
:
>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmark__url__contains="django")
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
If you don't add the related_query_name
, you can do the same types of
lookups manually:
>>> 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
接受content-type 和 object-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's database aggregation API works with a
GenericRelation
. For example, you
can find out how many tags all the bookmarks have:
>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}
django.contrib.contenttypes.forms
模块提供:
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)¶使用 modelormset_factory()
返回一个 GenericInlineFormSet
。
你必须提供 ct_field
和 fk_field
,如果它们与默认值 content_type
和 object_id
不同。其他参数与 modelselformset_factory()
和 inlineformset_factory()
中记载的类似。
for_concrete_model
参数对应于 for_concrete_model`
参数。
django.contrib.contenttypes.admin
模块提供了 GenericTabularInline
和 GenericInlineModelAdmin
的子类)。
这些类和函数可以在表单和管理中使用通用关系。更多信息请参见 模型表单集 和 管理 文档。
GenericInlineModelAdmin
¶GenericInlineModelAdmin
类继承了 InlineModelAdmin
类的所有属性。然而,它增加了一些自己的属性来处理通用关系:
ct_field
¶模型上的 ContentType
外键字段的名称。默认为 content_type
。
ct_fk_field
¶代表相关对象 ID 的整数字段的名称。默认值为 object_id
。
GenericTabularInline
¶GenericStackedInline
¶GenericInlineModelAdmin
的子类,分别具有堆栈式和表格式布局。
GenericPrefetch()
¶GenericPrefetch
(lookup, querysets=None, to_attr=None)¶This lookup is similar to Prefetch()
and it should only be used on
GenericForeignKey
. The querysets
argument accepts a list of querysets,
each for a different ContentType
. This is useful for GenericForeignKey
with non-homogeneous set of results.
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/")
>>> 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>]>
12月 05, 2023