contenttypes框架

Django包含一个contenttypes应用程序,可以跟踪安装在Django驱动项目中的所有模型,为您的模型提供一个高级的通用界面。

概述¶ T0>

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

Instances of ContentType have methods for returning the model classes they represent and for querying objects from those models. ContentType also has a custom manager that adds methods for working with ContentType and for obtaining instances of ContentType for a particular model.

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

安装contenttypes框架

contenttypes框架包含在django-admin startproject创建的默认INSTALLED_APPS列表中,但是如果您已经删除它,或者如果你手动设置你的INSTALLED_APPS列表,你可以通过添加'django.contrib.contenttypes'到你的INSTALLED_APPS

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

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

ContentType模型

的ContentType T0> ¶ T1>

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

app_label T0> ¶ T1>

模型所属应用程序的名称。 This is taken from the app_label attribute of the model, and includes only the last part of the application’s Python import path; django.contrib.contenttypes, for example, becomes an app_label of contenttypes.

模型 T0> ¶ T1>

模型类的名称。

此外,以下属性可用:

名称 T0> ¶ T1>

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

我们来看一个例子,看看它是如何工作的。 If you already have the contenttypes application installed, and then add the sites application to your INSTALLED_APPS setting and run manage.py migrate to install it, the model django.contrib.sites.models.Site will be installed into your database. 除此之外,将使用以下值创建ContentType的新实例:

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

ContentType实例上的方法

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

内容类型。 get_object_for_this_type T0>( ** kwargs T1>)¶ T2>

Takes a set of valid lookup arguments for the model the ContentType represents, and does a get() lookup on that model, returning the corresponding object.

内容类型。 model_class T0>()¶ T1>

返回由此ContentType实例表示的模型类。

例如,我们可以在User模型中查找ContentType

>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.get(app_label="auth", model="user")
<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>

Together, get_object_for_this_type() and model_class() enable two extremely important use cases:

  1. Using these methods, you can write high-level generic code that performs queries on any installed model – instead of importing and using a single specific model class, you can pass an app_label and model into a ContentType lookup at runtime, and then work with the model class or retrieve objects from it.
  2. 您可以将其他模型与ContentType联系起来,以将其实例绑定到特定的模型类,并使用这些方法访问这些模型类。

Django的一些捆绑应用程序使用后一种技术。 For example, the permissions system in Django’s authentication framework uses a Permission model with a foreign key to ContentType; this lets Permission represent concepts like “can add blog entry” or “can delete news story”.

The ContentTypeManager

ContentTypeManager T0> ¶ T1>

ContentType also has a custom manager, ContentTypeManager, which adds the following methods:

clear_cache T0>()¶ T1>

Clears an internal cache used by ContentType to keep track of models for which it has created ContentType instances. 你可能永远不需要自己调用这个方法; Django将在需要时自动调用它。

get_for_id T0>( ID T1>)¶ T2>

通过ID查找ContentType 由于此方法使用与get_for_model()相同的共享缓存,因此优先使用此方法来覆盖通常的ContentType.objects.get(pk=id)

get_for_modelmodelfor_concrete_model = True

获取模型类或模型实例,并返回表示该模型的ContentType实例。 for_concrete_model=False allows fetching the ContentType of a proxy model.

get_for_models* modelsfor_concrete_models = True

需要可变数量的模型类,并返回一个将模型类映射到表示它们的ContentType实例的字典。 for_concrete_models=False allows fetching the ContentType of proxy models.

get_by_natural_keyapp_labelmodel

返回由给定应用程序标签和型号名称唯一标识的ContentType实例。 此方法的主要目的是允许在反序列化过程中通过natural key引用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模型的例子。 But it’s possible to go one step further and use ContentType to enable truly generic (sometimes called “polymorphic”) relationships between models.

一个简单的例子是一个标签系统,可能是这样的:

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

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),它可以解决这个问题,并允许关系与任何模型:

GenericForeignKey T0> ¶ T1>

有三个部分可以设置一个GenericForeignKey

  1. 给你的模型一个ForeignKeyContentType 这个字段的通常名称是“content_type”。
  2. 为您的模型提供一个可以存储您将要关联的模型的主键值的字段。 对于大多数模型,这意味着一个PositiveIntegerField 这个字段的通常名称是“object_id”。
  3. 给你的模型一个GenericForeignKey,并把它传递给上面描述的两个字段的名字。 If these fields are named “content_type” and “object_id”, you can omit this – those are the default field names GenericForeignKey will look for.
for_concrete_model T0> ¶ T1>

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

主键类型兼容性

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

For example, if you want to allow generic relations to models with either IntegerField or CharField primary key fields, you can use CharField for the “object_id” field on your model since integers can be coerced to strings by get_db_prep_value().

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

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

序列化对ContentType对象的引用

如果您是从实现泛型关系的模型序列化数据(例如,生成fixtures)时,您应该使用自然键来唯一标识相关的ContentType对象。 有关更多信息,请参阅natural keysdumpdata --natural-foreign

这将启用与用于正常ForeignKey的API类似的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

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 T0> ¶ T1>
related_query_name T0> ¶ T1>

相关对象返回到此对象的关系在默认情况下不存在。 设置related_query_name从相关对象创建一个关系到这个关系。 这允许从相关对象查询和过滤。

如果您知道最常用的模型,则还可以添加“反向”通用关系以启用其他API。 例如:

from django.db import models
from django.contrib.contenttypes.fields import GenericRelation

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

通过related_query_name集定义GenericRelation允许从相关对象查询:

tags = GenericRelation(TaggedItem, related_query_name='bookmarks')

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

>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmarks__url__contains='django')
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>

当然,如果不添加反向关系,则可以手动执行相同类型的查找:

>>> b = Bookmark.objects.get(url='https://www.djangoproject.com/')
>>> bookmark_type = ContentType.objects.get_for_model(b)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id=b.id)
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>

Just as GenericForeignKey accepts the names of the content-type and object-ID fields as arguments, so too does GenericRelation; if the model which has the generic foreign key is using non-default names for those fields, you must pass the names of the fields when setting up a GenericRelation to it. For example, if the TaggedItem model referred to above used fields named content_type_fk and object_primary_key to create its generic foreign key, then a GenericRelation back to it would need to be defined like so:

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

Note also, that if you delete an object that has a GenericRelation, any objects which have a GenericForeignKey pointing at it will be deleted as well. 在上面的例子中,这意味着如果一个Bookmark对象被删除,指向它的任何TaggedItem对象将被同时删除。

Unlike ForeignKey, GenericForeignKey does not accept an on_delete argument to customize this behavior; if desired, you can avoid the cascade-deletion simply by not using GenericRelation, and alternate behavior can be provided via the pre_delete signal.

泛型关系和聚合

Django’s database aggregation API works with a GenericRelation. 例如,您可以找出所有书签有多少个标签:

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

通用关系形式

django.contrib.contenttypes.forms模块提供:

BaseGenericInlineFormSet T0> ¶ T1>
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

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

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

admin中的一般关系

The django.contrib.contenttypes.admin module provides GenericTabularInline and GenericStackedInline (subclasses of GenericInlineModelAdmin)

这些类和函数使得在表单和管理中使用泛型关系成为可能。 有关更多信息,请参阅model formsetadmin文档。

GenericInlineModelAdmin T0> ¶ T1>

GenericInlineModelAdmin类继承InlineModelAdmin类的所有属性。 然而,它增加了一些自己的工作与通用关系:

ct_field T0> ¶ T1>

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

ct_fk_field T0> ¶ T1>

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

GenericTabularInline T0> ¶ T1>
GenericStackedInline T0> ¶ T1>

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