Шаг 04. Админка: ModelAdmin, Inline и Actions
⚡ Кратко: что делаем на этом шаге
Цель: Зарегистрировать все модели в Django Admin с настройками list_display, list_filter, search_fields. Добавить TaskInline в ProjectAdmin. Написать кастомный action «Отметить выполненными».
- Файлы:
apps/projects/admin.py,apps/tasks/admin.py - Команды: запустить сервер, открыть Admin
- Результат: все модели доступны в Admin с удобными фильтрами; задачи видны внутри проекта
🎯 Цель этапа
Django Admin — мощный инструмент управления данными. Настроив его хорошо, можно создавать/редактировать проекты и задачи без API и без frontend. Это особенно полезно при разработке — проще наполнить базу тестовыми данными.
TabularInline позволяет видеть и редактировать связанные объекты
прямо в форме родительского объекта. Открываем проект — видим его задачи.
Это закрывает callout-verify из уроков 19/24 про inline-формы.
После этого шага у нас будет
- Project, Task, Comment — в Django Admin с настроенными фильтрами
- Inline: задачи видны внутри проекта
- Action: выбрать задачи → «Отметить выполненными» (bulk update)
📄 Затрагиваемые файлы
| Файл | Действие | Описание |
|---|---|---|
apps/projects/admin.py | Заполнить | ProjectAdmin с TaskInline |
apps/tasks/admin.py | Заполнить | TaskAdmin и CommentAdmin с actions |
🔨 Шаги
1. TaskInline и ProjectAdmin
# apps/projects/admin.py
from django.contrib import admin
from .models import Project
from apps.tasks.models import Task
class TaskInline(admin.TabularInline):
"""
Задачи внутри проекта — отображаются прямо в форме проекта.
TabularInline: компактный табличный вид (vs StackedInline — вертикальный).
"""
model = Task
fields = ("title", "assignee", "status", "priority", "due_date")
extra = 0 # не показывать пустые формы добавления по умолчанию
show_change_link = True # ссылка на полную форму редактирования задачи
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
list_display = ("name", "owner", "is_active", "task_count", "created_at")
list_filter = ("is_active", "created_at")
search_fields = ("name", "description", "owner__email")
readonly_fields = ("created_at", "updated_at")
filter_horizontal = ("members",) # удобный виджет для M2M
inlines = [TaskInline]
@admin.display(description="Задач")
def task_count(self, obj: Project) -> int:
"""Кол-во задач проекта — без лишнего запроса (через annotate в get_queryset)."""
return obj.tasks.filter(is_deleted=False).count()
2. TaskAdmin и CommentAdmin с action
# apps/tasks/admin.py
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Task, Comment
@admin.action(description="Отметить выбранные задачи как выполненные")
def mark_done(modeladmin, request, queryset):
"""
Bulk action: устанавливает status=DONE для выбранных задач.
queryset.update() не вызывает save() на каждом объекте — это быстро,
но обходит сигналы post_save (важно помнить).
"""
updated = queryset.update(status=Task.Status.DONE)
modeladmin.message_user(
request,
f"Отмечено выполненными: {updated} задач.",
)
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = (
"title", "project", "assignee", "status", "priority",
"due_date", "is_deleted", "created_at",
)
list_filter = ("status", "priority", "is_deleted", "project", "created_at")
search_fields = ("title", "description", "assignee__email", "project__name")
readonly_fields = ("created_at", "updated_at")
list_editable = ("status", "priority") # редактирование прямо в списке
actions = [mark_done]
date_hierarchy = "created_at"
def get_queryset(self, request):
"""
По умолчанию Admin показывает все записи, включая is_deleted=True.
Для Admin это нормально — нужно видеть и «удалённые» задачи.
В API (этап 11) добавим менеджер, скрывающий их от обычных пользователей.
"""
return super().get_queryset(request).select_related("project", "assignee")
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ("task", "author", "text_preview", "created_at")
list_filter = ("created_at",)
search_fields = ("text", "author__email", "task__title")
readonly_fields = ("created_at", "updated_at")
@admin.display(description="Текст")
def text_preview(self, obj: Comment) -> str:
"""Первые 60 символов комментария."""
return obj.text[:60] + ("..." if len(obj.text) > 60 else "")
TaskAdmin.get_queryset() мы добавляем select_related("project", "assignee").
Это оптимизация: без неё для каждой строки в списке Admin делался бы отдельный SQL-запрос
за связанным объектом (N+1 проблема). С select_related — один JOIN-запрос.
🧠 Объяснение логики
TabularInline vs StackedInline
Django предоставляет два стиля inline:
- TabularInline — компактная таблица: поля в одну строку. Хорошо для простых моделей с небольшим числом полей (как Task).
- StackedInline — вертикальный блок как полноценная форма. Хорош для сложных моделей с большим числом полей.
Для задач внутри проекта TabularInline компактнее и удобнее.
actions — bulk operations
Django Action — функция, которую пользователь Admin может применить к выбранным
объектам из списка. queryset.update(status=Task.Status.DONE) делает
один SQL UPDATE для всех выбранных строк — это быстро.
Но: queryset.update() не вызывает метод save() на каждом
объекте, а значит не срабатывают сигналы post_save. Если вам нужна
логика в сигналах (этап 10) — используйте цикл for obj in queryset: obj.save()
или вызывайте сигналы вручную.
list_editable, должны также быть в list_display.
Иначе Django выбросит ImproperlyConfigured.
✅ Проверка
1. Запускаем сервер
python manage.py runserver
2. Открываем Admin
Открываем http://127.0.0.1:8000/admin/
- В разделе «ПРОЕКТЫ» видим Project с колонками: название, владелец, активен, кол-во задач, дата создания
- Открыв проект, видим раздел «Tasks» с задачами в табличном виде (TaskInline)
- В разделе «ЗАДАЧИ» видим Task с колонками статус, приоритет, исполнитель; можно фильтровать и искать
- В Task list выбираем задачи → Action «Отметить как выполненные» → статус меняется
Диагностика: если что-то пошло не так
- Модели не видны в Admin — не добавили
@admin.registerили не включили приложение вINSTALLED_APPS ImproperlyConfigured: The value of 'list_editable[0]'...— поле есть вlist_editable, но нет вlist_display- TaskInline пустой — к проекту ещё нет задач; создайте через Admin напрямую или через shell
➡️ Что дальше
На следующем шаге переходим к самому сердцу DRF — сериализаторам.
Напишем ModelSerializer для всех моделей, добавим валидацию
и вложенные представления.
- Готово: Django Admin настроен, inline и action работают
- Далее (шаг 05):
apps/tasks/serializers.py,apps/projects/serializers.py— ModelSerializer, validate_, вложенные поля - После шага 05 у нас будет слой сериализации для всего API