Фреймворк 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модели.
-
До Django 1.8 свойство name было реальным полем в модели ContentType.
Давайте рассмотрим пример, чтобы увидеть, как это работает. Если у вас уже установлено приложение contenttypes, а затем вы добавите the sites application в настройку INSTALLED_APPS и запустите manage.py migrate, чтобы установить его, модель django.contrib.sites.models.Site будет установлена в вашей базе данных. Вместе с ней будет создан новый экземпляр ContentType со следующими значениями:
-
app_labelбудет установлено в'sites'(последняя часть пути Python «django.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(), например) через 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.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() [<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') [<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) [<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/1.9/ref/contrib/contenttypes/