Переопределение админки Django: архитектурные решения и их цена
Стандартная админка Django рассчитана на CRUD-операции с плоскими моделями. Когда процесс включает многошаговые формы, согласования и зависимые состояния, админку приходится ломать — и каждый способ сломать имеет свою цену в часах, багах и техдолге.

Где заканчивается ModelAdmin
Django Admin — это генератор интерфейса поверх ORM. Он решает одну задачу: дать пользователю список объектов, форму создания и форму редактирования. Всё, что выходит за рамки «открыл, поправил поле, сохранил», требует вмешательства. Проблема не в том, что админка плохая, — она отличная для своей задачи. Проблема в том, что бизнес-процессы редко укладываются в CRUD.
Типичные ситуации, в которых стандартный ModelAdmin перестаёт справляться:
- Объект проходит через цепочку статусов с разными правами на каждом этапе: черновик → на проверке → согласован → опубликован.
- Форма создания — это мастер из трёх-пяти шагов, где данные второго шага зависят от выбора на первом.
- Сохранение одного объекта запускает побочные эффекты: создание связанных записей, отправку уведомлений, вызов внешнего API.
- Разные роли видят разные поля одной и той же модели и могут выполнять разные действия.
Ниже — четыре подхода к решению, от минимального вмешательства до полного отказа от админки. Каждый с ценой.
Подход 1: максимальная кастомизация ModelAdmin
Идея простая — не уходить из стандартной админки, а использовать всё, что она позволяет переопределить. В Django 5.x у ModelAdmin десятки хуков: get_fields, get_readonly_fields, get_queryset, save_model, response_change, get_urls, кастомные actions и инлайны.
Что получается
Вы переопределяете get_readonly_fields, чтобы на статусе «согласован» все поля, кроме кнопки «опубликовать», стали нередактируемыми. Через get_urls добавляете собственные view для нестандартных действий. Через save_model навешиваете побочные эффекты. Через change_view подменяете шаблон.
# Django 5.x, пример динамических readonly-полей по статусу
class OrderAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if obj and obj.status == "approved":
# После согласования редактировать можно только комментарий
return [f.name for f in self.model._meta.fields if f.name != "comment"]
return self.readonly_fields
def save_model(self, request, obj, form, change):
previous_status = None
if obj.pk:
previous_status = self.model.objects.filter(pk=obj.pk).values_list("status", flat=True).first()
super().save_model(request, obj, form, change)
if previous_status != obj.status:
# Побочный эффект при смене статуса
notify_status_change(obj, previous_status, obj.status, user=request.user)
Цена
Этот путь работает, пока количество if-ов в одном классе ModelAdmin не превышает порога читаемости. На практике порог — около 200–300 строк. После этого get_readonly_fields содержит матрицу «статус × роль × тип объекта», save_model — цепочку побочных эффектов с непредсказуемым порядком, а каждый новый разработчик тратит день, чтобы понять, почему поле исчезает у менеджера, но видно админу.
Основная ловушка — отсутствие явной модели процесса. Логика размазана между get_readonly_fields, has_change_permission, save_model и кастомными actions. Тесты превращаются в интеграционные, потому что поведение зависит от состояния объекта, роли и порядка вызова хуков.
Подход 2: свои view внутри AdminSite
Вместо того чтобы нагружать ModelAdmin, вы регистрируете собственные view через get_urls на уровне AdminSite или конкретного ModelAdmin. View может быть обычной Django-view с собственным шаблоном, формой и логикой. Визуально она живёт «внутри» админки — наследует базовый шаблон admin/base_site.html, пользуется сессией и проверкой прав.
Что получается
Многошаговая форма оформления заказа — три отдельных view с промежуточным хранением в сессии. Страница согласования — отдельная view с собственной логикой отображения и кнопками действий. Дашборд — view со своим SQL.
# Регистрация кастомной view внутри ModelAdmin (Django 5.x)
class OrderAdmin(admin.ModelAdmin):
def get_urls(self):
custom_urls = [
path(
"/approve/",
self.admin_site.admin_view(self.approve_view), # оборачиваем в проверку прав
name="order_approve",
),
]
return custom_urls + super().get_urls()
def approve_view(self, request, order_id):
order = get_object_or_404(Order, pk=order_id)
if request.method == "POST":
order.approve(approved_by=request.user)
self.message_user(request, f"Заказ {order.number} согласован.")
return redirect(reverse("admin:app_order_changelist"))
context = {**self.admin_site.each_context(request), "order": order}
return render(request, "admin/order_approve.html", context)
Цена
Шаблоны. Как только вы выходите за пределы стандартного change_form.html, вы сами отвечаете за HTML, навигацию и breadcrumbs. Базовый шаблон админки не документирован как стабильный API — блоки могут меняться между мажорными версиями Django. Обновление Django с 4.x на 5.x сломало верстку у тех, кто переопределял {% block extrastyle %}.
Второй риск — размытие архитектуры. Часть логики живёт в ModelAdmin, часть — в отдельных view, часть — в формах. Нет единого места, где видна вся картина процесса.
Подход 3: отдельное приложение с собственным интерфейсом
Радикальный шаг: для сложного процесса создаётся отдельное Django-приложение со своими URL, шаблонами и правами. Админка остаётся для простых справочников, а процесс живёт рядом.
Что получается
Полный контроль над UX. Многошаговые формы — это обычные CBV или FBV. Права — через миксины. Логика процесса вынесена в сервисный слой. Шаблоны написаны под задачу, без борьбы с внутренностями админки.
Цена
Вы потеряли всё, что админка давала бесплатно: авторизацию (нужно повторить или подключить), навигацию, фильтры, пагинацию, логирование изменений (LogEntry), inline-редактирование связей. Каждая из этих мелочей — от четырёх часов до двух дней работы. В сумме — недели, которые в стартапе никто не закладывает.
Переопределение админки — это кредит под проценты. Каждый хук экономит день сейчас и стоит неделю через полгода. Собственный интерфейс — это инвестиция: дорого в моменте, но амортизируется, если процесс переживёт три итерации изменений.
Подход 4: гибрид — django-admin + SPA-виджеты
Современный компромисс: админка остаётся каркасом, но отдельные блоки страницы заменяются на интерактивные компоненты, которые общаются с API. Чаще всего это React, Vue или Alpine.js, подгружаемые через кастомный шаблон change_form.html. API делается через Django REST Framework или Django Ninja.
Цена
Два стека — Python и JavaScript — в одном проекте. Фронтенд-часть требует сборки, и теперь у вас npm в CI рядом с pip. Отладка сложнее: баг может быть в сериализаторе, в компоненте или в том, как админка подгружает статику. Alpine.js снижает порог входа, но ограничивает возможности.
Сравнение подходов
| Критерий | Кастомизация ModelAdmin | Свои view в AdminSite | Отдельное приложение | Админка + SPA-виджеты |
|---|---|---|---|---|
| Время старта | Часы | 1–3 дня | 1–3 недели | 3–7 дней |
| Потолок сложности процесса | Низкий (2–3 статуса, одна роль) | Средний | Нет ограничений | Средний–высокий |
| Стоимость поддержки через год | Высокая, растёт нелинейно | Средняя | Низкая, если есть сервисный слой | Средняя, зависит от размера фронтенда |
| Риск при обновлении Django | Низкий | Средний (шаблоны) | Низкий | Средний (статика + шаблоны) |
| Зависимость от Django Admin API | Полная | Частичная | Нулевая | Частичная |
Где каждый подход ломается
ModelAdmin
Ломается, когда процесс требует промежуточных данных, которых нет в модели. Пример: форма согласования просит оставить комментарий и выбрать следующего ревьюера. Ни то, ни другое не поле модели Order. Вы начинаете городить дополнительные формы поверх change_view — и получаете подход 2, но хуже, потому что пытаетесь впихнуть его в рамки подхода 1.
Свои view в AdminSite
Ломаются при обновлении Django, если вы наследовались от внутренних шаблонов. Также ломаются при масштабировании команды: два разработчика независимо добавляют view в один get_urls, и URL-конфликты выясняются в проде.
Отдельное приложение
Ломается организационно. Менеджер спрашивает: «Зачем нам два интерфейса?» — и через квартал вы дублируете справочники в оба места или пишете универсальный «портал», который дороже, чем оба интерфейса вместе.
Админка + SPA
Ломается, когда Alpine.js перестаёт хватать и вы переходите на React. Теперь у виджета своя сборка, свои зависимости и свой жизненный цикл. Админка становится не каркасом, а обузой — проще было сразу делать подход 3.
Когда что выбирать
Решение зависит от двух переменных: сложность процесса и срок жизни проекта.
- Прототип или внутренний инструмент, процесс из двух-трёх статусов, одна роль — подход 1. Не усложняйте.
- Процесс с тремя и более ролями, нестандартные формы, но проект живёт внутри компании — подход 2. View управляемы, шаблоны предсказуемы, пока вы контролируете версию Django.
- Продукт с длинным жизненным циклом, процесс — ядро бизнеса — подход 3. Заложите время на авторизацию и навигацию сразу.
- Нужна интерактивность отдельных элементов, но не всего интерфейса — подход 4 с Alpine.js. Не начинайте с React, если процесс помещается в 500 строк JS.
Сноска. Сервисный слой (Service Layer) — паттерн, при котором бизнес-логика выносится из моделей и view в отдельные функции или классы. В контексте Django это означает, что save_model в админке вызывает order_service.approve(order, user), а не содержит логику согласования напрямую. Тот же сервис используется в API, в CLI-команде и в тестах — без зависимости от AdminSite.
Что делать дальше
Прежде чем трогать admin.py, нарисуйте диаграмму состояний процесса. Если в ней больше четырёх состояний и два перехода зависят от роли — вы уже за пределами подхода 1, и начинать с него значит переписывать через квартал. Вынесите логику переходов в сервисный слой до того, как она прорастёт в хуки ModelAdmin. Посмотрите библиотеку django-fsm для формализации состояний — она не привязана к админке и работает на уровне модели. Измерьте, сколько строк занимает ваш текущий admin.py: если больше 300 — это сигнал, что пора выносить логику наружу.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Django Admin Site — официальная документация Django, django-fsm — библиотека для конечных автоматов в Django-моделях (GitHub)
Частые вопросы
Можно ли использовать стандартную Django-админку для процесса согласования документов?
Как не сломать кастомизацию при обновлении Django?
Стоит ли использовать django-fsm для управления статусами?
Что если мой admin.py уже 500 строк — переписывать всё?
Нужен ли React или Vue, если хочется интерактивности в админке?
А если у меня другая версия Django — подходы работают?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.