Налаштування Адмін-панелі Django у 2026 році: Дії, Фільтри та Питання на Співбесідах

Повний посібник з налаштування адмін-панелі Django 6 з користувацькими діями, фільтрами списків, fieldsets та inline моделями. Включає питання для співбесід.

Налаштування Адмін-панелі Django у 2026 році: Дії, Фільтри та Питання на Співбесідах

Налаштування адмін-панелі Django перетворює вбудований адміністративний інтерфейс з простого інструменту генерації коду на спеціалізований бек-офіс для виробничих застосунків. Django 6 постачається з покращеними функціями адміністрування, включаючи підрахунок фасетів для фільтрів, налаштовувані форми зміни пароля та іконки Font Awesome, що робить добре налаштовану адмін-панель справді приємною у використанні.

Ключова Інформація

ModelAdmin — це центральний клас для всіх налаштувань адмін-панелі. Опанування list_display, list_filter, search_fields, fieldsets та користувацьких дій дозволяє задовольнити 90% потреб у конфігурації панелі.

Основи ModelAdmin: list_display та search_fields

list_display контролює, які колонки з'являються на сторінці списку змін. Окрім простих назв полів, приймає викликувані функції з декоратором @admin.display() для обчислюваних значень. search_fields активує поле пошуку, яке за замовчуванням фільтрує записи за допомогою icontains.

python
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Article


class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "author_name", "status", "publish_date", "is_recent"]
    search_fields = ["title", "author__email", "content"]
    list_per_page = 50
    list_select_related = ["author"]  # Avoid N+1 queries

    @admin.display(description="Author", ordering="author__last_name")
    def author_name(self, obj):
        return f"{obj.author.first_name} {obj.author.last_name}"

    @admin.display(description="Recent?", boolean=True)
    def is_recent(self, obj):
        from django.utils import timezone
        return obj.publish_date >= timezone.now() - timezone.timedelta(days=7)


admin.site.register(Article, ArticleAdmin)

Декоратор @admin.display() замінює старіший патерн встановлення short_description як атрибута функції. Параметр boolean=True рендерить іконку галочки або X замість тексту True/False. Використання list_select_related запобігає запитам N+1 при доступі до полів зовнішніх ключів у list_display.

Користувацькі list_filter з SimpleListFilter

list_filter активує фільтри бічної панелі на сторінці списку змін. Назви полів працюють для базової фільтрації, але SimpleListFilter обробляє складну логіку фільтрування, що охоплює кілька полів або вимагає користувацьких запитів.

python
# admin.py
from datetime import date, timedelta
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Article


class PublishDateFilter(admin.SimpleListFilter):
    title = _("publish period")
    parameter_name = "published"  # URL parameter: ?published=this_week

    def lookups(self, request, model_admin):
        # Returns (value, label) tuples for the filter sidebar
        return [
            ("this_week", _("This week")),
            ("this_month", _("This month")),
            ("this_year", _("This year")),
            ("unpublished", _("Not published")),
        ]

    def queryset(self, request, queryset):
        # Filter the queryset based on self.value()
        today = date.today()
        if self.value() == "this_week":
            return queryset.filter(publish_date__gte=today - timedelta(days=7))
        if self.value() == "this_month":
            return queryset.filter(
                publish_date__year=today.year, publish_date__month=today.month
            )
        if self.value() == "this_year":
            return queryset.filter(publish_date__year=today.year)
        if self.value() == "unpublished":
            return queryset.filter(publish_date__isnull=True)
        return queryset


class ArticleAdmin(admin.ModelAdmin):
    list_filter = [
        "status",
        PublishDateFilter,
        ("author", admin.RelatedOnlyFieldListFilter),  # Show only authors with articles
    ]

RelatedOnlyFieldListFilter обмежує випадаючий список авторів лише тими, хто дійсно має статті, замість відображення кожного користувача в базі даних. Це покращує зручність використання, коли пов'язана модель має багато записів.

