Вычисляемые поля

В процессе работы с моделью в админ-панели нам может потребоваться какая-то дополнительная информация, которая будет вычисляться на основе существующих полей в базе данных. И мы можем добавить такую информацию двумя способами:

  1. Добавить метод с вычисляемым значением непосредственно в саму модель.
  2. Добавить метод в администратор модели.

Здесь мы разберём второй способ, но первый выглядит практически идентично, разве что в него не нужно дополнительно передавать объект модели, так как его параметр self и так на него ссылается)

Процедура создания вычисляемого поля:

  1. В администраторе модели добавляем наш метод. Помним о том, что этот метод должен принимать в качестве аргумента объект модели. Через двоеточие можно указать тип объекта, чтобы явно отобразить объект какой модели принимает данный метод.

    Например:

    # admin.py
     
    @admin.register(Category)
    class CategoryAdmin(admin.ModelAdmin):
        ...
     
        **def number_of_women(self, cat: Category):
            return cat.women.count()**
     
  2. Добавляем наш метод в список отображаемых атрибутов:

    @admin.register(Category)
    class CategoryAdmin(admin.ModelAdmin):
        list_display = ('id', 'title', **'number_of_women'**)
     
        def number_of_women(self, cat: Category):
            return cat.women.count()
     

Получаем отображение следующего вида в админ-панели:

250

Здесь важно упомянуть то, что как только мы начинаем работать со связанными полями, предварительно не загружая для них данные (помним про ленивые связи в django), то в нашем случае мы получаем дублирующие запросы к базе данных для каждой категории.

Untitled

Чтобы этого избежать, нам нужно добавить метод get_queryset в наш администратор модели. Мы уже использовали его ранее для создания кастомизированного менеджера модели. Метод get_queryset() возвращает набор объектов, которые будут отображаться в административной панели. Этот метод позволяет настроить QuerySet, который содержит эти объекты, прежде чем они будут отображены.

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'number_of_women')
 
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        # Предварительная загрузка связанных объектов 'women'
        queryset = queryset.prefetch_related('women')
        return queryset
 
    def number_of_women(self, cat: Category):
        return cat.women.count()

Пошагово:

  • Сначала вызывается метод super().get_queryset(request), который возвращает базовый QuerySet для модели. Этот базовый QuerySet содержит все объекты модели.
  • После получения базового QuerySet вы можете изменить его, фильтровать объекты или применять дополнительные условия. В нашем случае мы добавляем предварительную загрузку связанных данных.
  • Наконец, итоговый QuerySet возвращается из метода get_queryset().

Таким простым действием мы решаем проблему дублирующих запросов к базе данных)

И чтобы совсем всё было красивенько, можно ещё поднастроить то, в каком виде будет отображаться наше вычисляемое поле, с помощью декоратора - @admin.display().

Декоратор @admin.display()

Декоратор @admin.display() используется для установки специфических атрибутов на кастомные функции отображения (или говоря иначе наши вычисляемые поля).

Параметры декоратора @admin.display():

  1. boolean (bool): Определяет, будет ли отображаемое поле интерпретироваться как логическое значение. Если True, поле будет отображаться как чекбокс. По умолчанию None.
  2. ordering (str): Определяет порядок сортировки для данного поля при клике на его заголовок. Указывается строка с именем атрибута модели или префиксом - для обратной сортировки. По умолчанию None.
  3. description (str): Определяет человекочитаемое описание поля, которое будет отображаться в административной панели. По умолчанию None.
  4. empty_value (any): Определяет значение, которое будет отображаться, если значение поля пусто или равно None. По умолчанию None.

Добавляем декоратор к нашему методу. С атрибутом description всё хорошо, мы легко можем заменить текст на любой, который нас устраивает. А вот с сортировкой есть некоторые проблемы…

Поскольку поле number_of_women не является атрибутом модели, нельзя использовать его напрямую в качестве атрибута ordering в декораторе @admin.display(). То есть мы остаёмся без сортировки по этому полю, а что делать, если нам она нужна?

