Django предоставляет мощную стандартную админ-панель, которую можно заметно улучшить под задачи конкретного проекта. В предыдущих уроках мы научились подключать модели к админке, настраивать список отображаемых колонок и фильтров, редактировать поля, управлять заголовками и поиском.
В этом уроке мы пойдём дальше и разберём две ключевые возможности:
- Пользовательские (виртуальные) поля, которые не хранятся в базе данных, а вычисляются «на лету».
- Пользовательские действия (actions) — команды, которые можно применить сразу к нескольким выбранным записям.
Обе темы критически важны для реальных проектов: админ-панель — это инструмент разработчика и контент-менеджера, и чем удобнее она будет работать, тем меньше ошибок появится в данных.
Для примеров мы будем использовать упрощённые модели проекта cinemahub. Представим, что у нас есть модель Movie, содержащая:
- название фильма,
- описание,
- год выхода,
- статус публикации.
Каждая строка в админ-панели представляет запись в базе данных. Но иногда данные, которые мы хотим отображать, не должны храниться в таблице. Например:
- «Сколько символов в описании»
- «Сколько актёров привязано»
- «Возраст фильма»
- «Форматированная дата»
- «Превью текста»
Это и есть виртуальные поля — методы, которые Django может подставить в list_display так же, как обычные.
Откроем файл cinemahub/admin.py и немного настроим модель Movie.
Предположим, мы хотим показать в списке фильмов длину описания:
from django.contrib import admin
from .models import Movie
class MovieAdmin(admin.ModelAdmin):
def description_length(self, movie: Movie):
return f"{len(movie.description)} символов"Что здесь происходит:
- В метод передаётся конкретный объект Movie.
- Мы рассчитываем длину описания.
- Возвращаем строку — ровно то, что будет отображаться в колонке.
class MovieAdmin(admin.ModelAdmin):
list_display = ('title', 'year', 'is_published', 'description_length')
def description_length(self, movie: Movie):
return f"{len(movie.description)} символов"Теперь:
- Сохраняем файл.
- Переходим в браузер → /admin/.
- Открываем список фильмов.
Появилась новая колонка! Но Django назовёт её автоматически вроде «Description length». Это не слишком красиво.
Используем декоратор @admin.display:
@admin.display(description="Длина описания")
def description_length(self, movie: Movie):
return f"{len(movie.description)} символов"Проверяем в браузере — колонка переименована.
Мы показываем длину описания. Логично, что администратор мог бы захотеть увидеть:
- фильмы с самым длинным описанием,
- или наоборот — самые короткие.
Но сортировать по полю, которого нет в базе, Django не умеет. Чтобы сортировка всё же работала, нужно указать, по какому реальному полю она будет опираться.
В нашем случае это поле description.
@admin.display(description="Длина описания", ordering='description')
def description_length(self, movie: Movie):
return f"{len(movie.description)} символов"Теперь рядом с колонкой появится значок сортировки.
Django позволяет выделить несколько записей и применить к ним действие из выпадающего списка.
Например:
- отметить как опубликованные,
- снять с публикации,
- сменить категорию,
- проставить рейтинг,
- отправить уведомления (редко, но возможно).
Это экономит огромное количество времени и снижает риск ошибок.
В модели Movie предположим есть поле is_published = models.BooleanField(default=False).
Добавим простое действие:
class MovieAdmin(admin.ModelAdmin):
@admin.action(description="Опубликовать выбранные фильмы")
def set_published(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f"Опубликовано: {count} фильмов.")И регистрируем действие:
actions = ['set_published']queryset— это набор выбранных в админке записей.update()обновляет их сразу в базе одним запросом.self.message_user— это удобный методModelAdmin, который используетdjango.contrib.messagesпод капотом.
Проверка:
- Заходим в админку.
- Выбираем несколько фильмов чекбоксами.
- В выпадающем списке действий выбираем «Опубликовать выбранные фильмы».
- Нажимаем Выполнить.
Вверху появится сообщение вида:
Опубликовано: 3 фильмов.
from django.contrib import messages
class MovieAdmin(admin.ModelAdmin):
@admin.action(description="Снять с публикации")
def set_draft(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(
request,
f"{count} фильмов сняты с публикации.",
messages.WARNING
)
actions = ['set_published', 'set_draft']Параметр level (или второй позиционный аргумент) определяет уровень сообщения — в нашем случае messages.WARNING.
Теперь в админке есть два действия:
- «Опубликовать выбранные фильмы»
- «Снять с публикации»
messages.WARNING — это уровень (тип) уведомления в Django Messages Framework. Он обозначает предупреждающее сообщение и используется всякий раз, когда приложение или админ хочет показать админу/пользователю предупреждение (например, «операция выполнена, но с оговорками»). В примере админ-действия message_user(..., messages.WARNING) сообщает администратору об успешном выполнении, но в виде предупреждения (желтая/оранжевая метка).
Django имеет встроенный Messages Framework (django.contrib.messages). В нём определены уровни сообщений:
messages.DEBUGmessages.INFOmessages.SUCCESSmessages.WARNINGmessages.ERROR
messages.WARNING — это константа, которая представляет уровень предупреждения (обычно визуально оформляется как жёлтое/оранжевое уведомление).
Такие сообщения используются повсеместно:
- В view-функциях и class-based views для информирования пользователя о результате операции (успех/ошибка/предупреждение).
- В админ-панели — через
self.message_user(request, msg, level)внутри классовModelAdmin/AdminSite. Админ использует сообщения для вывода краткой обратной связи после действий. - В шаблонах (через
get_messagesили тег{% if messages %}) — для показа сообщений пользователю. - В коде middleware, сигналов, management-команд — везде, где нужно кратко оповестить о событии.
Причина: метод был определён ниже, но написан без self или с неправильным названием.
Правильно:
def description_length(self, movie):Неправильно:
def description_length(movie):Причина: вы указали искусственное поле в ordering.
Ошибка:
ordering = ['description_length']Правильно:
@admin.display(ordering='description')Причина: забыли добавить метод в список actions.
- Добавьте в админку поле «Возраст фильма». Возраст фильма рассчитывается по формуле:
текущий_год - год_выхода
- Создайте действие «Сделать год выхода 2000». Действие должно массово изменить год выхода на 2000.
- Добавьте колонку с обрезанным описанием (первые 30 символов). Например: "Очень интересный фи..."
- Добавьте в админку поле
from datetime import datetime
class MovieAdmin(admin.ModelAdmin):
@admin.display(description="Возраст фильма")
def movie_age(self, movie):
current_year = datetime.now().year
return f"{current_year - movie.year} лет"
list_display = ('title', 'year', 'movie_age', 'is_published')- Создайте действие
@admin.action(description="Установить год выхода — 2000")
def set_year_2000(self, request, queryset):
count = queryset.update(year=2000)
self.message_user(request, f"Год изменён у {count} фильмов.")
class MovieAdmin(admin.ModelAdmin):
actions = ['set_year_2000']- Добавьте колонку
class MovieAdmin(admin.ModelAdmin):
@admin.display(description="Краткое описание")
def short_description(self, movie):
return movie.description[:30] + "..."
list_display = ('title', 'short_description', 'is_published')- Что такое виртуальное поле и зачем оно может понадобиться в админ-панели?
- Можно ли сортировать таблицу по виртуальному полю? Если да — то как?
- В какой момент Django передаёт объект модели в метод виртуального поля?
- Что такое
actionsи для чего они используются? - Чем отличается
update()от изменения каждого объекта вручную? - Что делает метод
message_user()? - Как изменить название пользовательского действия?
- Где применяется
messages.WARNING? - Что произойдёт, если забыть добавить действие в список
actions?