Фасети Django 6: Лічильники Фільтрів на Бічній Панелі

Django 6 представив підрахунок фасетів, який відображає кількість відповідних об'єктів поруч з кожною опцією фільтра. Атрибут show_facets контролює цю поведінку.

python
# admin.py
from django.contrib import admin
from django.contrib.admin import ShowFacets
from .models import Article


class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "status", "category"]
    list_filter = ["status", "category", "author"]
    show_facets = ShowFacets.ALWAYS  # Always display counts

    # Alternative values:
    # ShowFacets.ALLOW - Show counts when ?_facets=1 in URL
    # ShowFacets.NEVER - Never show counts (for large datasets)

Лічильники фасетів виконують додаткові запити COUNT для кожної опції фільтра. На таблицях з мільйонами рядків слід встановити show_facets = ShowFacets.NEVER, щоб уникнути зниження продуктивності. Лічильники оновлюються динамічно при застосуванні фільтрів, показуючи скільки записів відповідає поточній комбінації фільтрів.

Користувацькі Адмін-Дії для Масових Операцій

Адміністративні дії виконують масові операції над вибраними об'єктами. Декоратор @admin.action встановлює дозволи та опис, що відображається у випадаючому меню.

python
# admin.py
from django.contrib import admin
from django.contrib import messages
from django.utils.translation import ngettext
from .models import Article


@admin.action(description="Publish selected articles", permissions=["change"])
def publish_articles(modeladmin, request, queryset):
    # Exclude already published articles
    unpublished = queryset.exclude(status="published")
    updated = unpublished.update(status="published")

    modeladmin.message_user(
        request,
        ngettext(
            "%d article was published.",
            "%d articles were published.",
            updated,
        )
        % updated,
        messages.SUCCESS,
    )


@admin.action(description="Export selected as JSON")
def export_as_json(modeladmin, request, queryset):
    from django.http import HttpResponse
    from django.core import serializers

    response = HttpResponse(content_type="application/json")
    response["Content-Disposition"] = 'attachment; filename="articles.json"'
    serializers.serialize("json", queryset, stream=response)
    return response  # Returning HttpResponse triggers file download


class ArticleAdmin(admin.ModelAdmin):
    actions = [publish_articles, export_as_json]

    def get_actions(self, request):
        # Conditionally remove actions based on user permissions
        actions = super().get_actions(request)
        if not request.user.has_perm("articles.delete_article"):
            del actions["delete_selected"]
        return actions

Повернення HttpResponse з дії ініціює завантаження файлу або перенаправляє на сторінку підтвердження. Параметр permissions обмежує дію користувачами з певними дозволами моделі. Перевизначення get_actions() дозволяє динамічно показувати або приховувати дії на основі поточного запиту.

Готовий до співбесід з Django?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Fieldsets та Inline Моделі для Організованих Форм

fieldsets організовує форми додавання та редагування у розгортувані секції. TabularInline та StackedInline вбудовують пов'язані моделі безпосередньо у батьківську форму, усуваючи навігацію між сторінками.

python
# admin.py
from django.contrib import admin
from .models import Article, ArticleImage, ArticleComment


class ArticleImageInline(admin.TabularInline):
    model = ArticleImage
    extra = 1  # Number of empty forms to display
    max_num = 5  # Maximum images per article
    fields = ["image", "caption", "order"]


class ArticleCommentInline(admin.StackedInline):
    model = ArticleComment
    extra = 0
    readonly_fields = ["created_at", "author"]
    can_delete = True


