Фреймворк 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'(последняя часть пути 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>]>
Определение GenericRelation с установленным related_query_name позволяет выполнять запросы из связанного объекта:
tags = GenericRelation(TaggedItem, related_query_name='bookmarks')
Это позволяет фильтровать, сортировать и выполнять другие операции запросов на Bookmark из TaggedItem:
>>> # Get all tags belonging to bookmarks containing `django` in the url >>> TaggedItem.objects.filter(bookmarks__url__contains='django') <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
Конечно, если вы не добавите обратную связь, вы можете выполнить те же типы запросов вручную:
>>> b = Bookmark.objects.get(url='https://www.djangoproject.com/') >>> bookmark_type = ContentType.objects.get_for_model(b) >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id=b.id) <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) -
Возвращает набор форм
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/2.1/ref/contrib/contenttypes/