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 的其他一些捆绑的应用程序都需要它:
认证框架
使用它将用户权限与特定模型绑定。ContentType
模型¶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
实例,其值如下:
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
>>> 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_model_class()
共同实现了两个极其重要的用例:
app_label
和 model
传递到一个 ContentType
的查找中,然后与模型类一起工作,或者从中检索对象。ContentType
相关联,以此将它的实例与特定的模型类绑定,并使用这些方法来获取对这些模型类的访问。Django 的几个捆绑应用都使用了后一种技术。例如,Django 的认证框架中的 :class:``权限系统 <django.contrib.auth.models.Permission>` 使用了一个 Permission
模型,该模型的外键为 ContentType
;这使得 Permission
可以表示“可以添加博客条目”或“可以删除新闻报道”等概念。
ContentTypeManager
¶ContentTypeManager
¶ContentType
also has a custom
manager, ContentTypeManager
,
which adds the following methods:
clear_cache
()¶Clears an internal cache used by
ContentType
to keep track
of models for which it has created
ContentType
instances. You
probably won't ever need to call this method yourself; Django will call
it automatically when it's needed.
get_for_id
(id)¶Lookup a ContentType
by ID.
Since this method uses the same shared cache as
get_for_model()
,
it's preferred to use this method over the usual
ContentType.objects.get(pk=id)
get_for_model
(model, for_concrete_model=True)¶Takes either a model class or an instance of a model, and returns the
ContentType
instance
representing that model. for_concrete_model=False
allows fetching
the ContentType
of a proxy
model.
get_for_models
(*models, for_concrete_models=True)¶Takes a variadic number of model classes, and returns a dictionary
mapping the model classes to the
ContentType
instances
representing them. for_concrete_models=False
allows fetching the
ContentType
of proxy
models.
get_by_natural_key
(app_label, model)¶Returns the ContentType
instance uniquely identified by the given application label and model
name. The primary purpose of this method is to allow
ContentType
objects to be
referenced via a natural key
during deserialization.
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>
Adding a foreign key from one of your own models to
ContentType
allows your model to
effectively tie itself to another model class, as in the example of the
Permission
model above. But it's possible
to go one step further and use
ContentType
to enable truly
generic (sometimes called "polymorphic") relationships between models.
For example, it could be used for a tagging system like so:
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
A normal ForeignKey
can only "point
to" one other model, which means that if the TaggedItem
model used a
ForeignKey
it would have to
choose one and only one model to store tags for. The contenttypes
application provides a special field type (GenericForeignKey
) which
works around this and allows the relationship to be with any
model:
GenericForeignKey
¶There are three parts to setting up a
GenericForeignKey
:
ForeignKey
to ContentType
. The usual
name for this field is "content_type".PositiveIntegerField
. The usual name
for this field is "object_id".GenericForeignKey
, and
pass it the names of the two fields described above. 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
¶If False
, the field will be able to reference proxy models. Default
is True
. This mirrors the for_concrete_model
argument to
get_for_model()
.
Primary key type compatibility
The "object_id" field doesn't have to be the same type as the
primary key fields on the related models, but their primary key values
must be coercible to the same type as the "object_id" field by its
get_db_prep_value()
method.
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()
.
For maximum flexibility you can use a
TextField
which doesn't have a
maximum length defined, however this may incur significant performance
penalties depending on your database backend.
There is no one-size-fits-all solution for which field type is best. You should evaluate the models you expect to be pointing to and determine which solution will be most effective for your use case.
Serializing references to ContentType
objects
If you're serializing data (for example, when generating
fixtures
) from a model that implements
generic relations, you should probably be using a natural key to uniquely
identify related ContentType
objects. See natural keys and
dumpdata --natural-foreign
for more information.
This will enable an API similar to the one used for a normal
ForeignKey
;
each TaggedItem
will have a content_object
field that returns the
object it's related to, and you can also assign to that field or use it when
creating a 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>
If the related object is deleted, the content_type
and object_id
fields
remain set to their original values and the GenericForeignKey
returns
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)
Likewise, GenericForeignKey
s
does not appear in ModelForm
s.
GenericRelation
¶The relation on the related object back to this object doesn't exist by
default. Setting related_query_name
creates a relation from the
related object back to this one. This allows querying and filtering
from the related object.
If you know which models you'll be using most often, you can also add a "reverse" generic relationship to enable an additional API. For example:
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(TaggedItem)
Bookmark
instances will each have a tags
attribute, which can
be used to retrieve their associated 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>]>
You can also use add()
, create()
, or set()
to create
relationships:
>>> 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>]>
The remove()
call will bulk delete the specified model objects:
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
The clear()
method can be used to bulk delete all related objects for an
instance:
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
Defining GenericRelation
with
related_query_name
set allows querying from the related object:
tags = GenericRelation(TaggedItem, related_query_name='bookmark')
This enables filtering, ordering, and other query operations on Bookmark
from TaggedItem
:
>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmark__url__contains='django')
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
If you don't add the related_query_name
, you can do the same types of
lookups manually:
>>> 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>]>
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. In the example above, this means that
if a Bookmark
object were deleted, any TaggedItem
objects pointing at
it would be deleted at the same time.
Unlike ForeignKey
,
GenericForeignKey
does not accept
an on_delete
argument to customize this
behavior; if desired, you can avoid the cascade-deletion by not using
GenericRelation
, and alternate
behavior can be provided via the pre_delete
signal.
Django's database aggregation API works with a
GenericRelation
. For example, you
can find out how many tags all the bookmarks have:
>>> Bookmark.objects.aggregate(Count('tags'))
{'tags__count': 3}
The django.contrib.contenttypes.forms
module provides:
BaseGenericInlineFormSet
generic_inlineformset_factory()
, for use with
GenericForeignKey
.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)¶Returns a GenericInlineFormSet
using
modelformset_factory()
.
You must provide ct_field
and fk_field
if they are different from
the defaults, content_type
and object_id
respectively. Other
parameters are similar to those documented in
modelformset_factory()
and
inlineformset_factory()
.
The for_concrete_model
argument corresponds to the
for_concrete_model
argument on GenericForeignKey
.
The django.contrib.contenttypes.admin
module provides
GenericTabularInline
and
GenericStackedInline
(subclasses of
GenericInlineModelAdmin
)
These classes and functions enable the use of generic relations in forms and the admin. See the model formset and admin documentation for more information.
GenericInlineModelAdmin
¶The GenericInlineModelAdmin
class inherits all properties from an
InlineModelAdmin
class. However,
it adds a couple of its own for working with the generic relation:
ct_field
¶The name of the
ContentType
foreign key
field on the model. Defaults to content_type
.
ct_fk_field
¶The name of the integer field that represents the ID of the related
object. Defaults to object_id
.
GenericTabularInline
¶GenericStackedInline
¶Subclasses of GenericInlineModelAdmin
with stacked and tabular
layouts, respectively.
3月 04, 2021