Spec-Zone.ru › Django 3.2

Фреймворк 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 модели.

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

  1. Используя эти методы, вы можете написать высокоуровневый универсальный код, который выполняет запросы к любой установленной модели — вместо импорта и использования одного конкретного класса модели, вы можете передавать app_label и model в запрос к ContentType во время выполнения, а затем работать с классом модели или извлекать объекты из него.
  2. Вы можете связать другую модель с 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 ссылаться с помощью естественного ключа во время десериализации.

Метод 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:

  1. Добавьте в вашу модель ForeignKey к ContentType. Обычно это поле называется «content_type».
  2. Добавьте в вашу модель поле, которое может хранить значения первичных ключей из моделей, к которым вы хотите установить связь. Для большинства моделей это PositiveIntegerField. Обычно это поле называется «object_id».
  3. Добавьте 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>

Если связанный объект удален, поля 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 не отображаются в ModelForm.

Обратные обобщенные связи

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 будут иметь атрибут 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 принимает имена полей типа содержимого и идентификатора объекта в качестве аргументов, так и 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, 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 3.2:

Были добавлены аргументы absolute_max и can_delete_extra.

Обобщенные связи в админ-панели

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

END_OF_DOCUMENT_MARKER

Эти классы и функции позволяют использовать общие отношения в формах и админке. Дополнительную информацию см. в документации по формам модели и админке.

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/3.2/ref/contrib/contenttypes/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API