Skip to content

Latest commit

 

History

History
329 lines (213 loc) · 14.2 KB

File metadata and controls

329 lines (213 loc) · 14.2 KB

Модуль 6. Урок 35. Пользовательские поля и действия в админ-панели Django

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

В этом уроке мы пойдём дальше и разберём две ключевые возможности:

  1. Пользовательские (виртуальные) поля, которые не хранятся в базе данных, а вычисляются «на лету».
  2. Пользовательские действия (actions) — команды, которые можно применить сразу к нескольким выбранным записям.

Обе темы критически важны для реальных проектов: админ-панель — это инструмент разработчика и контент-менеджера, и чем удобнее она будет работать, тем меньше ошибок появится в данных.

Для примеров мы будем использовать упрощённые модели проекта cinemahub. Представим, что у нас есть модель Movie, содержащая:

  • название фильма,
  • описание,
  • год выхода,
  • статус публикации.

1. Зачем нужны пользовательские поля

Каждая строка в админ-панели представляет запись в базе данных. Но иногда данные, которые мы хотим отображать, не должны храниться в таблице. Например:

  • «Сколько символов в описании»
  • «Сколько актёров привязано»
  • «Возраст фильма»
  • «Форматированная дата»
  • «Превью текста»

Это и есть виртуальные поля — методы, которые Django может подставить в list_display так же, как обычные.


2. Пример: добавляем виртуальное поле в список фильмов

Откроем файл 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)} символов"

Теперь:

  1. Сохраняем файл.
  2. Переходим в браузер → /admin/.
  3. Открываем список фильмов.

Появилась новая колонка! Но Django назовёт её автоматически вроде «Description length». Это не слишком красиво.


3. Настраиваем заголовок виртуального поля

Используем декоратор @admin.display:

@admin.display(description="Длина описания")
def description_length(self, movie: Movie):
    return f"{len(movie.description)} символов"

Проверяем в браузере — колонка переименована.


4. Добавляем сортировку по виртуальному полю

Мы показываем длину описания. Логично, что администратор мог бы захотеть увидеть:

  • фильмы с самым длинным описанием,
  • или наоборот — самые короткие.

Но сортировать по полю, которого нет в базе, Django не умеет. Чтобы сортировка всё же работала, нужно указать, по какому реальному полю она будет опираться.

В нашем случае это поле description.

@admin.display(description="Длина описания", ordering='description')
def description_length(self, movie: Movie):
    return f"{len(movie.description)} символов"

Теперь рядом с колонкой появится значок сортировки.


5. Пользовательские действия в админ-панели

Django позволяет выделить несколько записей и применить к ним действие из выпадающего списка.

Например:

  • отметить как опубликованные,
  • снять с публикации,
  • сменить категорию,
  • проставить рейтинг,
  • отправить уведомления (редко, но возможно).

Это экономит огромное количество времени и снижает риск ошибок.


6. Пример: создаём действие «Опубликовать выбранные фильмы»

В модели 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 под капотом.

Проверка:

  1. Заходим в админку.
  2. Выбираем несколько фильмов чекбоксами.
  3. В выпадающем списке действий выбираем «Опубликовать выбранные фильмы».
  4. Нажимаем Выполнить.

Вверху появится сообщение вида:

Опубликовано: 3 фильмов.

7. Добавляем обратное действие — снять с публикации

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

messages.WARNING — это уровень (тип) уведомления в Django Messages Framework. Он обозначает предупреждающее сообщение и используется всякий раз, когда приложение или админ хочет показать админу/пользователю предупреждение (например, «операция выполнена, но с оговорками»). В примере админ-действия message_user(..., messages.WARNING) сообщает администратору об успешном выполнении, но в виде предупреждения (желтая/оранжевая метка).

Django имеет встроенный Messages Framework (django.contrib.messages). В нём определены уровни сообщений:

  • messages.DEBUG
  • messages.INFO
  • messages.SUCCESS
  • messages.WARNING
  • messages.ERROR

messages.WARNING — это константа, которая представляет уровень предупреждения (обычно визуально оформляется как жёлтое/оранжевое уведомление).

Такие сообщения используются повсеместно:

  • В view-функциях и class-based views для информирования пользователя о результате операции (успех/ошибка/предупреждение).
  • В админ-панели — через self.message_user(request, msg, level) внутри классов ModelAdmin/AdminSite. Админ использует сообщения для вывода краткой обратной связи после действий.
  • В шаблонах (через get_messages или тег {% if messages %}) — для показа сообщений пользователю.
  • В коде middleware, сигналов, management-команд — везде, где нужно кратко оповестить о событии.

8. Потенциальные ошибки и как их исправлять

Ошибка №1 — виртуальное поле не отображается в list_display

Причина: метод был определён ниже, но написан без self или с неправильным названием.

Правильно:

def description_length(self, movie):

Неправильно:

def description_length(movie):

Ошибка №2 — сортировка по виртуальному полю не работает

Причина: вы указали искусственное поле в ordering.

Ошибка:

ordering = ['description_length']

Правильно:

@admin.display(ordering='description')

Ошибка №3 — действие не отображается

Причина: забыли добавить метод в список actions.


Практическая часть

  1. Добавьте в админку поле «Возраст фильма». Возраст фильма рассчитывается по формуле: текущий_год - год_выхода

  1. Создайте действие «Сделать год выхода 2000». Действие должно массово изменить год выхода на 2000.

  1. Добавьте колонку с обрезанным описанием (первые 30 символов). Например: "Очень интересный фи..."

Сравнить решение

  1. Добавьте в админку поле
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')
  1. Создайте действие
@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']
  1. Добавьте колонку
class MovieAdmin(admin.ModelAdmin):

    @admin.display(description="Краткое описание")
    def short_description(self, movie):
        return movie.description[:30] + "..."

    list_display = ('title', 'short_description', 'is_published')

Вопросы

  1. Что такое виртуальное поле и зачем оно может понадобиться в админ-панели?
  2. Можно ли сортировать таблицу по виртуальному полю? Если да — то как?
  3. В какой момент Django передаёт объект модели в метод виртуального поля?
  4. Что такое actions и для чего они используются?
  5. Чем отличается update() от изменения каждого объекта вручную?
  6. Что делает метод message_user()?
  7. Как изменить название пользовательского действия?
  8. Где применяется messages.WARNING?
  9. Что произойдёт, если забыть добавить действие в список actions?

Предыдущий урок | Следующий урок