Фреймворк contenttypes
Django включает приложение contenttypes, которое может отслеживать все модели, установленные в вашем проекте Django, предоставляя высокоуровневый универсальный интерфейс для работы с вашими моделями.
Обзор
В основе приложения contenttypes лежит модель ContentType, которая находится в django.contrib.contenttypes.models.ContentType. Экземпляры модели ContentType представляют и хранят информацию о моделях, установленных в вашем проекте, и новые экземпляры ContentType автоматически создаются при установке новых моделей.
Экземпляры ContentType имеют методы для возвращения классов моделей, которые они представляют, и для запроса объектов из этих моделей. Модель ContentType также имеет специальный менеджер, который добавляет методы для работы с ContentType и для получения экземпляров ContentType для конкретной модели.
Связи между вашими моделями и ContentType также могут быть использованы для настройки «универсальных» отношений между экземпляром одной из ваших моделей и экземплярами любой установленной модели.
Установка фреймворка contenttypes
Фреймворк contenttypes включён в стандартный список INSTALLED_APPS, созданный django-admin startproject, но если вы его удалили или вручную настроили список INSTALLED_APPS, вы можете включить его, добавив 'django.contrib.contenttypes' в ваше значение настройки INSTALLED_APPS.
В целом, рекомендуется иметь установленный фреймворк contenttypes; несколько других встроенных приложений Django требуют его:
- Приложение администрирования использует его для протоколирования истории каждого объекта, добавленного или изменённого через интерфейс администрирования.
- Django's
authentication frameworkиспользует его для привязки разрешений пользователей к конкретным моделям.
Модель ContentType
-
class ContentType -
Каждый экземпляр
ContentTypeимеет два поля, которые вместе уникально описывают установленную модель:-
app_label -
Имя приложения, к которому принадлежит модель. Оно взято из атрибута
app_labelмодели и включает только последнюю часть пути импорта приложения;django.contrib.contenttypes, например, становитсяapp_labelдляcontenttypes.
-
model -
Имя класса модели.
Кроме того, доступно следующее свойство:
-
name -
Человекочитаемое имя типа контента. Оно взято из атрибута
verbose_nameмодели.
-
Давайте рассмотрим пример, чтобы понять, как это работает. Если у вас уже установлено приложение contenttypes, а затем вы добавляете the sites application в настройку INSTALLED_APPS и запускаете manage.py migrate, чтобы установить его, модель django.contrib.sites.models.Site будет установлена в вашей базе данных. Вместе с ней будет создан новый экземпляр ContentType со следующими значениями:
-
app_labelбудет установлено в'sites'(последняя часть пути импортаdjango.contrib.sites). -
modelбудет установлено в'site'.
Методы экземпляров ContentType
Каждый экземпляр ContentType имеет методы, которые позволяют перейти от экземпляра ContentType к представляемой им модели или получить объекты из этой модели:
-
ContentType.get_object_for_this_type(**kwargs) -
Принимает набор допустимых условий поиска для модели, которую представляет
ContentType, и выполняетa get() lookupдля этой модели, возвращая соответствующий объект.
-
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_class() позволяют использовать два крайне важных случая использования:
- Используя эти методы, вы можете написать высокоуровневый универсальный код, который выполняет запросы к любой установленной модели – вместо импорта и использования конкретного класса модели, вы можете передать
app_labelиmodelв запросContentTypeво время выполнения и затем работать с классом модели или извлекать из неё объекты. - Вы можете связать другую модель с
ContentTypeкак способ привязки экземпляров к конкретным классам моделей и использовать эти методы для получения доступа к этим классам моделей.
Несколько встроенных приложений Django используют последний приём. Например, the permissions system в фреймворке аутентификации Django использует модель Permission с внешним ключом к ContentType; это позволяет Permission представлять такие понятия, как «может добавлять запись в блог» или «может удалять новость».
Менеджер 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_models=Falseпозволяет получитьContentTypeпрокси-моделей.
-
get_by_natural_key(app_label, model) -
Возвращает экземпляр
ContentType, однозначно идентифицируемый заданным приложением и именем модели. Основное назначение этого метода — разрешить ссылаться на объектыContentTypeс помощью естественного ключа во время десериализации.
-
Метод get_for_model() особенно полезен, когда вам нужно работать с ContentType, но вы не хотите тратить время на получение метаданных модели для выполнения ручного поиска:
>>> from django.contrib.auth.models import User >>> ContentType.objects.get_for_model(User) <ContentType: user>
Общие отношения
Добавление внешнего ключа от одной из ваших собственных моделей к 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
class Meta:
indexes = [
models.Index(fields=["content_type", "object_id"]),
]
Обычный ForeignKey может указывать только на одну другую модель, что означает, что если модель TaggedItem использовала ForeignKey, она должна была выбрать одну и только одну модель для хранения тегов. Приложение contenttypes предоставляет специальный тип поля (GenericForeignKey) , который обходит это ограничение и позволяет установить отношения с любой моделью:
-
class GenericForeignKey -
Существует три этапа настройки
GenericForeignKey:- Добавьте вашей модели
ForeignKeyкContentType. Обычно для этого поля используется имя «content_type». - Добавьте вашей модели поле, которое может хранить значения первичных ключей из моделей, с которыми вы будете связаны. Для большинства моделей это означает
PositiveIntegerField. Обычно это поле называется «object_id». - Добавьте вашей модели
GenericForeignKeyи передайте ему имена двух полей, описанных выше. Если эти поля названы «content_type» и «object_id», вы можете это пропустить — это имена полей, которыеGenericForeignKeyбудет искать по умолчанию.
В отличие от
ForeignKey, индекс базы данных не создаётся автоматически дляGenericForeignKey, поэтому рекомендуется использоватьMeta.indexes, чтобы добавить собственный индекс по нескольким столбцам. Это поведение может измениться в будущем.-
for_concrete_model -
Если
False, поле сможет ссылаться на прокси-модели. Значение по умолчанию —True. Это дублирует аргументfor_concrete_modelкget_for_model().
- Добавьте вашей модели
Совместимость типов первичных ключей
Поле «object_id» не обязательно должно быть того же типа, что и поля первичных ключей связанных моделей, но их значения первичных ключей должны быть приводимы к типу поля «object_id» с помощью его метода get_db_prep_value().
Например, если вы хотите разрешить общие отношения к моделям с полями первичного ключа типа IntegerField или CharField, вы можете использовать CharField для поля «object_id» вашей модели, поскольку целые числа могут быть приведены к строкам методом get_db_prep_value().
Для максимальной гибкости вы можете использовать TextField, у которого нет определённой максимальной длины, однако это может привести к существенным потерям производительности в зависимости от используемого вами бэкенда базы данных.
Нет универсального решения, какой тип поля лучше всего подходит. Вы должны оценить модели, к которым вы ожидаете указания, и определить, какое решение будет наиболее эффективным для вашего случая.
Сериализация ссылок на объекты ContentType
Если вы сериализуете данные (например, при генерации fixtures) из модели, которая реализует общие отношения, вам, вероятно, следует использовать естественный ключ для уникальной идентификации связанных ContentType объектов. Смотрите естественные ключи и dumpdata --natural-foreign для получения дополнительной информации.
Это позволит использовать API, аналогичный API для обычного ForeignKey; каждый 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_type и object_id сохраняют свои исходные значения, а GenericForeignKey возвращает None:
>>> guido.delete() >>> t.content_object # returns None
Из-за того, как реализован GenericForeignKey, вы не можете использовать такие поля напрямую с фильтрами (filter() и exclude(), например) через API базы данных. Поскольку GenericForeignKey не является обычным объектом поля, эти примеры не будут работать:
# This will fail >>> TaggedItem.objects.filter(content_object=guido) # This will also fail >>> TaggedItem.objects.get(content_object=guido)
Аналогично, GenericForeignKey не отображаются в ModelForm.
Обратные общие отношения
-
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 будут иметь атрибут 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>]>
Также можно использовать add(), create(), или set() для создания отношений:
>>> 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>]>
Вызов remove() будет массово удалять указанные объекты модели:
>>> b.tags.remove(t3) >>> b.tags.all() <QuerySet [<TaggedItem: django>]> >>> TaggedItem.objects.all() <QuerySet [<TaggedItem: django>]>
Метод clear() можно использовать для массового удаления всех связанных объектов для экземпляра:
>>> b.tags.clear() >>> b.tags.all() <QuerySet []> >>> TaggedItem.objects.all() <QuerySet []>
Определение GenericRelation со значением related_query_name позволяет выполнять запросы со связанного объекта:
tags = GenericRelation(TaggedItem, related_query_name="bookmark")
Это позволяет фильтровать, сортировать и выполнять другие операции запроса с Bookmark из TaggedItem:
>>> # Get all tags belonging to bookmarks containing `django` in the url >>> TaggedItem.objects.filter(bookmark__url__contains="django") <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Если вы не добавляете related_query_name, вы можете выполнить те же типы запросов вручную:
>>> 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 также делает это; если модель, имеющая общий внешний ключ, использует нестандартные имена для этих полей, вы должны передать имена полей при настройке 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.
Общие отношения и агрегация
API агрегации базы данных Django работает с GenericRelation. Например, вы можете узнать, сколько тегов у всех закладок:
>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}
Общее отношение в формах
Модуль django.contrib.contenttypes.forms предоставляет:
BaseGenericInlineFormSet- Фабрику формсетов
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иfk_fieldесли они отличаются от значений по умолчанию,content_typeиobject_idсоответственно. Другие параметры аналогичны описанным вmodelformset_factory()иinlineformset_factory().Аргумент
for_concrete_modelсоответствует аргументуfor_concrete_modelвGenericForeignKey.
Общие отношения в админке
Модуль django.contrib.contenttypes.admin предоставляет GenericTabularInline и GenericStackedInline (подклассы GenericInlineModelAdmin)
Эти классы и функции позволяют использовать общие отношения в формах и админке. Подробнее см. документацию по наборам форм моделей и админке наборы форм моделей и админка.
-
class GenericInlineModelAdmin -
Класс
GenericInlineModelAdminнаследует все свойства от классаInlineModelAdmin. Однако он добавляет несколько своих собственных для работы с общим отношением:-
ct_field -
Имя поля внешнего ключа
ContentTypeв модели. По умолчаниюcontent_type.
-
ct_fk_field -
Имя целочисленного поля, которое представляет идентификатор связанного объекта. По умолчанию
object_id.
-
-
class GenericTabularInline
-
class GenericStackedInline -
Подклассы
GenericInlineModelAdminсо стоечным и табличным макетами соответственно.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.2/ref/contrib/contenttypes/