Фреймворк 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
authentication frameworkиспользует его для привязки разрешений пользователей к конкретным моделям.
Модель ContentType
-
class ContentType -
Каждый экземпляр
ContentTypeимеет два поля, которые вместе уникально описывают установленную модель:-
app_label -
Имя приложения, к которому принадлежит модель. Оно взято из атрибута
app_labelмодели и включает только последнюю часть пути импорта приложения;django.contrib.contenttypes, например, становится атрибутомapp_labelcontenttypes.
-
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по 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='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) -
Возвращает формсет, используя
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)
Эти классы и функции позволяют использовать обобщенные отношения в формах и админке. См. документацию по формам множества моделей и админке для получения дополнительной информации.
END_OF_DOCUMENT_MARKER-
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.2/ref/contrib/contenttypes/