Spec-Zone.ru › Django 5.0

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 требуют его:

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

Модель 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' (последняя часть пути импорта 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 в качестве способа привязки экземпляров к конкретным классам моделей и использовать эти методы для доступа к этим классам моделей.
END_OF_DOCUMENT_MARKER

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

Менеджер 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_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

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

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

class GenericForeignKey

Существует три части настройки 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
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()

Новая функция в Django 5.0.
class GenericPrefetch(lookup, querysets=None, 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/5.0/ref/contrib/contenttypes/

Spec-Zone.ru

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