# Django 5.2.8 documentation

# 内容类型框架

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 的其他一些捆绑的应用程序都需要它：

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

- Django 的 `认证框架` 使用它将用户权限与特定模型绑定。

## `ContentType` 模型

class 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` 实例，其值如下：

- `app_label` 将被设置为 `'sites'` （Python 路径 `django.contrib.sites` 的最后一部分）。

- `model` 将被设置为 `'site'`。

## `ContentType` 实例的方法

每个 `ContentType` 实例都有一些方法，允许你从 `ContentType` 实例获得它所代表的模型，或者从该模型中检索对象。

ContentType.get_object_for_this_type(using=None, **kwargs)

  接受一组有效的 查找参数，用于 `ContentType` 表示的模型，并在该模型上执行 `get() 查找`，返回相应的对象。`using` 参数可用于指定不同于默认数据库的数据库。

ContentType.model_class()

  返回这个 `ContentType` 实例所代表的模型类。

例如，我们可以查找 `User` 模型的 `ContentType`：

    >>> 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_label` 和 `model` 传递到一个 `ContentType` 的查找中，然后与模型类一起工作，或者从中检索对象。

2. 你可以将另一个模型与 `ContentType` 相关联，以此将它的实例与特定的模型类绑定，并使用这些方法来获取对这些模型类的访问。

Django 的几个捆绑应用都使用了后一种技术。例如，Django 的认证框架中的 :class:``权限系统 <django.contrib.auth.models.Permission>` 使用了一个 `Permission` 模型，该模型的外键为 `ContentType`；这使得 `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` 对象在反序列化过程中通过 自然键 被引用。

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.PositiveBigIntegerField()
        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` 分为三步：

  1. 给你的模型一个 `ForeignKey` 到 `ContentType`。这个字段的通常名称是 “content_type”。

  2. Give your model a field that can store primary key values from the models you'll be relating to. For most models, this means a `PositiveBigIntegerField`. The usual name for this field is "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” 字段的类型一致。

例如，如果你想允许通用关系到具有 `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)

Likewise, `GenericForeignKey`s do not appear in `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")

这使得可以从 `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` 接受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 的数据库聚合 API 可以与 `GenericRelation` 一起使用。例如，您可以找出所有书签有多少个标签：

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

### 表单中的通用关系

`django.contrib.contenttypes.forms` 模块提供：

- `BaseGenericInlineFormSet`

- 一个表单集工厂， `generic_inlineformset_factory()`，用于 `GenericForeignKey`。

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)

  使用 `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` 的子类）。

这些类和函数可以在表单和管理中使用通用关系。更多信息请参见 模型表单集 和 管理 文档。

class GenericInlineModelAdmin

  `GenericInlineModelAdmin` 类继承了 `InlineModelAdmin` 类的所有属性。然而，它增加了一些自己的属性来处理通用关系：

  ct_field

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

  ct_fk_field

    代表相关对象 ID 的整数字段的名称。默认值为 `object_id`。

class GenericTabularInline

class GenericStackedInline

  `GenericInlineModelAdmin` 的子类，分别具有堆栈式和表格式布局。

### `GenericPrefetch()`

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