• 3.1
  • 5.0
  • 6.1
  • Версия документации: 3.2

Фреймворк контентных типов

В Django входит приложение contenttypes, которое позволяет отслеживать все модели вашего Django проекта. Это приложение предоставляет высокоуровневый, обобщенный интерфейс для работы с вашими моделями.

Обзор

В основе приложения contenttypes лежит модель ContentType, которая находится в django.contrib.contenttypes.models.ContentType. Экземпляр ContentType представляет и хранит информацию о моделях, использующихся в вашем проекте, и новые экземпляры модели ContentType создаются автоматически при добавлении новых моделей в проект.

У экземпляров ContentType есть методы, позволяющие получить класс модели, который они представляют, или запросить объекты этой модели. У модели ContentType имеется также собственный менеджер, который предоставляет методы для работы с классом ContentType и для получения экземпляров ContentType для конкретной модели.

Взаимосвязь между ContentType и вашими моделями можно использовать для создания «обобщенных» («generic» ) отношений между экземпляром вашей модели и экземпляром любой другой модели в проекте.

Установка и подключение contenttypes

Фреймворк contenttypes включен по умолчанию и находится в списке INSTALLED_APPS файла настроек, созданного вызовом команды django-admin startproject. Если вам необходимо отключить фреймворк или добавить его вручную, просто удалите (или добавьте) в список INSTALLED_APPS приложение 'django.contrib.contenttypes'.

Рекомендуется всегда подключать фреймворк контентных типов в проект, поскольку его наличие требуется для работы ряда других встроенных приложений Django:

  • Встроенное приложение администрирования Django использует его для журналирования истории добавления или изменения объектов через админку.

  • Django’s authentication framework uses it to tie user permissions to specific models.

Модель ContentType

class ContentType

Каждый экземпляр ContentType содержит два поля, которые вместе уникальным образом описывают каждую модель в приложении:

app_label

Имя приложения в которое входит данная модель. Данные берутся из атрибута app_label модели и включают в себя только последнюю часть пути, который используется для импорта модели. Н-р, в случае «django.contrib.contenttypes» используется значение атрибута app_label для «contenttypes».

model

Имя модели класса.

Также доступны следующие свойства:

name

«Читабельное» имя модели. Берется из атрибута verbose_name модели.

Покажем на примере как это все работает. Если приложение contenttypes уже установлено, то добавьте приложение sites в INSTALLED_APPS файла настроек и выполните команду manage.py migrate для создания таблиц и завершения установки модели django.contrib.sites.models.Site. Параллельно с этим будет создан новый экземпляр ContentType со следующими значениями:

  • Атрибут app_label со значением 'sites' (последняя часть django.contrib.sites).

  • Атрибут model со значением 'site'.

Методы экземпляра ContentType

Каждый экземпляр ContentType имеет методы, которые позволяют получить доступ к модели, представленной этим экземпляром ContentType , или получить объекты этой модели:

ContentType.get_object_for_this_type(**kwargs)

Takes a set of valid lookup arguments for the model the ContentType represents, and does a get() lookup on that model, returning the corresponding object.

ContentType.model_class()

Возвращает класс модели, представленной данным экземпляром ContentType.

For example, we could look up the ContentType for the User model:

>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label='auth', model='user')
>>> user_type
<ContentType: user>

And then use it to query for a particular User, or to get access to the User model class:

>>> 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(), дает нам два крайне важных и полезных варианта их использования:

  1. Воспользовавшись этими методами, вы можете писать высокоуровневый , обобщенный(generic) код, и выполнять запрос к любой установленной в приложении модели. Вместо того, чтобы импортировать конкретные модели «по одиночке», вы можете передать нужные параметры app_label и model в класс ContentType во время выполнения, и затем работать с полученной моделью класса или получить конкретные объекты этой модели.

  2. Вы можете связывать другую модель с ContentType для привязки экземпляров этой модели с любыми произвольными классами моделей проекта, и использовать указанные методы для доступа к этим моделям.

Некоторые из встроенных приложений Django используют последний подход. Н-р, в системе полномочий( permissions system), в фреймворке аутентификации Django, в модели Permission используется внешний ключ(foreign key) к ContentType; это позволяет создать обобщенную связь с различными моделями и реализовать концепцию ограничений, такую как «пользователь может добавить запись в блог» или «пользователь может удалить сообщение из новостей».

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_model=False позволяет получить ContentType для прокси-модели.

get_by_natural_key(app_label, model)

Возвращает экземпляр ContentType, однозначно определенный переданной меткой приложения и именем модели. Главной задачей этого метода является предоставление возможности ссылаться на объекты ContentType посредством натуральных ключей в процессе десериализации.

The get_for_model() method is especially useful when you know you need to work with a ContentType but don’t want to go to the trouble of obtaining the model’s metadata to perform a manual lookup:

>>> from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)
<ContentType: user>

Обобщенные связи(generic relations)