class ArticleAdmin(admin.ModelAdmin):
    fieldsets = [
        (
            None,
            {
                "fields": ["title", "slug", "author"],
            },
        ),
        (
            "Content",
            {
                "fields": ["content", "excerpt"],
                "classes": ["wide"],  # Full-width fields
            },
        ),
        (
            "Publishing",
            {
                "fields": ["status", "publish_date", "category", "tags"],
                "classes": ["collapse"],  # Collapsed by default
            },
        ),
        (
            "SEO",
            {
                "fields": ["meta_title", "meta_description"],
                "classes": ["collapse"],
                "description": "Search engine optimization fields",
            },
        ),
    ]
    inlines = [ArticleImageInline, ArticleCommentInline]
    prepopulated_fields = {"slug": ["title"]}  # Auto-generate slug from title
    autocomplete_fields = ["author", "category"]  # AJAX-powered select
    filter_horizontal = ["tags"]  # Widget for M2M selection


admin.site.register(Article, ArticleAdmin)

TabularInline відображає пов'язані об'єкти у компактному табличному форматі, що підходить для простих моделей з кількома полями. StackedInline показує кожен пов'язаний об'єкт як окремий блок форми, краще підходить для моделей з багатьма полями. Опція autocomplete_fields замінює стандартний випадаючий список на віджет пошуку AJAX, що є необхідним, коли пов'язана модель має тисячі записів.

Налаштування Сайту Адміністрування

Створення підкласу AdminSite дозволяє мати кілька адміністративних інтерфейсів з різним брендингом, дозволами або реєстраціями моделей. Django 6 додав можливість налаштування password_change_form для суворіших політик паролів.

python
# admin.py
from django.contrib import admin
from django.contrib.admin import AdminSite
from .models import Article, Category


class ContentAdminSite(AdminSite):
    site_header = "Content Management"
    site_title = "CMS"
    index_title = "Dashboard"

    def has_permission(self, request):
        # Restrict to staff in the 'editors' group
        return (
            request.user.is_active
            and request.user.is_staff
            and request.user.groups.filter(name="editors").exists()
        )


content_admin = ContentAdminSite(name="content_admin")
content_admin.register(Article, ArticleAdmin)
content_admin.register(Category)

# In urls.py:
# path('content-admin/', content_admin.urls),

Кожен екземпляр AdminSite має власний простір імен URL, сторінку входу та набір зареєстрованих моделей. Цей патерн підходить для застосунків з різними ролями користувачів, де редактори не повинні бачити управління користувачами або системні налаштування.

Питання на Співбесідах: Налаштування Адмін-панелі Django

Ці питання часто з'являються на технічних співбесідах з Django. Розуміння базових механізмів, а не лише синтаксису, відрізняє кандидатів середнього рівня від старших інженерів.

П: Як додати обчислювану колонку до list_display, яку не можна сортувати?

Декоратор @admin.display() приймає параметр ordering, який вказує яке поле бази даних використовувати для сортування. Без нього заголовок колонки не є клікабельним. Обчислювані значення, що агрегують кілька записів або викликають зовнішні сервіси, не можуть бути відсортовані, оскільки немає базової колонки бази даних для впорядкування.

П: Який вплив лічильників фасетів на продуктивність і коли їх слід вимикати?

Фасети виконують один запит COUNT на кожну опцію фільтра, помножену на кількість активних фільтрів. На таблиці з 10 мільйонами рядків і 5 опціями фільтра на 3 полях, це 15 додаткових запитів COUNT на кожне завантаження сторінки. Слід встановити show_facets = ShowFacets.NEVER, коли будь-який фільтр націлений на таблицю з більш ніж 100 000 рядків без відповідних індексів.

П: Чим відрізняються адмін-дії від користувацьких представлень адміністратора?

Дії працюють з queryset вибраних об'єктів і інтегруються з прапорцями вибору списку змін. Користувацькі представлення адміністратора — це окремі сторінки, доступні через користувацькі URL, підходять для звітів, імпортів або дашбордів, які не працюють з вибраними записами. Дії не повинні виконувати тривалі операції, оскільки вони блокують запит.

П: Чому використовувати RelatedOnlyFieldListFilter замість стандартного фільтра?

