Django 包含一个contenttypes
应用,它可以追踪安装在你的Django 项目里的所有应用,并提供一个高层次的、通用的接口用于与你的模型进行交互。
概述
Contenttypes 的核心应用是ContentType
模型,存在于 django.contrib.contenttypes.models.ContentType
。ContentType
的实例表示并存储你的项目当中安装的应用的信息,并且新的 ContentType
实例每当新的模型安装时会自动创建。
ContentType
实例具有返回它们表示的模型类的方法,以及从这些模型查询对象的方法。ContentType
还有一个自定义的管理器用于添加方法来与ContentType
工作,以及用于获得ContentType
实例的特定模型。
你的模型和ContentType
之间的关系还可以用于一个模型实例和任意一个已经安装的模型的实例建立“generic关联”。
安装Contenttypes 框架
Contenttypes 框架包含在django-admin startproject
创建的默认的INSTALLED_APPS
列表中,但如果你移除了它或者你手动创建 INSTALLED_APPS
列表,你可以通过添加'django.contrib.contenttypes'
到你的 INSTALLED_APPS
设置中来启用它。
一般来说,安装Contenttypes 框架是个好主意。许多Django 的捆绑应用需要它:
- Admin 应用使用它来记录通过Admin 界面添加或更改每个对象的历史。
- Django 的
authentication 框架
用它来授用户权限给特殊的模型。
的ContentType
class ContentType
[source]
每一个ContentType
实例有两个字段,共同来唯一描述一个已经安装的模型。
app_label
模型所在的应用的名称。 这取自模型的app_label
属性,并只包括应用的Python 导入路径的最后的部分。例如,”django.contrib.contenttypes”的app_label
是”contenttypes”。
model
模型的类的名称。
此外,下面的属性是可用的︰
name
[source]
Contenttype 的人类可读的的名称。它取之于模型的verbose_name
属性。
Changed in Django 1.8:
在Django 1.8 之前,name
属性是ContentType
模型的一个字段。
让我们看看一个例子,看看它如何工作。如果你已经安装contenttypes
应用,那么添加sites应用
到你的INSTALLED_APPS
设置并运行manage.py migrate
来安装它,模型django.contrib.sites.models.Site
将安装到你的数据库中。同时将创建ContentType
的一个具有以下值的新实例︰
方法ContentType
每一个 ContentType
都有一些方法允许你用ContentType
实例来到达它所代表的model, 或者从model取出对象:
ContentType.``get_object_for_this_type
(**kwargs)[source]
接收ContentType
表示的模型所接收的查询参数,对该模型做一次get() 查询
,然后返回相应的对象。
ContentType.``model_class
()[source]
返回此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()
一起使用可以实现两个极其重要的功能︰
- 使用这些方法,你可以编写高级别的泛型代码,执行查询任何已安装的模型 —— 而不是导入和使用单一特定模型的类,可以通过
app_label
和model
到ContentType
在运行时查找,然后使用这个模型类或从它获取对象。 - 你可以关联另一个模型到
ContentType
作为一种绑定它到特定模型类的方式,然后使用这些方法来获取对那些模型的访问。
几个Django 捆绑的应用利用后者的技术。例如,Django 的认证框架中的权限系统
使用的Permission
模型具有一个外键到ContentType
;这允许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_model=False
允许获取代理模型的ContentType
。
get_by_natural_key
(app_label, model)[source]
返回由给定的应用标签和模型名称唯一标识的ContentType
实例。这种方法的主要目的是为允许ContentType
对象在反序列化期间通过自然键来引用。
get_for_model()
方法特别有用,当你知道你需要与ContentType
交互但不想要去获取模型元数据以执行手动查找的麻烦︰
>>> from django.contrib.auth.models import User
>>> user_type = ContentType.objects.get_for_model(User)
>>> user_type
<ContentType: user>
通用关系
在你的model添加一个外键到 ContentType
这将允许你更快捷的绑定自身到其他的model class,就像上述的 Permission
model 一样。但是它非常有可能进一步的利用 ContentType
来实现真正的 generic (有时称之为多态) relationships 在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)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self): # __unicode__ on Python 2
return self.tag
一个普通的 ForeignKey
只能指向其他任意一个model,这是说 TaggedItem
model用一个 ForeignKey
只能一个,并且也只能有这么一个model粗存tags。contenttypes application提供了一个特殊的字段 (GenericForeignKey
) 避免了这个问题并且允许你和任何一个model建立关联关系。
class GenericForeignKey
[source]
三个步骤建立 GenericForeignKey
:
- 给你的model设置一个
ForeignKey
字段到ContentType
. 一般命名为“content_type”. - 给你的model设置一个字段,用来储存你想要关联的model主键值。对于大多数model,,这是一个
PositiveIntegerField
字段。并且通常命名为 “object_id”. - 给你的model一个
GenericForeignKey
字段, 把1,2点提到的那两个字段的名词传给他。如果这两个字段名字分别为“content_type” 和 “object_id”, 你就可以省略他们 –GenericForeignKey
默认的会自动去查找这个两个命名的字段。
for_concrete_model
如果为False
,那么字段将会涉及到proxy models(代理模型)。默认是True
. 这映射了 for_concrete_model
的参数到 get_for_model()
.
allow_unsaved_instance_assignment
New in Django 1.8.
与ForeignKey.allow_unsaved_instance_assignment
类似。
自1.7版起已弃用:此类过去在django.contrib.contenttypes.generic
中定义。将从Django 1.9中删除从此旧位置导入的支持。
主键类型的兼容性。
“object_id” 字段并不总是相同的,这是由于储存在相关模型中的主键类型的关系,但是他们的主键值必须被转变为相同类型,这是通过 “object_id” 字段 的 get_db_prep_value()
方法。
例如, 如果你想要简历generic 关系到一个IntegerField
或者CharField
为主键的模型, 你可以使用 CharField
给 “object_id”字段,因为数字是可以被 get_db_prep_value()
转化为字母的。
为了更大的灵活性你也可以用 TextField
,一个没有限制最大长度的字段,,然而这可能给你的数据库带来巨大的影响。
这里没有一个一刀切的解决办法来应对哪个字段类型最好的问题。你应该估摸一下。哪些models 你想要关联,并根据此决定一个最佳方案。(废话。)
序列化ContentType
的对象引用。
如果你想要序列化一个建立了 generic关系的model数据(for example, when generating fixtures
) ,你应该用一个自然键来唯一的标识相关的 ContentType
对象。有关详细信息,请参阅natural keys和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>
由于 GenericForeignKey
完成的方式问题,,你没有办法用这个字段直接执行数据库API,filters的操作。比如 (filter()
and exclude()
, for example) 。因为一个 GenericForeignKey
不是一个普通的字段对象t, 这些例子是不会工作的:
# 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
[source]
自1.7版起已弃用:此类过去在django.contrib.contenttypes.generic
中定义。将从Django 1.9中删除从此旧位置导入的支持。
related_query_name
New in Django 1.7.
默认情况下,相关对象返回到该对象的关系不存在。设置 related_query_name
来创建一个对象从关联对象返回到对象自身。这允许查询和筛选相关的对象。
如果你知道你最经常使用哪种型号的,你还可以添加一个“反向”的通用关系,以使其能附加一个附加的API。 例如:
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()
[<TaggedItem: django>, <TaggedItem: python>]
New in Django 1.7.
定义一个GenericRelation
伴有related_query_name
可以允许从相关联的对象中查询。
tags = GenericRelation(TaggedItem, related_query_name='bookmarks')
这允许你从TaggedItem
执行过滤筛选, 排序, 和其他的查询操作Bookmark
:
>>> # Get all tags belonging to books containing `django` in the url
>>> TaggedItem.objects.filter(bookmarks__url__contains='django')
[<TaggedItem: django>, <TaggedItem: python>]
就像GenericForeignKey
接受 content-type 和 object-ID 字段的命名为参数,,GenericRelation
也是一样的。如果一个model的 generic foreignkey 字段使用的不是默认的命名,当你创建一个GenericRelation
时一定要显示的传递这个字段的命名给它。例如,假如 TaggedItem
model关联到上述所用的字段用 content_type_fk
和object_primary_key
两个名称来创建一个 generic foreign key,然后一个 GenericRelation
需要这样定义:
tags = GenericRelation(TaggedItem,
content_type_field='content_type_fk',
object_id_field='object_primary_key')
当然,如果你没有添加一个反向的关系,你可以手动做相同类型的查找:
>>> 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)
[<TaggedItem: django>, <TaggedItem: python>]
注意,如果一个GenericRelation
model 在它的GenericForeignKey
的 ct_field
or fk_field
使用了非默认值,(for example, if you had a Comment
model that uses ct_field="object_pk"
), 你就要设置 GenericRelation
中的content_type_field
和/或 object_id_field
来分别匹配 GenericForeignKey
中的ct_field
和 fk_field
:
comments = fields.GenericRelation(Comment, object_id_field="object_pk")
同时请注意,如果你删除了一个具有GenericRelation
的对象, 任何以 GenericForeignKey
指向他的对象也会被删除. 在上面的例子中, 如果一个 Bookmark
对象被删除了,任何指向它的 TaggedItem
对象也会被同时删除。.
不同于 ForeignKey
, GenericForeignKey
并不接受一个 on_delete
参数来控制它的行为:如果你非常渴望这种可控制行为,你应该不使用 GenericRelation
来避免这种级联删除,并且这种行为控制也可以通过 pre_delete
信号来提供。
通用关系和聚合
Django’s database aggregation API不能与GenericRelation
配合使用。例如,您可能会试图尝试以下操作:
Bookmark.objects.aggregate(Count('tags'))
这将不会正确工作,然而generic relation添加了额外的查询集过滤来保证正确的内容类型, 但是 aggregate()
方法并没有被考虑进来。现在, 如果你需要在 generic relations使用聚合,你只能不通过聚合API来计算他们。
Generic relation在表单中
django.contrib.contenttypes.forms
模块提供:
class BaseGenericInlineFormSet
[source]
自1.7版起已弃用:此类过去在django.contrib.contenttypes.generic
中定义。将从Django 1.9中删除从此旧位置导入的支持。
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)[source]
使用modelformset_factory()
返回GenericInlineFormSet
。
如果它们分别与默认值,content_type
和object_id
不同,则必须提供ct_field
和fk_field
。其他参数与modelformset_factory()
和inlineformset_factory()
中记录的参数类似。
for_concrete_model
参数对应于GenericForeignKey
上的for_concrete_model
参数。
自1.7版起已弃用:此函数用于在django.contrib.contenttypes.generic
中定义。将从Django 1.9中删除从此旧位置导入的支持。
Changed in Django 1.7:
min_num
和validate_min
。
管理中的通用关系
django.contrib.contenttypes.admin
模块提供GenericTabularInline
和GenericStackedInline
(GenericInlineModelAdmin
的子类别)
这些类和函数确保了generic relations在forms 和 admin的使用。有关详细信息,请参阅model formset和admin文档。
class GenericInlineModelAdmin
[source]
GenericInlineModelAdmin
类继承了来自InlineModelAdmin
类的所有属性。但是,它添加了一些自己的用于处理通用关系:
ct_field
模型上的ContentType
外键字段的名称。默认为content_type
。
ct_fk_field
表示相关对象的ID的整数字段的名称。默认为object_id
。
自1.7版起已弃用:此类过去在django.contrib.contenttypes.generic
中定义。将从Django 1.9中删除从此旧位置导入的支持。
class GenericTabularInline
[source]
class GenericStackedInline
[source]
GenericInlineModelAdmin
的子类,分别具有堆叠和表格布局。
自1.7版起已弃用:这些类以前在django.contrib.contenttypes.generic
中定义。将从Django 1.9中删除从此旧位置导入的支持。