Spec-Zone.ru › Django 5.1

The contenttypes framework

Django includes a contenttypes application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.

Обзор

В основе приложения 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’s authentication framework использует его для привязки разрешений пользователей к определённым моделям.

Модель ContentType

class ContentType [source]

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

app_label

Имя приложения, к которому принадлежит модель. Оно взято из атрибута app_label модели и включает только последнюю часть пути импорта приложения; django.contrib.contenttypes, например, становится app_label для contenttypes.

model

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

Кроме того, доступно следующее свойство:

name [source]

Человекопонятное имя типа содержимого. Оно взято из атрибута 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) [source]

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

Изменено в Django 5.1:

Аргумент using был добавлен.

ContentType.model_class() [source]

Возвращает класс модели, представленный данным экземпляром 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 [source]

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

clear_cache() [source]

Очищает внутренний кэш, используемый ContentType для отслеживания моделей, для которых были созданы экземпляры ContentType. Вам, вероятно, никогда не придётся вызывать этот метод самостоятельно; Django вызовет его автоматически, когда это потребуется.

get_for_id(id) [source]

Поиск ContentType по идентификатору. Поскольку этот метод использует тот же общий кэш, что и get_for_model(), предпочтительнее использовать этот метод вместо обычного ContentType.objects.get(pk=id)

get_for_model(model, for_concrete_model=True) [source]

Принимает класс модели или экземпляр модели и возвращает экземпляр ContentType, представляющий эту модель. for_concrete_model=False позволяет извлечь ContentType прокси-модели.

get_for_models(*models, for_concrete_models=True) [source]

Принимает переменное число классов моделей и возвращает словарь, сопоставляющий классы моделей с экземплярами ContentType, которые их представляют. for_concrete_models=False позволяет извлекать ContentType прокси-моделей.

get_by_natural_key(app_label, model) [source]

Возвращает экземпляр 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

    class Meta:
        indexes = [
            models.Index(fields=["content_type", "object_id"]),
        ]

Обычный ForeignKey может только «указывать» на другую модель, что означает, что если модель TaggedItem использовала ForeignKey, ей пришлось бы выбрать одну и только одну модель для хранения тегов. Приложение contenttypes предоставляет специальный тип поля (GenericForeignKey) который обходит это и позволяет установить отношение с любой моделью:

class GenericForeignKey [source]

Для настройки GenericForeignKey требуется три шага:

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

Обобщённые связи и агрегация

API Django для агрегации баз данных работает с GenericRelation. Например, вы можете узнать, сколько тегов имеют все закладки:

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

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

Модуль django.contrib.contenttypes.forms предоставляет:

  • BaseGenericInlineFormSet
  • Фабрику наборов форм generic_inlineformset_factory() для использования с GenericForeignKey.
class BaseGenericInlineFormSet [source]
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) [source]

Возвращает 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 [source]

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

ct_field

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

ct_fk_field

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

class GenericTabularInline [source]
class GenericStackedInline [source]

Подклассы GenericInlineModelAdmin с выводами «таблица» и «стек», соответственно.

GenericPrefetch()

Новое в Django 5.0.
class GenericPrefetch(lookup, querysets, to_attr=None) [source]

Этот поиск похож на 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/5.1/ref/contrib/contenttypes/

Spec-Zone.ru

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