Фреймворк 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:
- Приложение admin использует его для регистрации истории каждого объекта, добавленного или изменённого через интерфейс администрирования.
- Django
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'(последняя часть пути Pythondjango.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по идентификатору. Поскольку этот метод использует тот же общий кэш, что и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
Обычный 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, поле сможет ссылаться на прокси-модели. По умолчанию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 3.2:Были добавлены аргументы
absolute_maxиcan_delete_extra.
Обобщенные отношения в админке
Модуль 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.0/ref/contrib/contenttypes/