Spec-Zone.ru › Django 6.0

Фреймворк 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, входящим в комплект поставки:

  • Приложение администрирования использует его для ведения истории каждого объекта, добавленного или изменённого через интерфейс администрирования.
  • authentication framework Django использует его для привязки разрешений пользователей к определённым моделям.

Модель 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 со следующими значениями:

  • app_label получит значение 'sites' (последнюю часть пути Python django.contrib.sites).
  • model получит значение 'site'.

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

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

ContentType.get_object_for_this_type(using=None, **kwargs) [исходный код]

Принимает набор допустимых аргументов поиска для модели, представляемой объектом ContentType, и выполняет для этой модели a get() lookup, возвращая соответствующий объект. Аргумент using можно использовать, чтобы указать базу данных, отличную от базы данных по умолчанию.

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.PositiveBigIntegerField()
    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 состоит из трёх частей:

  1. Добавьте в модель ForeignKey на ContentType. Обычно это поле называется «content_type».
  2. Добавьте в модель поле, способное хранить значения первичных ключей моделей, с которыми будет устанавливаться связь. Для большинства моделей это означает использование PositiveBigIntegerField. Обычно это поле называется «object_id».
  3. Добавьте в модель GenericForeignKey и передайте ему имена двух описанных выше полей. Если эти поля называются «content_type» и «object_id», этот параметр можно опустить — это имена полей по умолчанию, которые будет искать GenericForeignKey.

В отличие от ForeignKey, для GenericForeignKey база данных не создаёт индекс автоматически, поэтому рекомендуется использовать Meta.indexes, чтобы добавить собственный индекс по нескольким столбцам. В будущем это поведение может измениться.

for_concrete_model

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

Совместимость типов первичных ключей

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

Например, если вы хотите разрешить универсальные связи с моделями, у которых поля первичных ключей имеют тип IntegerField или CharField, для поля «object_id» в модели можно использовать CharField, поскольку метод 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.contrib.contenttypes.admin предоставляет GenericTabularInline и GenericStackedInline (подклассы GenericInlineModelAdmin).

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

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/ref/contrib/contenttypes/

Spec-Zone.ru

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