Фреймворк контентных типов¶
В Django входит приложение contenttypes, которое позволяет отслеживать все модели вашего Django проекта. Это приложение предоставляет высокоуровневый, обобщенный интерфейс для работы с вашими моделями.
Обзор¶
В основе приложения contenttypes лежит модель ContentType, которая находится в django.contrib.contenttypes.models.ContentType. Экземпляр ContentType представляет и хранит информацию о моделях, использующихся в вашем проекте, и новые экземпляры модели ContentType создаются автоматически при добавлении новых моделей в проект.
У экземпляров ContentType есть методы, позволяющие получить класс модели, который они представляют, или запросить объекты этой модели. У модели ContentType имеется также собственный менеджер, который предоставляет методы для работы с классом ContentType и для получения экземпляров ContentType для конкретной модели.
Взаимосвязь между ContentType и вашими моделями можно использовать для создания «обобщенных» («generic» ) отношений между экземпляром вашей модели и экземпляром любой другой модели в проекте.
Установка и подключение contenttypes¶
Фреймворк contenttypes включен по умолчанию и находится в списке INSTALLED_APPS файла настроек, созданного вызовом команды django-admin startproject. Если вам необходимо отключить фреймворк или добавить его вручную, просто удалите (или добавьте) в список INSTALLED_APPS приложение 'django.contrib.contenttypes'.
Рекомендуется всегда подключать фреймворк контентных типов в проект, поскольку его наличие требуется для работы ряда других встроенных приложений Django:
Встроенное приложение администрирования Django использует его для журналирования истории добавления или изменения объектов через админку.
Django’s
authentication frameworkuses it to tie user permissions to specific models.
Модель ContentType¶
- class ContentType¶
Каждый экземпляр
ContentTypeсодержит два поля, которые вместе уникальным образом описывают каждую модель в приложении:- app_label¶
Имя приложения в которое входит данная модель. Данные берутся из атрибута
app_labelмодели и включают в себя только последнюю часть пути, который используется для импорта модели. Н-р, в случае «django.contrib.contenttypes» используется значение атрибутаapp_labelдля «contenttypes».
- model¶
Имя модели класса.
Также доступны следующие свойства:
- name¶
«Читабельное» имя модели. Берется из атрибута
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)¶
Takes a set of valid lookup arguments for the model the
ContentTyperepresents, and doesa get() lookupon that model, returning the corresponding object.
- ContentType.model_class()¶
Возвращает класс модели, представленной данным экземпляром
ContentType.
For example, we could look up the
ContentType for the
User model:
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label='auth', model='user')
>>> user_type
<ContentType: user>
And then use it to query for a particular
User, or to get access
to the User model class:
>>> 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(), дает нам два крайне важных и полезных варианта их использования:
Воспользовавшись этими методами, вы можете писать высокоуровневый , обобщенный(generic) код, и выполнять запрос к любой установленной в приложении модели. Вместо того, чтобы импортировать конкретные модели «по одиночке», вы можете передать нужные параметры
app_labelиmodelв классContentTypeво время выполнения, и затем работать с полученной моделью класса или получить конкретные объекты этой модели.Вы можете связывать другую модель с
ContentTypeдля привязки экземпляров этой модели с любыми произвольными классами моделей проекта, и использовать указанные методы для доступа к этим моделям.
Некоторые из встроенных приложений Django используют последний подход. Н-р, в системе полномочий( permissions system), в фреймворке аутентификации Django, в модели Permission используется внешний ключ(foreign key) к ContentType; это позволяет создать обобщенную связь с различными моделями и реализовать концепцию ограничений, такую как «пользователь может добавить запись в блог» или «пользователь может удалить сообщение из новостей».
ContentTypeManager¶
- class ContentTypeManager¶
Класс
ContentTypeобладает собственным менеджером,ContentTypeManager, который включает в себя следующие методы:- clear_cache()¶
Очищает внутренний кэш, используемый
ContentTypeдля отслеживания моделей, для которых были созданы экземплярыContentType. Вероятно, вам никогда не придется вызывать этот метод напрямую, поскольку Django вызывает его автоматически, при необходимости.
- get_for_id(id)¶
Получение экземпляра
ContentTypeпо идентификатору(ID). Поскольку данный метод использует тот же разделяемый кэш, что и метод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_model=Falseпозволяет получитьContentTypeдля прокси-модели.
- get_by_natural_key(app_label, model)¶
Возвращает экземпляр
ContentType, однозначно определенный переданной меткой приложения и именем модели. Главной задачей этого метода является предоставление возможности ссылаться на объекты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>
Обобщенные связи(generic relations)¶
Добавление внешнего ключа в одну из ваших меделей на 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:Создайте в вашей модели поле типа
ForeignKey, указав в качестве внешней моделиContentType. Обычно такому полю дают имя «content_type».Создайте в вашей модели поле, которое будет хранить значения первичных ключей экземпляров модели, с которой вы создаете связь. Для большинства моделей, это поле типа
PositiveIntegerField. Обычно такому полю дают имя «object_id».Создайте в вашей модели поле типа
GenericForeignKey, и передайте ему в качестве аргументов, имена полей созданных ранее. Если эти поля названы «content_type» и «object_id», вы можете не передавать их, – эти имена используются вGenericForeignKeyпо умолчанию.
- for_concrete_model¶
При
False, поле может ссылаться на прокси-модель. Отображает аргументfor_concrete_modelметодаget_for_model(). По умолчанию равноTrue.
Тип первичного ключа
Поле «object_id» не обязательно должно быть того же типа, что и у первичного ключа в привязанной модели, но должно соблюдаться условие, что значения первичного ключа могут быть приведены к тому же типу, что и у поля «object_id « методом get_db_prep_value().
Н-р, если вы хотите создать обобщенные отношения с моделями, использующими в качестве первичных ключей типы IntegerField или CharField, вы можете использовать тип CharField для вашего поля «object_id», поскольку целочисленные значения могут быть корректно приведены к строковым методом get_db_prep_value().
Для максимальной гибкости можно использовать тип TextField, который не накладывает ограничения на длину строки, но такое решение может негативно отразиться на производительности, в зависимости от используемой базы данных.
Не существует универсального решения для выбора наилучшего типа поля. Вы должны изучить модели с которыми собираетесь работать, и на основании этого принять решение о выборе типа для поля, руководствуясь принципами эффективности.
Сериализация связей с ContentType
При сериализации данных модели (например, при создании fixtures) , которая имеет обобщенные связи, вам вероятно необходимо будет воспользоваться натуральным ключом, чтобы корректно определить связи с объектами ContentType. Смотрите натуральные ключи и dumpdata --natural-foreign для дополнительной информации.
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)
Также GenericForeignKeys не отображется в ModelForms.
Обратная обобщенная связь(reverse generic relations)¶
- class GenericRelation¶
По умолчанию обратная связь не создается. Чтобы создать такую связь, укажите параметр
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 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 []>
Создав GenericRelation с related_query_name, можно использовать связь в запросах:
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>]>
Также как GenericForeignKey, GenericRelation принимает аргументами имена полей content-type и object-ID . Если модель, имеющая обобщенный внешний ключ не использует имена по-умолчанию для этих полей, а любые другие, – вы должны передать эти имена полей в GenericRelation при его инициализации. Н-р, если бы мы использовали в модели TaggedItem поля с именами content_type_fk и object_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’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}
Обобщенные связи в формах¶
Модуль django.contrib.contenttypes.forms предлагает нам следующее:
Фабрика создания набора форм,
generic_inlineformset_factory(), для использования сGenericForeignKey.
- 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, absolute_max=None, can_delete_extra=True)¶
Возвращает
GenericInlineFormSet, используяmodelformset_factory().Вы должны предоставить имена для
ct_fieldиobject_idесли они отличаются от значений по умолчанию, -content_typeиobject_id. Прочие параметры аналогичны тем, что описаны вmodelformset_factory()иinlineformset_factory().Аргумент
for_concrete_modelсоответствует параметруfor_concrete_modelдляGenericForeignKey.Changed in Django 3.2:The
absolute_maxandcan_delete_extraarguments were added.
Обобщенные связи в админке¶
Модуль django.contrib.contenttypes.admin предоставляет GenericTabularInline и GenericStackedInline (дочерние классы GenericInlineModelAdmin)
Эти классы и функции позволяют использовать обобщенные отношения объектов при создании форм и в админке Django. За дополнительной информацией обратитесь к модель набора форм и admin .
- class GenericInlineModelAdmin¶
Класс
GenericInlineModelAdminнаследует все свойства классаInlineModelAdmin. Но также имеет ряд собственных атрибутов для работы с обобщенными связями:- ct_field¶
Имя поля внешнего ключа
ContentTypeмодели. По умолчаниюcontent_type.
- ct_fk_field¶
Имя целочисленного поля, которое хранит идентификатор конкретного объекта связанной модели. По умолчанию
object_id.
- class GenericTabularInline¶
- class GenericStackedInline¶
Подклассы
GenericInlineModelAdminпозволяющие настраивать отображение данных в сложенном(stacked) или табличном(tabular) виде, соответственно.