ContentTypes框架

Django包括 contenttypes 可以跟踪Django供电项目中安装的所有模型的应用程序,为使用模型提供高级通用接口。

概述

ContentTypes应用程序的核心是 ContentType 模型,住在 django.contrib.contenttypes.models.ContentType . 实例 ContentType 表示和存储有关项目中安装的模型以及的新实例的信息 ContentType 在安装新模型时自动创建。

实例 ContentType 具有返回它们所表示的模型类以及从这些模型查询对象的方法。 ContentType 也有 custom manager 增加了使用的方法 ContentType 以及获取 ContentType 对于特定型号。

你的模型和 ContentType 还可以用于启用某个模型的实例与已安装的任何模型的实例之间的“通用”关系。

安装ContentTypes框架

默认情况下包含ContentTypes框架 INSTALLED_APPS 创建的列表 django-admin startproject ,但如果您已将其移除或手动设置 INSTALLED_APPS 列表,您可以通过添加 'django.contrib.contenttypes' 对你 INSTALLED_APPS 设置。

安装ContentTypes框架通常是一个好主意;Django的其他捆绑应用程序中有几个需要它:

  • 管理应用程序使用它来记录通过管理界面添加或更改的每个对象的历史记录。

  • Django authentication framework 使用它将用户权限绑定到特定模型。

这个 ContentType 模型

class ContentType

的每个实例 ContentType 共有两个字段,它们共同唯一地描述已安装的模型:

app_label

模型所属应用程序的名称。这是从 app_label 模型的属性,并且仅包括 last 应用程序的python导入路径的一部分; django.contrib.contenttypes 例如,变为 app_label 属于 contenttypes .

model

模型类的名称。

此外,以下属性可用:

name

内容类型的可读名称。这是从 verbose_name 模型的属性。

让我们看一个例子来看看这是如何工作的。如果你已经有了 contenttypes 已安装应用程序,然后添加 the sites application 对你 INSTALLED_APPS 设置与运行 manage.py migrate 要安装它,模型 django.contrib.sites.models.Site 将安装到数据库中。与之一起的是 ContentType 将使用以下值创建:

  • app_label 将被设置为 'sites' (python路径的最后一部分 django.contrib.sites

  • model 将被设置为 'site' .

方法对 ContentType 实例

ContentType 实例的方法允许您从 ContentType 它所代表的模型的实例,或从该模型检索对象:

ContentType.get_object_for_this_type(**kwargs)

接受一组有效的 lookup arguments 对于模型 ContentType 表示,并执行 a get() lookup 在该模型上,返回相应的对象。

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() 启用两个极其重要的用例:

  1. 使用这些方法,您可以编写对任何已安装模型执行查询的高级通用代码,而不是导入和使用单个特定的模型类,您可以通过 app_labelmodel 变成一个 ContentType 在运行时查找,然后使用模型类或从中检索对象。

  2. 您可以将另一个模型与 ContentType 作为将它的实例与特定的模型类联系起来的一种方法,并使用这些方法来访问这些模型类。

Django的几个捆绑应用程序使用了后一种技术。例如, the permissions system 在Django的身份验证框架中,使用 Permission 带有外键的模型 ContentType 这让 Permission 表示“可以添加日志”或“可以删除新闻报道”等概念。

这个 ContentTypeManager

class ContentTypeManager

ContentType 还有一个自定义管理器, ContentTypeManager ,其中添加了以下方法:

clear_cache()

清除由使用的内部缓存 ContentType 跟踪为其创建的模型 ContentType 实例。您可能永远不需要自己调用这个方法;Django会在需要时自动调用它。

get_for_id(id)

查找A ContentType 按ID。因为此方法使用的共享缓存与 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 要通过引用的对象 natural key 在反序列化过程中。

这个 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 模型A ForeignKey 它必须选择一个而且只有一个模型来存储标签。ContentTypes应用程序提供了一个特殊的字段类型 (GenericForeignKey )它围绕着这一点工作,并允许与任何模型的关系:

class GenericForeignKey

设置一个 GenericForeignKey

  1. 给你的模型A ForeignKeyContentType . 此字段的常用名称是“内容类型”。

  2. 为模型提供一个字段,该字段可以存储与之相关的模型中的主键值。对于大多数型号,这意味着 PositiveIntegerField . 此字段的常用名称是“object_id”。

  3. 给你的模型A GenericForeignKey ,并将上述两个字段的名称传递给它。如果这些字段被命名为“content_type”和“object_id”,则可以省略这一点——它们是默认字段名。 GenericForeignKey 会寻找。

不同于 ForeignKey ,数据库索引是 not 在上自动创建 GenericForeignKey ,因此建议您使用 Meta.indexes 若要添加自己的多列索引,请执行以下操作。此行为 may change 在未来。

for_concrete_model

如果 False ,该字段将能够引用代理模型。默认是 True . 这反映了 for_concrete_model 参数 get_for_model() .

主键类型兼容性

“object_id”字段不必与相关模型上的主键字段类型相同,但它们的主键值必须通过其 get_db_prep_value() 方法。

例如,如果您希望允许与具有 IntegerFieldCharField 主键字段,可以使用 CharField 因为整数可以被强制为字符串 get_db_prep_value() .

为了获得最大的灵活性,您可以使用 TextField 它没有定义最大长度,但是这可能会导致严重的性能损失,具体取决于您的数据库后端。

没有一种适合所有字段类型的解决方案是最佳的。您应该评估期望指向的模型,并确定哪个解决方案对您的用例最有效。

将引用序列化到 ContentType 对象

如果正在序列化数据(例如,在生成 fixtures )从实现通用关系的模型中,您可能应该使用自然键来唯一地标识相关的 ContentType 对象。见 natural keysdumpdata --natural-foreign 更多信息。

这将启用一个类似于用于正常 ForeignKey ;每个 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_typeobject_id 字段仍设置为其原始值,并且 GenericForeignKey 退货 None

>>> guido.delete()
>>> t.content_object  # returns None

由于路途不便 GenericForeignKey ,则不能将此类字段直接与筛选器一起使用 (filter()exclude() 例如)通过数据库API。因为一个 GenericForeignKey 不是普通的字段对象,这些示例将 not 工作:

# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)

同样地, GenericForeignKey S不出现在 ModelForm S

反转一般关系

class GenericRelation
related_query_name

默认情况下,返回此对象的相关对象上的关系不存在。设置 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.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>]>

