Spec-Zone.ru › Django 5.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 требуют его:

  • Приложение admin использует его для регистрации истории каждого объекта, добавленного или изменённого через интерфейс admin.
  • Фреймворк аутентификации Django 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 по ID. Поскольку этот метод использует тот же общий кэш, что и 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.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 [source]

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

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

Spec-Zone.ru

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