Добавление внешнего ключа в одну из ваших меделей на 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:

  1. Создайте в вашей модели поле типа ForeignKey, указав в качестве внешней модели ContentType. Обычно такому полю дают имя «content_type».

  2. Создайте в вашей модели поле, которое будет хранить значения первичных ключей экземпляров модели, с которой вы создаете связь. Для большинства моделей, это поле типа PositiveIntegerField. Обычно такому полю дают имя «object_id».

  3. Создайте в вашей модели поле типа GenericForeignKey, и передайте ему в качестве аргументов, имена полей созданных ранее. Если эти поля названы «content_type» и «object_id», вы можете не передавать их, – эти имена используются в GenericForeignKey по умолчанию.

for_concrete_model

При False, поле может ссылаться на прокси-модель. Отображает аргумент for_concrete_model метода get_for_model(). По умолчанию равно True.

Тип первичного ключа

Поле «object_id» не обязательно должно быть того же типа, что и у первичного ключа в привязанной модели, но должно соблюдаться условие, что значения первичного ключа могут быть приведены к тому же типу, что и у поля «object_id « методом get_db_prep_value().

Н-р, если вы хотите создать обобщенные отношения с моделями, использующими в качестве первичных ключей типы IntegerField или CharField, вы можете использовать тип CharField для вашего поля «object_id», поскольку целочисленные значения могут быть корректно приведены к строковым методом get_db_prep_value().

Для максимальной гибкости можно использовать тип TextField, который не накладывает ограничения на длину строки, но такое решение может негативно отразиться на производительности, в зависимости от используемой базы данных.

Не существует универсального решения для выбора наилучшего типа поля. Вы должны изучить модели с которыми собираетесь работать, и на основании этого принять решение о выборе типа для поля, руководствуясь принципами эффективности.

Сериализация связей с ContentType

При сериализации данных модели (например, при создании fixtures) , которая имеет обобщенные связи, вам вероятно необходимо будет воспользоваться натуральным ключом, чтобы корректно определить связи с объектами ContentType. Смотрите натуральные ключи и dumpdata --natural-foreign для дополнительной информации.

This will enable an API similar to the one used for a normal ForeignKey; each TaggedItem will have a content_object field that returns the object it’s related to, and you can also assign to that field or use it when creating a 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>

If the related object is deleted, the content_type and object_id fields remain set to their original values and the GenericForeignKey returns None:

>>> guido.delete()
>>> t.content_object  # returns None

Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because a GenericForeignKey isn’t a normal field object, these examples will not work:

# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)

Также GenericForeignKeys не отображется в ModelForms.

Обратная обобщенная связь(reverse generic relations)

class GenericRelation
related_query_name

По умолчанию обратная связь не создается. Чтобы создать такую связь, укажите параметр 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 instances will each have a tags attribute, which can be used to retrieve their associated 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>]>

You can also use add(), create(), or set() to create relationships:

>>> 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>]>

The remove() call will bulk delete the specified model objects:

>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>

The clear() method can be used to bulk delete all related objects for an instance:

>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>

Создав GenericRelation с related_query_name, можно использовать связь в запросах:

tags = GenericRelation(TaggedItem, related_query_name='bookmark')

This enables filtering, ordering, and other query operations on Bookmark from TaggedItem:

>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmark__url__contains='django')
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>

If you don’t add the related_query_name, you can do the same types of lookups manually:

>>> 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 принимает аргументами имена полей content-type и object-ID . Если модель, имеющая обобщенный внешний ключ не использует имена по-умолчанию для этих полей, а любые другие, – вы должны передать эти имена полей в 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 works with a GenericRelation. For example, you can find out how many tags all the bookmarks have:

>>> Bookmark.objects.aggregate(Count('tags'))
{'tags__count': 3}

Обобщенные связи в формах

Модуль django.contrib.contenttypes.forms предлагает нам следующее:

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 и object_id если они отличаются от значений по умолчанию, - content_type и object_id. Прочие параметры аналогичны тем, что описаны в modelformset_factory() и inlineformset_factory().

Аргумент for_concrete_model соответствует параметру for_concrete_model для GenericForeignKey.

Changed in Django 3.2:

The absolute_max and can_delete_extra arguments were added.

Обобщенные связи в админке

Модуль django.contrib.contenttypes.admin предоставляет GenericTabularInline и GenericStackedInline (дочерние классы GenericInlineModelAdmin)

Эти классы и функции позволяют использовать обобщенные отношения объектов при создании форм и в админке Django. За дополнительной информацией обратитесь к модель набора форм и admin .

class GenericInlineModelAdmin

Класс GenericInlineModelAdmin наследует все свойства класса InlineModelAdmin. Но также имеет ряд собственных атрибутов для работы с обобщенными связями:

ct_field

Имя поля внешнего ключа ContentType модели. По умолчанию content_type.

ct_fk_field

Имя целочисленного поля, которое хранит идентификатор конкретного объекта связанной модели. По умолчанию object_id.

class GenericTabularInline
class GenericStackedInline

Подклассы GenericInlineModelAdmin позволяющие настраивать отображение данных в сложенном(stacked) или табличном(tabular) виде, соответственно.

Back to Top