您还可以使用 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 []>

定义 GenericRelation 具有 related_query_name 设置允许从相关对象进行查询::

tags = GenericRelation(TaggedItem, related_query_name="bookmark")

这将启用筛选、排序和其他查询操作 Bookmark 从… TaggedItem

>>> # 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_fkobject_primary_key 要创建其通用外键,则 GenericRelation 回到这里需要这样定义:

tags = GenericRelation(
    TaggedItem,
    content_type_field="content_type_fk",
    object_id_field="object_primary_key",
)

还要注意,如果删除的对象 GenericRelation ,任何具有 GenericForeignKey 指向它也将被删除。在上面的示例中,这意味着如果 Bookmark 对象已被删除,任何 TaggedItem 指向它的对象将同时被删除。

不像 ForeignKeyGenericForeignKey 不接受 on_delete 参数自定义此行为;如果需要,可以通过不使用 GenericRelation ,并且可以通过 pre_delete 信号。

一般关系和聚合

Django's database aggregation APIGenericRelation 。例如,您可以找出所有书签有多少标签:

>>> 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)

返回A GenericInlineFormSet 使用 modelformset_factory() .

你必须提供 ct_fieldfk_field 如果它们与默认值不同, content_typeobject_id 分别。其他参数与 modelformset_factory()inlineformset_factory() .

这个 for_concrete_model 参数对应于 for_concrete_model 参数 GenericForeignKey .

管理中的一般关系

这个 django.contrib.contenttypes.admin 模块提供 GenericTabularInlineGenericStackedInline (子类) GenericInlineModelAdmin

这些类和函数允许在表单和管理中使用通用关系。见 model formsetadmin 有关详细信息的文档。

class GenericInlineModelAdmin

这个 GenericInlineModelAdmin 类从继承所有属性 InlineModelAdmin 类。但是,它为处理一般关系添加了一些自己的内容:

ct_field

的名字 ContentType 模型上的外键字段。默认为 content_type .

ct_fk_field

表示相关对象ID的整数字段的名称。默认为 object_id .

class GenericTabularInline
class GenericStackedInline

亚类 GenericInlineModelAdmin 分别使用堆叠和表格布局。

GenericPrefetch()

New in Django 5.0.
class GenericPrefetch(lookup, querysets=None, to_attr=None)[源代码]

此查找类似于 Prefetch() 并且它应该只用于 GenericForeignKey 。这个 querysets 参数接受一组查询集,每个查询集对应一个不同的 ContentType 。这对以下方面很有用 GenericForeignKey 具有非齐次的结果集。

>>> 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>]>