Стандартний RelatedFieldListFilter запитує всі об'єкти в пов'язаній таблиці. Коли зовнішній ключ вказує на модель User зі 100 000 записами, випадаючий список фільтра завантажує всі 100 000 опцій, заморожуючи браузер. RelatedOnlyFieldListFilter запитує лише значення, які існують у записах поточної моделі, зазвичай значно менший набір.

Типові Помилки, Яких Слід Уникати у Виробництві

Кілька патернів, що працюють у розробці, спричиняють проблеми при масштабуванні.

python
# admin.py
from django.contrib import admin
from .models import Order


class OrderAdmin(admin.ModelAdmin):
    # Mistake: Accessing related fields without list_select_related
    # This causes N+1 queries, one per row in the list view
    list_display = ["id", "customer_email", "total"]

    # Fix: Prefetch related objects
    list_select_related = ["customer"]

    def customer_email(self, obj):
        return obj.customer.email

    # Mistake: search_fields on unindexed large text columns
    # search_fields = ["notes"]  # Slow on large tables

    # Fix: Limit search to indexed fields
    search_fields = ["id", "customer__email"]

    # Mistake: Expensive computation in list_display
    # def order_total(self, obj):
    #     return sum(item.price * item.qty for item in obj.items.all())  # N+1

    # Fix: Use annotation in get_queryset
    def get_queryset(self, request):
        from django.db.models import Sum, F

        qs = super().get_queryset(request)
        return qs.annotate(computed_total=Sum(F("items__price") * F("items__qty")))

    @admin.display(description="Total", ordering="computed_total")
    def total(self, obj):
        return obj.computed_total

Перевизначення get_queryset() для додавання анотацій зберігає обчислювані значення в одному запиті, замість звернення до бази даних один раз на кожен рядок. Профілювання адмін-панелі за допомогою Django Debug Toolbar дозволяє виявити запити N+1 до того, як вони потраплять у виробництво.

Сторонні Теми для Адмін-панелі: Django Unfold

Django Unfold — найактивніше підтримувана тема адміністрування для Django 6, побудована на Tailwind CSS. Вона розширює стандартну адмін-панель, а не замінює її, тому існуючі конфігурації ModelAdmin продовжують працювати.

python
# settings.py
INSTALLED_APPS = [
    "unfold",  # Must come before django.contrib.admin
    "unfold.contrib.filters",  # Advanced filter widgets
    "unfold.contrib.forms",  # Enhanced form widgets
    "django.contrib.admin",
    # ...
]

Unfold додає темний режим, адаптивні макети, бічну навігацію та розширені типи фільтрів, такі як RangeNumericFilter та RangeDateFilter. Інтеграція HTMX та Alpine.js забезпечує інтерактивність без накладних витрат повноцінного SPA-фреймворку. Для команд, яким потрібна відполірована адмін-панель без створення користувацького дашборду, Unfold економить тижні роботи над фронтендом.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Що Запам'ятати про Налаштування Адмін-панелі Django

  • list_display з @admin.display() контролює колонки; додавання ordering забезпечує можливість сортування обчислюваних значень
  • SimpleListFilter обробляє користувацьку логіку фільтрування, що охоплює кілька полів або вимагає діапазонів дат
  • Фасети Django 6 показують лічильники фільтрів; вимкнення через ShowFacets.NEVER на великих таблицях дозволяє уникнути накладних витрат на запити COUNT
  • Адмін-дії з @admin.action(permissions=[...]) обробляють масові операції; повернення HttpResponse забезпечує експорт файлів
  • Використання list_select_related та анотованих queryset у get_queryset() запобігає запитам N+1 у представленнях списків
  • RelatedOnlyFieldListFilter запобігає зависанню браузера при фільтруванні за зовнішнім ключем до великих таблиць
  • Практикування цих патернів з питаннями на співбесідах Django, спеціально спрямованими на конфігурацію адмін-панелі
Щоденний виклик

Чи знайдеш ти помилку в Django?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 13 вересня 2026 р.

Поділитися

Пов'язані статті