← К оглавлению урока

🏠 Домашнее задание 11: Инлайн-формы и Admin Actions

📋 Проект: Менеджер задач 🎯 Сложность: Средняя

⚡ Кратко: три задания

  1. SubTaskInline в TaskAdmin — создавать подзадачи прямо при редактировании задачи
  2. Укороченный __str__ для Task — первые 10 символов + «...» в списке, полное название в Inline-выпадашке
  3. Action «Done» для SubTask — массовый перевод подзадач в статус Done

Задание из LMS (полный текст)

Домашнее задание: Проект "Менеджер задач" — Инлайн формы и Admin actions

Задание 1:
Добавить настройку инлайн форм для админ класса задач. При создании задачи должна появиться возможность создавать сразу и подзадачу.

Задание 2:
Названия задач могут быть длинными и ухудшать читаемость в Админ панели, поэтому требуется выводить в списке задач укороченный вариант – первые 10 символов с добавлением «...», если название длиннее, при этом при выборе задачи для создания подзадачи должно отображаться полное название. Необходимо реализовать такую возможность.

Задание 3:
Реализовать свой action для Подзадач, который поможет выводить выбранные в Админ панели объекты в статус Done.

Подготовка окружения

1. Виртуальное окружение и зависимости

# Создать и активировать venv (PowerShell)
python -m venv venv
.\venv\Scripts\Activate.ps1

# Установить Django
pip install django

# Если проект уже существует — только активировать venv
.\venv\Scripts\Activate.ps1

2. Структура проекта

Предполагаем, что у вас уже есть Django-проект с приложением tasks из предыдущих ДЗ. Если нет:

django-admin startproject task_manager .
python manage.py startapp tasks

Добавить 'tasks' в INSTALLED_APPS в settings.py.

3. Создать суперпользователя (если ещё нет)

python manage.py createsuperuser

4. Git — зафиксировать точку отсчёта

git add .
git commit -m "chore: начало ДЗ 11"

Исходные модели (напоминание)

Используем модели из предыдущих ДЗ. Минимальная структура для этого задания:

# tasks/models.py
from django.db import models


class Task(models.Model):
    class Status(models.TextChoices):
        TODO = 'TODO', 'To Do'
        IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
        DONE = 'DONE', 'Done'

    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.TODO
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ['title']


class SubTask(models.Model):
    class Status(models.TextChoices):
        TODO = 'TODO', 'To Do'
        IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
        DONE = 'DONE', 'Done'

    title = models.CharField(max_length=200)
    task = models.ForeignKey(
        Task,
        on_delete=models.CASCADE,
        related_name='subtasks'
    )
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.TODO
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
python manage.py makemigrations
python manage.py migrate

Пошаговое решение

Задание 1: SubTaskInline для TaskAdmin

Логика: нужно добавить Inline-форму для SubTask на страницу редактирования Task. SubTask имеет ForeignKey на Task — это именно то отношение, которое нужно Inline.

Выбор типа: так как SubTask имеет мало полей, подходит TabularInline (компактнее). Теоретически можно и StackedInline — это на усмотрение.

# tasks/admin.py
from django.contrib import admin
from .models import Task, SubTask


class SubTaskInline(admin.TabularInline):
    model = SubTask     # SubTask имеет FK на Task
    extra = 1           # одна пустая строка для новой подзадачи
    fields = ['title', 'status']  # только нужные поля


@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    inlines = [SubTaskInline]   # подключаем Inline
    list_display = ['title', 'status', 'created_at']


@admin.register(SubTask)
class SubTaskAdmin(admin.ModelAdmin):
    list_display = ['title', 'task', 'status']

Проверка: открыть Admin → Tasks → создать/открыть задачу → внизу должна быть секция «Sub tasks» с табличной формой.

Задание 2: Укороченный __str__ для списка, полное название в Inline

Логика задачи:

  • В списке задач Admin показывать первые 10 символов + «...» (если длиннее)
  • В выпадающем поле выбора (Inline-форма SubTask, поле task) — полное название

Ключевое понимание: __str__ используется везде (и в списке, и в выпадашке). Чтобы в списке показывать короткое, а в выпадашке полное — нужно добавить в list_display кастомный метод, а __str__ оставить полным.

