Фреймворк 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_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.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
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): # __unicode__ on Python 2
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>
Из-за способа реализации GenericForeignKey, вы не можете использовать такие поля напрямую с фильтрами (filter() и exclude(), например) через базу данных. Поскольку 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.db import models
from django.contrib.contenttypes.fields import GenericRelation
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 принимает имена полей типа контента и 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
-
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 -
Имя целочисленного поля, представляющего ID связанного объекта. По умолчанию
object_id.
-
-
class GenericTabularInline
-
class GenericStackedInline -
Подклассы
GenericInlineModelAdminсо стоечным и табулярным макетами, соответственно.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/ref/contrib/contenttypes/