Система типов содержимого¶
Django включает приложение contenttypes
, которое может отслеживать все модели, установленные в вашем проекте на базе Django, предоставляя высокоуровневый, общий интерфейс для работы с вашими моделями.
Обзор¶
В основе приложения contenttypes лежит модель ContentType
, которая живет по адресу django.contrib.contenttypes.models.ContentType
. Экземпляры ContentType
представляют и хранят информацию о моделях, установленных в вашем проекте, а новые экземпляры ContentType
автоматически создаются всякий раз, когда устанавливаются новые модели.
Экземпляры ContentType
имеют методы для возврата классов моделей, которые они представляют, и для запроса объектов из этих моделей. ContentType
также имеет custom manager, который добавляет методы для работы с 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
модели и включает только последнюю часть пути импорта Python приложения;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
со следующими значениями:
Методы на экземплярах ContentType
¶
Каждый экземпляр ContentType
имеет методы, которые позволяют вам перейти от экземпляра ContentType
к модели, которую он представляет, или получить объекты из этой модели:
-
ContentType.
get_object_for_this_type
(**kwargs)¶ Принимает набор допустимых lookup arguments для модели, которую представляет
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
ссылаться через natural key во время десериализации.
-
Метод 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
объектов. Более подробную информацию смотрите в natural keys и dumpdata --natural-foreign
.
Это позволит использовать 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
s не появляется в ModelForm
s.
Обратные родовые отношения¶
-
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
принимает в качестве аргументов имена полей content-type и object-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
.
Общие отношения и агрегация¶
Django’s database aggregation API работает с 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
).
Эти классы и функции позволяют использовать общие отношения в формах и админке. Более подробную информацию смотрите в документации model formset и admin.
-
class
GenericInlineModelAdmin
¶ Класс
GenericInlineModelAdmin
наследует все свойства от классаInlineModelAdmin
. Однако он добавляет несколько собственных для работы с общим отношением:-
ct_field
¶ Имя поля внешнего ключа
ContentType
в модели. По умолчанию имеет значениеcontent_type
.
-
ct_fk_field
¶ Имя целочисленного поля, представляющего идентификатор связанного объекта. По умолчанию имеет значение
object_id
.
-
-
class
GenericTabularInline
¶
-
class
GenericStackedInline
¶ Подклассы
GenericInlineModelAdmin
со стековой и табличной компоновкой, соответственно.