Система типов содержимого¶
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
class Meta:
indexes = [
models.Index(fields=["content_type", "object_id"]),
]
Обычный ForeignKey может «указывать» только на одну другую модель, что означает, что если модель TaggedItem использует ForeignKey, то ей придется выбрать одну и только одну модель для хранения тегов. Приложение contenttypes предоставляет специальный тип поля (GenericForeignKey), который позволяет обойти эту проблему и установить связь с любой моделью:
-
class
GenericForeignKey¶ Настройка
GenericForeignKeyсостоит из трех частей:- Дайте вашей модели значение от
ForeignKeyдоContentType. Обычное название этого поля - «content_type». - Дайте вашей модели поле, которое может хранить значения первичного ключа из моделей, с которыми вы будете связываться. Для большинства моделей это означает
PositiveIntegerField. Обычное название этого поля - «object_id». - Дайте вашей модели
GenericForeignKey, и передайте ей имена двух полей, описанных выше. Если эти поля имеют имена «content_type» и «object_id», вы можете опустить это - это имена полей по умолчанию, которые будет искатьGenericForeignKey.
В отличие от
ForeignKey, индекс базы данных не автоматически создается дляGenericForeignKey, поэтому рекомендуется использоватьMeta.indexesдля добавления собственного индекса нескольких столбцов. Это поведение may change в будущем.-
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)
Аналогично, GenericForeignKeys не появляется в ModelForms.
Обратные родовые отношения¶
-
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>]>
Для создания отношений можно также использовать add(), create() или set():
>>> t3 = TaggedItem(tag="Web development")
>>> b.tags.add(t3, bulk=False)
>>> b.tags.create(tag="Web framework")
<TaggedItem: Web framework>
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>, <TaggedItem: Web development>, <TaggedItem: Web framework>]>
>>> b.tags.set([t1, t3])
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: Web development>]>
Вызов remove() приведет к массовому удалению указанных объектов модели:
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
Метод clear() может быть использован для массового удаления всех связанных объектов для экземпляра:
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
Определение 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, absolute_max=None, can_delete_extra=True)¶ Возвращает
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со стековой и табличной компоновкой, соответственно.
GenericPrefetch()¶
-
class
GenericPrefetch(lookup, querysets, to_attr=None)¶
Этот поиск аналогичен Prefetch() и должен использоваться только на GenericForeignKey. Аргумент querysets принимает список наборов запросов, каждый из которых предназначен для разных ContentType. Это полезно для GenericForeignKey с неоднородным набором результатов.
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/")
>>> animal = Animal.objects.create(name="lion", weight=100)
>>> TaggedItem.objects.create(tag="great", content_object=bookmark)
>>> TaggedItem.objects.create(tag="awesome", content_object=animal)
>>> prefetch = GenericPrefetch(
... "content_object", [Bookmark.objects.all(), Animal.objects.only("name")]
... )
>>> TaggedItem.objects.prefetch_related(prefetch).all()
<QuerySet [<TaggedItem: Great>, <TaggedItem: Awesome>]>