The contenttypes framework
Django includes a contenttypes application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.
Обзор
В основе приложения 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'(последняя часть пути 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
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.1/ref/contrib/contenttypes/