# tasks/models.py
class Task(models.Model):
    ...
    def __str__(self):
        return self.title  # полное название — для Inline выпадашки

    def short_title(self):
        """Укороченное название для list_display"""
        if len(self.title) > 10:
            return self.title[:10] + '...'
        return self.title
    short_title.short_description = 'Название'  # заголовок столбца
# tasks/admin.py
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    inlines = [SubTaskInline]
    list_display = ['short_title', 'status', 'created_at']
    #              ^^^^^^^^^^^^ используем метод, не __str__
    search_fields = ['title']
Почему так? __str__ вызывается при формировании выпадающего списка для ForeignKey-поля в Inline. Если сделать __str__ коротким — выпадашка тоже будет с «...», что неудобно для выбора нужной задачи. Кастомный метод short_title используется только в list_display.

Задание 3: Action «Done» для SubTask

Логика: создать action, который переводит выбранные подзадачи в статус DONE. Использовать queryset.update() для эффективности.

# tasks/admin.py
from django.contrib import admin
from .models import Task, SubTask


class SubTaskInline(admin.TabularInline):
    model = SubTask
    extra = 1
    fields = ['title', 'status']


@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    inlines = [SubTaskInline]
    list_display = ['short_title', 'status', 'created_at']
    search_fields = ['title']


@admin.register(SubTask)
class SubTaskAdmin(admin.ModelAdmin):
    list_display = ['title', 'task', 'status', 'created_at']
    list_filter = ['status']

    def mark_done(self, request, queryset):
        queryset.update(status=SubTask.Status.DONE)

    mark_done.short_description = "Перевести выбранные подзадачи в статус Done"

    actions = [mark_done]
Шаги проверки Action:
  1. Открыть Admin → Sub tasks
  2. Отметить одну или несколько подзадач чекбоксами
  3. В выпадающем списке «Action» выбрать «Перевести выбранные подзадачи в статус Done»
  4. Нажать «Go»
  5. Убедиться, что статус изменился на «Done»

Итоговый admin.py

# tasks/admin.py
from django.contrib import admin
from .models import Task, SubTask


class SubTaskInline(admin.TabularInline):
    """Inline для создания подзадач на странице задачи."""
    model = SubTask
    extra = 1
    fields = ['title', 'status']


@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    inlines = [SubTaskInline]
    list_display = ['short_title', 'status', 'created_at']
    search_fields = ['title']
    list_filter = ['status']


@admin.register(SubTask)
class SubTaskAdmin(admin.ModelAdmin):
    list_display = ['title', 'task', 'status', 'created_at']
    list_filter = ['status', 'task']
    search_fields = ['title']

    def mark_done(self, request, queryset):
        """Массовый перевод в статус Done."""
        queryset.update(status=SubTask.Status.DONE)

    mark_done.short_description = "Перевести выбранные подзадачи в статус Done"
    actions = [mark_done]

Проверка в VS Code

Через терминал

# Убедиться, что миграции применены
python manage.py migrate

# Запустить dev-сервер
python manage.py runserver

# Открыть в браузере
# http://127.0.0.1:8000/admin/

Запуск через F5 (launch.json)

Создать или обновить .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Django: runserver",
      "type": "debugpy",
      "request": "launch",
      "program": "${workspaceFolder}/manage.py",
      "args": ["runserver", "8000"],
      "django": true,
      "justMyCode": true
    }
  ]
}

Нажать F5 — сервер запустится в режиме отладки. Можно ставить точки останова в admin.py (например, внутри mark_done), чтобы видеть состояние queryset.

Где ставить точки останова

  • mark_done — строка queryset.update(...): посмотреть, какие объекты в QuerySet
  • SubTaskInline — переопределите save_model если нужна отладка сохранения

Чеклист проверки

  • При открытии Task в Admin → снизу появляется секция «Sub tasks» с TabularInline
  • Можно добавить SubTask прямо из формы Task (не переходя в раздел Sub tasks)
  • В списке Tasks название обрезается до 10 символов + «...»
  • В выпадающем поле task в SubTask Inline — полное название задачи
  • В разделе Sub tasks → выбрать несколько → Action «Перевести... в Done» → статус изменился

Связь с теорией и примерами урока