В данном случае, как вариант, можно использовать аннотирование на уровне получения query_set. Мы изменяем логику подсчёта женщин в категории и делаем это на шаге формирования набора данных.Так как мы добавляем аннотацию для набора, и это действие предзагружает для нас весь этот набор, мы можем избавиться от вызова метода - prefetch_related)

from django.db.models import Count
 
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'number_of_women')
    list_display_links = ('title',)
    ordering = ('title',)
 
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return **queryset.annotate(_number_of_women=Count('women'))**
 
    @admin.display(**description='Всего женщин в категории', ordering='_number_of_women')**
    def number_of_women(self, cat: Category):
        **return cat._number_of_women**

Здесь конечно может закрасться мысль, а что если вообще убрать тогда метод и передавать в list_displayаннотированное поле напрямую? Нет, так работать не будет) только через метод. Ну и плюс у нас есть возможность добавить кастомное имя.

Итого:

Untitled

Django Admin Actions

Django Admin Actions - это функции, которые можно использовать для выполнения действий над списком выбранных объектов на странице списка изменений. Это может быть полезно, если вам нужно сделать одно и то же изменение для нескольких объектов одновременно.

Создание действий

Чтобы создать действие, необходимо:

  1. Определить функцию, которая будет выполнять действие (внутри администратора модели!).
  2. Добавить её в переменную actions нашего администратора.

Пример:

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'is_published', 'time_update')
    **actions = ('make_pub',)**
    ...
 
    **def make_pub(self, request, queryset):**
        queryset.update(is_published=True)

Параметры:

Функция действия принимает три параметра:

  • self: Экземпляр класса для модели, к которой применяется действие.
  • request: Объект HTTP-запроса.
  • queryset: Объект QuerySet, содержащий выбранные объекты.

Использование действий

Действия доступны в выпадающем списке “Действия” на странице списка изменений.

Декоратор @admin.action()

Декоратор @admin.action() используется для настройки пользовательских действий (actions) в административной панели.

Основные параметры этого декоратора:

  • permissions: определяет разрешения, необходимые для выполнения данного действия. Этот параметр принимает список разрешений, которые пользователь должен иметь для доступа к данному действию. Например, permissions=["publish"].
  • description: определяет описание действия, которое будет отображаться в пользовательском интерфейсе администратора. Описание может быть любой строкой, объясняющей назначение данного действия. Например, description="Mark selected stories as published".

Добавим к нашему действию понятное название.

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'is_published', 'time_update')
    **actions = ('make_pub',)**
    ...
 
		@admin.action(description='Опубликовать')
    **def make_pub(self, request, queryset):**
        queryset.update(is_published=True)

Note

Использование этого декоратора не является обязательным для создания функции действия, но может быть полезно использовать его без аргументов в качестве маркера, чтобы визуально выделить все функции действия.

Добавление информирующего сообщения

Метод message_user() в классе django.contrib.admin.ModelAdmin используется для отправки сообщения пользователю об успешном (или не успешном) выполнении изменения.

Синтаксис:

message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False)

  • request: Объект запроса HTTP. Это обычно объект HttpRequest, который передается в методы представлений Django.
  • message: Строка, содержащая текст сообщения, которое вы хотите отправить пользователю.
  • level: Уровень сообщения. Может быть одним из значений из модуля django.contrib.messagesINFOSUCCESSWARNING или ERROR. По умолчанию установлено значение INFO.
  • extra_tags: Дополнительные теги для сообщения, которые могут использоваться для кастомизации стиля или форматирования сообщения. Эти теги добавляются к стандартным тегам, определенным для каждого уровня сообщения.
  • fail_silently: Флаг, определяющий, будет ли возбуждено исключение, если сообщение не удастся отправить. Если установлено значение True, исключение не будет возбуждено, и метод просто вернет None. По умолчанию установлено значение False.

Добавим его в наш метод с публикацией постов:

from django.contrib import admin, messages
 
    @admin.action(description='Опубликовать')
    def make_pub(self, request, queryset):
        count = queryset.update(is_published=True)
        self.message_user(request, f'Успешно опубликовано {count} постов')

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