内容类型框架

Django 包含一个 contenttypes 应用程序,可以跟踪在您的 Django 项目中安装的所有模型,并提供一个高级的通用接口来处理您的模型。

概述

内容类型应用程序的核心是 ContentType 模型,它位于 django.contrib.contenttypes.models.ContentTypeContentType 的实例表示并存储有关项目中安装的模型的信息,并且每当安装新的模型时,都会自动创建新的 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 将变为 contenttypesapp_label

model

模型类的名称。

此外,以下属性可用

name[source]

内容类型的可读名称。这取自模型的 verbose_name 属性。

让我们看一个示例来说明它是如何工作的。如果您已经安装了 contenttypes 应用程序,然后将 sites 应用程序 添加到您的 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(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() 共同支持两个极其重要的用例。

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

  2. 您可以将另一个模型关联到 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 包括三个部分。

  1. 为您的模型提供指向 ContentTypeForeignKey。此字段的常用名称为“content_type”。

  2. 为你的模型添加一个字段,用于存储你将要关联的模型的主键值。对于大多数模型,这意味着一个PositiveIntegerField。这个字段通常命名为“object_id”。

  3. 为你的模型添加一个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”字段相同的类型。

例如,如果你想允许泛型关系关联具有IntegerFieldCharField主键字段的模型,你可以使用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_typeobject_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

默认情况下,相关对象回指此对象的关联关系不存在。设置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_nameGenericRelation定义允许从相关对象进行查询。

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

这使得能够从TaggedItemBookmark进行过滤、排序和其他查询操作。

>>> # 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对象将同时被删除。

ForeignKey 不同,GenericForeignKey 不接受 on_delete 参数来自定义此行为;如果需要,可以通过不使用 GenericRelation 来避免级联删除,并且可以通过 pre_delete 信号提供替代行为。

泛型关系和聚合

Django 的数据库聚合 APIGenericRelation 一起使用。例如,您可以找出所有书签有多少个标签。

>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}

表单中的泛型关系

django.contrib.contenttypes.forms 模块提供

class BaseGenericInlineFormSet[source]
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_typeobject_id 分别不同,则必须提供 ct_fieldfk_field。其他参数类似于 modelformset_factory()inlineformset_factory() 中记录的参数。

for_concrete_model 参数对应于 GenericForeignKey 上的 for_concrete_model 参数。

管理站点中的泛型关系

django.contrib.contenttypes.admin 模块提供 GenericTabularInlineGenericStackedInlineGenericInlineModelAdmin 的子类)。

这些类和函数支持在表单和管理站点中使用泛型关系。有关更多信息,请参阅 模型表单集管理站点 文档。

class GenericInlineModelAdmin[source]

GenericInlineModelAdmin 类继承了 InlineModelAdmin 类中的所有属性。但是,它添加了一些用于处理泛型关系的属性。

ct_field

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

ct_fk_field

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

class GenericTabularInline[source]
class GenericStackedInline[source]

分别具有堆叠和表格布局的 GenericInlineModelAdmin 的子类。

GenericPrefetch()

Django 5.0 中的新功能。
class GenericPrefetch(lookup, querysets, to_attr=None)[source]

此查找类似于 Prefetch(),并且只能用于 GenericForeignKeyquerysets 参数接受一个查询集列表,每个查询集对应一个不同的 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>]>
返回顶部