内容类型框架 — Django 文档

来自菜鸟教程
Django/docs/3.1.x/ref/contrib/contenttypes
跳转至:导航、​搜索

内容类型框架

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

概览

contenttypes 应用程序的核心是 ContentType 模型,它位于 django.contrib.contenttypes.models.ContentTypeContentType 的实例表示并存储有关安装在项目中的模型的信息,并且每当安装新模型时都会自动创建 ContentType 的新实例。

ContentType 的实例具有返回它们所代表的模型类以及从这些模型中查询对象的方法。 ContentType 还有一个 自定义管理器 ,它添加了使用 ContentType 和获取特定模型的 ContentType 实例的方法。

您的模型与 ContentType 之间的关系还可用于启用您的模型之一的实例与您已安装的任何模型的实例之间的“通用”关系。


安装内容类型框架

contenttypes 框架包含在由 django-admin startproject 创建的默认 :setting:`INSTALLED_APPS` 列表中,但如果您已将其删除或手动设置 :setting: `INSTALLED_APPS` 列表,您可以通过将 'django.contrib.contenttypes' 添加到 :setting:`INSTALLED_APPS` 设置来启用它。

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

  • 管理应用程序使用它来记录通过管理界面添加或更改的每个对象的历史记录。
  • Django 的 身份验证框架 使用它来将用户权限绑定到特定模型。


ContentType型号

class ContentType

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

app_label

模型所属的应用程序的名称。 这取自模型的 app_label 属性,仅包含应用程序 Python 导入路径的 last 部分; 例如,django.contrib.contenttypes成为contenttypesapp_label

model

模型类的名称。

此外,以下属性可用:

name

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

让我们看一个例子,看看它是如何工作的。 如果您已经安装了 contenttypes 应用程序,然后将 站点应用程序 添加到您的 :setting:`INSTALLED_APPS` 设置并运行 manage.py migrate要安装它,模型 django.contrib.sites.models.Site 将安装到您的数据库中。 随之而来的是一个新的 ContentType 实例,其值如下:

  • app_label 将设置为 'sites'(Python 路径 django.contrib.sites 的最后一部分)。
  • 型号将设置为'site'


ContentType 实例上的方法

每个 ContentType 实例都有一些方法,允许您从 ContentType 实例获取它所代表的模型,或者从该模型中检索对象:

ContentType.get_object_for_this_type(**kwargs)
ContentType 表示的模型获取一组有效的 查找参数 ,并在该模型上执行 get() 查找 ,返回相应的对象。
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_labelmodel 传递到ContentType 在运行时查找,然后使用模型类或从中检索对象。
  2. 您可以将另一个模型与 ContentType 相关联,作为将其实例绑定到特定模型类的一种方式,并使用这些方法来访问这些模型类。

Django 的一些捆绑应用程序使用了后一种技术。 比如Django的认证框架中的权限系统使用的是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 对象。

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

一个普通的 ForeignKey 只能“指向”另一个模型,这意味着如果 TaggedItem 模型使用 ForeignKey 它必须选择一个并且只有一个模型存储标签。 contenttypes 应用程序提供了一个特殊的字段类型 (GenericForeignKey),它可以解决这个问题,并允许与任何模型建立关系:

class GenericForeignKey

设置 GenericForeignKey 分为三个部分:

  1. 给你的模型一个 ForeignKeyContentType。 该字段的常用名称是“content_type”。

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

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

for_concrete_model

如果False,该字段将能够引用代理模型。 默认值为 True。 这将 for_concrete_model 参数映射到 get_for_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 不是一个普通的字段对象,这些例子将 not 工作:

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

同样,GenericForeignKeys 不会出现在 ModelForms 中。

反向泛型关系

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

使用 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_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 的数据库聚合 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)

使用 modelformset_factory() 返回 GenericInlineFormSet

如果 ct_fieldfk_field 与默认值不同,您必须分别提供 content_typeobject_id。 其他参数类似于 modelformset_factory()inlineformset_factory() 中记录的参数。

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

管理中的通用关系

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

这些类和函数允许在表单和管理中使用通用关系。 有关更多信息,请参阅 模型表单集管理 文档。

class GenericInlineModelAdmin

GenericInlineModelAdmin 类继承了 InlineModelAdmin 类的所有属性。 但是,它添加了一些自己的用于处理通用关系:

ct_field

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

ct_fk_field

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

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