В Django входит приложение contenttypes, которое позволяет отслеживать все модели вашего Django проекта. Это приложение предоставляет высокоуровневый, обобщенный интерфейс для работы с вашими моделями.
В основе приложения contenttypes лежит модель ContentType, которая находится в django.contrib.contenttypes.models.ContentType. Экземпляр ContentType представляет и хранит информацию о моделях, использующихся в вашем проекте, и новые экземпляры модели ContentType создаются автоматически при добавлении новых моделей в проект.
У экземпляров ContentType есть методы, позволяющие получить класс модели, который они представляют или получить объект для этого класса модели. У модели ContentType имеется также собственный менеджер(custom manager), который предоставляет методы для работы с классом ContentType и для получения экземпляров ContentType для конкретной модели.
Взаимосвязь между ContentType и вашими моделями можно использовать для создания “обобщенных” (“generic” ) отношений между экземпляром вашей модели и экземпляром любой другой модели в проекте.
Фреймворк contenttypes включен по умолчанию и находится в списке INSTALLED_APPS файла настроек, созданного вызовом команды django-admin.py startproject. Если вам необходимо отключить фреймворк или добавить его вручную, просто удалите (или добавьте) в список INSTALLED_APPS приложение 'django.contrib.contenttypes'.
Рекомендуется всегда подключать contenttypes фреймворк в проекте, поскольку его наличие требуется для работы ряда других встроенных приложений Django.
Встроенное приложение администрирования Django использует contenttypes для ведения логов по добавлению или изменению объектов через админку.
Система комментариев Django (django.contrib.comments) использует contenttypes для “добавления” комментариев к моделям.
Каждый экземпляр ContentType содержит три поля, которые вместе уникальным образом описывают каждую модель в приложении.
Первое, это имя приложения в которое входит данная модель. Данные берутся из атрибута app_label модели и включают в себя только последнюю часть пути, который используется для импорта модели. Н-р, в случае “django.contrib.contenttypes” используется значение атрибута app_label для “contenttypes”.
Имя модели класса
“Читабельное” имя модели. Берется из атрибута verbose_name модели.
Покажем на примере как это все работает. Если приложение contenttypes уже установлено, то добавьте приложение sites в INSTALLED_APPS файла настроек и выполните команду manage.py syncdb для создания таблиц и завершения установки модели django.contrib.sites.models.Site. Параллельно с этим будет создан новый экземпляр ContentType со следующими значениями:
Каждый экземпляр ContentType имеет методы, которые позволяют получить доступ к модели, представленной этим экземпляром ContentType , или получить объекты этой модели:
Принимает набор корректных фильтров полей(lookup arguments) для модели, представленной данным ContentType и выполняет метод get() этой модели, возвращая соответствующий объект.
Возвращает класс модели, представленной данным экземпляром 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(), дает нам два крайне важных и полезных варианта их использования:
Воспользовавшись этими методами, вы можете писать высокоуровневый , обобщенный(generic) код, и выполнять запрос к любой установленной в приложении модели. Вместо того, чтобы импортировать конкретные модели “по одиночке”, вы можете передать нужные параметры app_label и model в класс ContentType во время выполнения, и затем работать с полученной моделью класса или получить конкретные объекты этой модели.
Вы можете использовать ContentType чтобы связать экземпляр вашей модели с любыми произвольными классами моделей проекта, и использовать указанные методы для доступа к этим моделям.
Некоторые из встроенных приложений Django используют последний подход. Н-р, в системе полномочий( permissions system), в фреймворке аутентификации Django, в модели Permission используется внешний ключ(foreign key) к ContentType; это позволяет создать обобщенную связь с различными моделями и реализовать концепцию ограничений, такую как “пользователь может добавить запись в блог” или “пользователь может удалить сообщение из новостей”.
Класс ContentType обладает собственным менеджером, ContentTypeManager, который включает в себя следующие методы:
Очищает внутренний кэш, используемый ContentType для отслеживания моделей, для которых были созданы экземпляры ContentType. Вероятно, вам никогда не придется вызывать этот метод напрямую, поскольку Django вызывает его автоматически, при необходимости.
Получить экземпляр ContentType по идентификатору(ID). Поскольку метод использует тот же разделяемый кэш, что и метод get_for_model(), предпочтительней пользоваться именно им, а не привычным запросом ContentType.objects.get(pk=id).
Принимает в качестве аргумента либо класс модели, либо экземпляр модели, и возвращает экземпляр ContentType, представляющего данную модель.
Принимает в качестве аргумента произвольное число классов модели и возвращает словарь с отображением класса модели на экземпляр ContentType, представляющего данную модель.
Возвращает экземпляр ContentType, определенный уникальным образом для переданных аргументов: имя приложения(application label) и имя модели(model name). Главное назначение этого метода, дать возможность ссылаться на объекты ContentType посредством натуральных ключей(natural key) в процессе десериализации.
Метод get_for_model() особенно полезен, когда вам необходимо работать с ContentType, но вы не хотите “заморачиваться” с получением метаданных модели для поиска вручную:
>>> from django.contrib.auth.models import User
>>> user_type = ContentType.objects.get_for_model(User)
>>> user_type
<ContentType: user>
До Django 1.5, методы get_for_model() и get_for_models() всегда возвращали ContentType, связанный с конкретной указанной моделью или моделями. Это означало, что нет возможности получить ContentType прокси-модели(proxy model), используя данные методы. В Django 1.5 вы можете передать флажок с булевым значением – for_concrete_model и for_concrete_models соответственно, чтобы указать желаете ли вы или нет получить экземпляр ContentType для конкретной или прямой модели(direct model).
Вы можете создать в вашей модели внешний ключ на ContentType, что позволит связать вашу модель с любой другой моделью, как это было описано выше на примере Permission. Но можно пойти еще дальше и использовать ContentType для реализации абсолютно обобщенных (иногда говорят “полиморфных”) отношений между моделями.
Вот простой пример: реализуем систему тэгов(ярлычков), которая могла бы выглядеть так
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.tag
Обычное поле ForeignKey может “указывать” только на одну модель, что означает, - если в модели TaggedItem есть поле ForeignKey, его можно “связать” с одной и только одной моделью, для которой и будут сохраняться тэги. Приложение contenttypes предоставляет нам поле специального типа (GenericForeignKey), которое решает обозначенную выше проблему и позволяет создать связь с любой моделью:
Существуют три правила по созданию и настройке GenericForeignKey:
Создайте в вашей модели поле типа ForeignKey, указав в качестве внешней модели ContentType. Обычно такому полю дают имя “content_type”.
Создайте в вашей модели поле, которое будет хранить значения первичных ключей экземпляров модели, с которой вы создаете связь. Для большинства моделей, это поле типа PositiveIntegerField. Обычно такому полю дают имя “object_id”.
Создайте в вашей модели поле типа GenericForeignKey, и передайте ему в качестве аргументов, имена полей созданных ранее. Если эти поля названы “content_type” и “object_id”, вы можете не передавать их, – эти имена используются в GenericForeignKey по умолчанию.
Primary key type compatibility
Поле “object_id” не обязательно должно быть того же типа, что и у первичного ключа в привязанной модели, но должно соблюдаться условие, что значения первичного ключа могут быть приведены к тому же типу, что и у поля “object_id ” методом get_db_prep_value().
Н-р, если вы хотите создать обобщенные отношения с моделями, использующими в качестве первичных ключей типы IntegerField или CharField, вы можете использовать тип CharField для вашего поля “object_id”, поскольку целочисленные значения могут быть корректно приведены к строковым методом get_db_prep_value().
Для максимальной гибкости можно использовать тип TextField, который не накладывает ограничения на длину строки, но такое решение может негативно отразиться на производительности, в зависимости от используемой базы данных.
Не существует универсального решения для выбора наилучшего типа поля. Вы должны изучить модели с которыми собираетесь работать, и на основании этого принять решение о выборе типа для поля, руководствуясь принципами эффективности.
Serializing references to ContentType objects
При сериализации данных модели (н-р, при создании fixtures) , которая имеет обобщенные связи, вам вероятно необходимо будет воспользоваться натуральным ключем, чтобы корректно определить связи с объектами ContentType. Смотрите натуральные ключи(natural keys) и dumpdata --natural для дополнительной информации.
После создания связи мы можем использовать 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)
Если модель, с которой предстоит работать наиболее часто, известна заранее, вы можете добавить “обратную” обобщенную связь между моделями и использовать дополнительные возможности API. Н-р:
class Bookmark(models.Model):
url = models.URLField()
tags = generic.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>]
Также как GenericForeignKey, GenericRelation принимает аргументами имена полей content-type и object-ID . Если модель, имеющая обобщенный внешний ключ не использует имена по-умолчанию для этих полей, а любые другие, – вы должны передать эти имена полей в GenericRelation при его инициализации. Н-р, если бы мы использовали в модели TaggedItem поля с именами content_type_fk и object_primary_key при создании внешнего ключа, то поле GenericRelation следовало бы определить таким образом:
tags = generic.GenericRelation(TaggedItem,
content_type_field='content_type_fk',
object_id_field='object_primary_key')
Ну и конечно, если вы не захотите добавить обратную связь, вы можете получить доступ к объекту и “обходным путем”:
>>> 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>]
Обратите внимание, если в модели с GenericRelation не используются значения по умолчанию для ct_field или fk_field для GenericForeignKey (н-р, в приложении django.contrib.comments используются ct_field="object_pk"), вам необходимо установить content_type_field и/или object_id_field таким образом, чтобы значения ct_field и fk_field в GenericRelation соответствовали значениям для ct_field и fk_field в модели содержащей GenericForeignKey:
comments = generic.GenericRelation(Comment, object_id_field="object_pk")
Также обратите внимание, что в случае удаления объекта, имеющего поле GenericRelation, все объекты у которых GenericForeignKey указывает на этот удаляемый объект, тоже будут удалены. Для примера выше это значит, что если удалить объект Bookmark, то любые TaggedItem, связанные с ним, будут удалены вместе с ним.
В отличии от обычного ForeignKey, тип GenericForeignKey не принимает аргумент on_delete для расширения поведения модели; если это необходимо, вы можете избежать каскадного удаления связанных объектов просто не используя GenericRelation, и указать необходимое поведение с помощью сигнала pre_delete.
API агрегации Django для баз данных не работает с GenericRelation. Н-р, у вас могло бы возникнуть желание попытаться сделать нечто подобное:
Bookmark.objects.aggregate(Count('tags'))
Тем не менее, это не сработает так, как вы ожидаете. Обобщенные связи используют дополнительные фильтры в запросе(queryset), чтобы гарантировать непротиворечивость контентного типа, но метод aggregate() не принимает это в расчет. К настоящему моменту, если вам необходимо выполнить агрегирующие запросы с объектами с обобщенной связью, вы должны выполнить это другими методами, не используя возможности API для агрегации.
Модуль django.contrib.contenttypes.generic предлагает нам следующее:
Классы GenericTabularInline и GenericStackedInline (подклассы GenericInlineModelAdmin)
Набор форм, generic_inlineformset_factory(), для использования с GenericForeignKey
Эти классы и функции позволяют использовать обобщенные отношения объектов при создании форм и в админке Django. За дополнительной информацией обратитесь к модель набора форм и admin .
Класс GenericInlineModelAdmin наследует все свойства класса InlineModelAdmin. Но также имеет ряд собственных атрибутов для работы с обобщенными связями:
Имя поля внешнего ключа ContentType модели. По умолчанию content_type.
Имя целочисленного поля, которое хранит идентификатор конкретного объекта связанной модели. По умолчанию object_id.
Подклассы GenericInlineModelAdmin позволяющие настраивать отображение данных в сложенном(stacked) или табличном(tabular) виде, соответственно.
Возвращает GenericInlineFormSet, используя modelformset_factory().
Вы должны предоставить имена для ct_field и object_id если они отличаются от значений по умолчанию, - content_type и object_id. Прочие параметры аналогичны тем, что описаны в modelformset_factory() и inlineformset_factory().
Mar 30, 2016