Вычисляемые поля
В процессе работы с моделью в админ-панели нам может потребоваться какая-то дополнительная информация, которая будет вычисляться на основе существующих полей в базе данных. И мы можем добавить такую информацию двумя способами:
- Добавить метод с вычисляемым значением непосредственно в саму модель.
- Добавить метод в администратор модели.
Здесь мы разберём второй способ, но первый выглядит практически идентично, разве что в него не нужно дополнительно передавать объект модели, так как его параметр self и так на него ссылается)
Процедура создания вычисляемого поля:
-
В администраторе модели добавляем наш метод. Помним о том, что этот метод должен принимать в качестве аргумента объект модели. Через двоеточие можно указать тип объекта, чтобы явно отобразить объект какой модели принимает данный метод.
Например:
# admin.py @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): ... **def number_of_women(self, cat: Category): return cat.women.count()**
-
Добавляем наш метод в список отображаемых атрибутов:
@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()
Получаем отображение следующего вида в админ-панели:
Здесь важно упомянуть то, что как только мы начинаем работать со связанными полями, предварительно не загружая для них данные (помним про ленивые связи в django), то в нашем случае мы получаем дублирующие запросы к базе данных для каждой категории.
Чтобы этого избежать, нам нужно добавить метод 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()
:
boolean
(bool): Определяет, будет ли отображаемое поле интерпретироваться как логическое значение. ЕслиTrue
, поле будет отображаться как чекбокс. По умолчаниюNone
.ordering
(str): Определяет порядок сортировки для данного поля при клике на его заголовок. Указывается строка с именем атрибута модели или префиксом-
для обратной сортировки. По умолчаниюNone
.description
(str): Определяет человекочитаемое описание поля, которое будет отображаться в административной панели. По умолчаниюNone
.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
аннотированное поле напрямую? Нет, так работать не будет) только через метод. Ну и плюс у нас есть возможность добавить кастомное имя.
Итого:
Django Admin Actions
Django Admin Actions - это функции, которые можно использовать для выполнения действий над списком выбранных объектов на странице списка изменений. Это может быть полезно, если вам нужно сделать одно и то же изменение для нескольких объектов одновременно.
Создание действий
Чтобы создать действие, необходимо:
- Определить функцию, которая будет выполнять действие (внутри администратора модели!).
- Добавить её в переменную 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.messages
:INFO
,SUCCESS
,WARNING
или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} постов')
〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