Фреймворк 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 использует его для регистрации истории каждого объекта, добавленного или изменённого через интерфейс администрирования.
- Приложение
authentication frameworkDjango использует его для привязки разрешений пользователей к определённым моделям.
Модель 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 >>> ContentType.objects.get(app_label="auth", model="user") <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
Обычный 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.0/ref/contrib/contenttypes/