Django包含一个contenttypes
应用,它可以追踪Django项目里安装的所有应用,提供一个高层次的、通用的接口用于与模型交互。
Contenttypes应用的核心是ContentType
模型,位于django.contrib.contenttypes.models.ContentType
。 ContentType
的实例表示并保存项目中安装的模型的信息,每当有新的模型时会自动创建新的ContentType
实例。
ContentType
的实例可以返回它们表示的模型类以及从这些模型查询对象。 ContentType
还有一个自定义的管理器,它添加的方法可以与ContentType
一起工作,用于获得特定模型的ContentType
实例。
模型和ContentType
之间的关系还可以用于模型的一个实例和任意一个已经安装的模型的实例建立“generic关联”。
Contenttypes 框架包含在django-admin startproject
创建的默认的INSTALLED_APPS
列表中,但如果你移除了它或者你手动创建 INSTALLED_APPS
列表,你可以通过添加'django.contrib.contenttypes'
到你的 INSTALLED_APPS
设置中来启用它。
安装contenttypes框架通常是个好主意;几个Django的其他捆绑应用程序需要它:
authentication framework
用它来授用户权限给特殊的模型。ContentType
模型¶ContentType
¶每一个ContentType
实例有两个字段,共同来唯一描述一个已经安装的模型。
app_label
¶模型所在的应用的名称。 这取自模型的app_label
属性,仅包含应用的Python导入路径的最后一个部分;例如django.contrib.contenttypes
的app_label
为contenttypes
。
model
¶模型的类的名称。
此外,还有下面的属性︰
name
¶Contenttype适于阅读的名称。 它取之于模型的verbose_name
属性。
让我们看看一个例子,看看它如何工作。 如果你已经安装contenttypes
应用,然后添加sites应用
到你的INSTALLED_APPS
设置,并运行manage.py migrate
来安装它,那么模型django.contrib.sites.models.Site
将安装到你的数据库中。 同时将创建ContentType
的一个具有以下值的新实例︰
ContentType
实例上的方法¶ContentType
的实例方法允许你从ContentType
实例得到它所代表的模型,或者从该模型获取对象:
ContentType.
get_object_for_this_type
(**kwargs)¶接收ContentType
表示的模型所接收的查找参数,对该模型做一个get()查找
,然后返回相应的对象。
ContentType.
model_class
()¶返回此ContentType
实例所表示的模型类。
例如,我们可以通过ContentType
查找到User
模型︰
>>> 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>
get_object_for_this_type()
和model_class()
一起使用可以实现两个极其重要的使用场景︰
app_label
和model
给ContentType
在运行时查找,然后使用这个模型类或从它获取对象。ContentType
作为一种绑定它的实例到特定模型类的方式,然后使用这些方法来获取对那些模型的访问。几个Django捆绑的应用就利用到后者这个技术。
例如,在Django身份验证框架中,权限系统
使用的Permission
模型有一个到ContentType
的外键;这允许Permission
表示“可以添加博客条目”或“可以删除新闻故事”等概念。
ContentTypeManager
¶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
对象在反序列化期间通过natural key来引用。
get_for_model()
方法特别有用,当你知道你需要与ContentType
交互但不想要去获取模型元数据以执行手动查找的麻烦︰
>>> from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)
<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, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self): # __unicode__ on Python 2
return self.tag
普通的ForeignKey
只能“指向”一个其它的模型,这是说如果TaggedItem
模型用一个 ForeignKey
,它必须且只能选择为一个模型来保存tag。 Contenttypes应用提供一个特殊的字段(GenericForeignKey
) 避免了这个问题并且允许和任何一个模型建立关联关系:
GenericForeignKey
¶建立GenericForeignKey
需要三个步骤:
ForeignKey
字段到ContentType
。 一般命名为“content_type”。PositiveIntegerField
字段。 这个名字通常命名为“object_id”。GenericForeignKey
字段, 把上面提到的那两个字段的名称传给它。 如果这两个字段名字分别为“content_type” 和 “object_id”, 你就可以省略他们 – GenericForeignKey
默认的会自动去查找这个两个命名的字段。for_concrete_model
¶如果为False
,那么字段将可以引用代理模型。 默认是True
。 它映射for_concrete_model
参数到get_for_model()
。
主键类型的兼容性。
“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
不会出现在ModelForm
中。
GenericRelation
¶默认情况下,相关对象返回到该对象的关系不存在。 设置 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)
TaggedItems
的每个实例都会有一个tags
属性,可以用来获取相关的 Bookmark
:
>>> 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>]>
定义一个GenericRelation
伴有related_query_name
可以允许从相关联的对象中查询。
tags = GenericRelation(TaggedItem, related_query_name='bookmarks')
这允许你从Bookmark
执行过滤筛选, 排序, 和其他的查询操作TaggedItem
:
>>> # 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>]>
正如GenericForeignKey
接受内容类型和对象ID字段的名称作为参数,GenericRelation
也是如此。如果具有通用外键的模型对这些字段使用非默认名称,则必须在设置GenericRelation
时传递字段的名称。 例如,假如 object_primary_key
model关联到上述所用的字段用 content_type_fk
和TaggedItem
两个名称来创建一个 generic foreign 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’s database aggregation API适用于GenericRelation
。 例如,您可以找到所有书签的标签数量:
>>> Bookmark.objects.aggregate(Count('tags'))
{'tags__count': 3}
django.contrib.contenttypes.forms
模块提供:
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
。
如果它们分别与默认值,content_type
和fk_field
不同,则必须提供ct_field
和object_id
。 其他参数与modelformset_factory()
和inlineformset_factory()
中记录的参数类似。
for_concrete_model
参数对应于GenericForeignKey
上的for_concrete_model
参数。
django.contrib.contenttypes.admin
模块提供GenericTabularInline
和GenericStackedInline
(GenericInlineModelAdmin
的子类别)
这些类和函数确保了generic relations在forms 和 admin的使用。 有关详细信息,请参阅model formset和admin文档。
GenericInlineModelAdmin
¶GenericInlineModelAdmin
类继承了来自InlineModelAdmin
类的所有属性。 但是,它添加了一些自己的用于处理通用关系:
ct_field
¶模型上的ContentType
外键字段的名称。 默认为content_type
。
ct_fk_field
¶表示相关对象的ID的整数字段的名称。 默认为object_id
。
GenericTabularInline
¶GenericStackedInline
¶GenericInlineModelAdmin
的子类,分别具有堆叠和表格布局。
2017年9月6日