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[source] -
Каждый экземпляр
ContentTypeимеет два поля, которые вместе однозначно описывают установленную модель:-
app_label -
Имя приложения, к которому принадлежит модель. Оно взято из атрибута
app_labelмодели и включает только последнюю часть пути импорта приложения;django.contrib.contenttypes, например, становитсяapp_labelдляcontenttypes.
-
model -
Имя класса модели.
Кроме того, доступно следующее свойство:
-
name[source] -
Человекопонятное имя типа содержимого. Оно взято из атрибута
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(using=None, **kwargs)[source] -
Принимает набор допустимых условий поиска для модели, которую представляет
ContentType, и выполняетa get() lookupдля этой модели, возвращая соответствующий объект. Аргументusingможет использоваться для указания базы данных, отличной от стандартной.Изменено в Django 5.1:Аргумент
usingбыл добавлен.
-
ContentType.model_class()[source] -
Возвращает класс модели, представленный данным экземпляром
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[source] -
ContentTypeтакже имеет пользовательский менеджер,ContentTypeManager, который добавляет следующие методы:-
clear_cache()[source] -
Очищает внутренний кэш, используемый
ContentTypeдля отслеживания моделей, для которых были созданы экземплярыContentType. Вам, вероятно, никогда не придётся вызывать этот метод самостоятельно; Django вызовет его автоматически, когда это потребуется.
-
get_for_id(id)[source] -
Поиск
ContentTypeпо идентификатору. Поскольку этот метод использует тот же общий кэш, что иget_for_model(), предпочтительнее использовать этот метод вместо обычногоContentType.objects.get(pk=id)
-
get_for_model(model, for_concrete_model=True)[source] -
Принимает класс модели или экземпляр модели и возвращает экземпляр
ContentType, представляющий эту модель.for_concrete_model=Falseпозволяет извлечьContentTypeпрокси-модели.
-
get_for_models(*models, for_concrete_models=True)[source] -
Принимает переменное число классов моделей и возвращает словарь, сопоставляющий классы моделей с экземплярами
ContentType, которые их представляют.for_concrete_models=Falseпозволяет извлекатьContentTypeпрокси-моделей.
-
get_by_natural_key(app_label, model)[source] -
Возвращает экземпляр
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[source] -
Для настройки
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[source] -
-
Связь с обратным объектом по умолчанию не существует. Установка
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 принимает имена полей content-type и object-ID в качестве аргументов, 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[source]
-
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)[source] -
Возвращает
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[source] -
Класс
GenericInlineModelAdminнаследует все свойства от классаInlineModelAdmin. Однако он добавляет несколько собственных свойств для работы с обобщёнными связями:-
ct_field -
Имя поля внешнего ключа
ContentTypeв модели. По умолчаниюcontent_type.
-
ct_fk_field -
Имя целочисленного поля, представляющего идентификатор связанного объекта. По умолчанию
object_id.
-
-
class GenericTabularInline[source]
-
class GenericStackedInline[source] -
Подклассы
GenericInlineModelAdminс выводами «таблица» и «стек», соответственно.
GenericPrefetch()
-
class GenericPrefetch(lookup, querysets, to_attr=None)[source]
Этот поиск похож на Prefetch() и должен использоваться только с GenericForeignKey. Аргумент querysets принимает список наборов запросов, каждый для разных ContentType. Это полезно для GenericForeignKey с неоднородным набором результатов.
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/")
>>> animal = Animal.objects.create(name="lion", weight=100)
>>> TaggedItem.objects.create(tag="great", content_object=bookmark)
>>> TaggedItem.objects.create(tag="awesome", content_object=animal)
>>> prefetch = GenericPrefetch(
... "content_object", [Bookmark.objects.all(), Animal.objects.only("name")]
... )
>>> TaggedItem.objects.prefetch_related(prefetch).all()
<QuerySet [<TaggedItem: Great>, <TaggedItem: Awesome>]>
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/ref/contrib/contenttypes/