Настройка админки Django

Автоматически генерируемый админский сайт в Django является одним из самых сильных мест фреймворка. Централизованный интерфейс администратора позволяет легко просматривать данные модели и манипулировать ими. Это позволяет сэкономить массу времени при разработке и управлении контентом.

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

В этой статье на практических примерах мы рассмотрим, как настроить админку сайта Django. Мы рассмотрим встроенные возможности настройки, а также настройку с помощью сторонних пакетов, таких как DjangoQL, django-import-export и django-admin-interface.

Цели

К концу этой статьи вы сможете:

  1. Выполнить базовую конфигурацию сайта администратора Django
  2. Объясните, как атрибуты модели Django влияют на админку сайта
  3. Использование list_display для управления отображением полей модели
  4. Добавлять пользовательские поля в list_display и форматировать существующие
  5. Добавить ссылки на связанные объекты модели в list_display
  6. Возможность поиска и фильтров через search_fields и list_filter
  7. Работа с инлайнами модели для отношений N:1 и M:M
  8. Использование действий администратора Django и создание собственных действий
  9. Определять формы и шаблоны администратора Django
  10. Использование DjangoQL для расширенного поиска
  11. Импорт данных в различные форматы и экспорт данных в различные форматы с помощью django-import-export
  12. Изменение внешнего вида админки сайта с помощью django-admin-interface

Настройка проекта

Для демонстрации различных вариантов настройки сайта администратора я подготовил простое веб-приложение. Это веб-приложение служит системой продажи билетов на мероприятия. Оно позволяет управлять местами проведения мероприятий, концертами, категориями концертов и билетами.

У него следующая модель отношений между сущностями:

Tickets ERD Model

Я настоятельно рекомендую вам сначала ознакомиться с этим веб-приложением. После прочтения вы сможете применить полученные знания в своих Django-проектах.

Сначала возьмите исходный код из репозитория на GitHub:

$ git clone https://github.com/duplxey/django-admin-customization.git --branch base
$ cd django-admin-customization

Создайте виртуальную среду и активизируйте ее:

$ python3.11 -m venv env && source env/bin/activate

Установите требования и выполните миграцию базы данных:

(venv)$ pip install -r requirements.txt
(venv)$ python manage.py migrate

Создайте суперпользователя и заполните базу данных:

(venv)$ python manage.py createsuperuser
(venv)$ python manage.py populate_db

Запуск сервера разработки:

(venv)$ python manage.py runserver

Откройте ваш любимый веб-браузер и перейдите по адресу http://localhost:8000/admin. Попробуйте использовать учетные данные суперпользователя для доступа к сайту администратора Django. После этого убедитесь, что база данных заполнена несколькими площадками, категориями концертов, концертами и билетами.

Прежде чем продолжить, я предлагаю вам проверить модели в tickets/models.py. Обратите внимание на то, какие поля имеет модель и как модели связаны между собой.

Основная настройка сайта администратора

На сайте администратора Django предусмотрены некоторые базовые опции конфигурации. С их помощью можно изменить название сайта, заголовок, URL сайта и т.д. Настройка site_header может быть особенно удобна, если у вас есть несколько окружений и вы хотите легко различать их.

Настройки admin.site обычно изменяются в основном файле проекта urls.py.

Переименуйте администратора Django в "TicketPlus" и отметьте текущее окружение как dev:

# core/urls.py

admin.site.site_title = "TicketsPlus site admin (DEV)"
admin.site.site_header = "TicketsPlus administration"
admin.site.index_title = "Site administration"

Все настройки можно посмотреть, обратившись к файлу Django contrib/admin/sites.py.

Другое, что следует сделать, это изменить URL /admin по умолчанию. Это затруднит злоумышленникам поиск вашей административной панели.

Измените свой core/urls.py следующим образом:

# core/urls.py

urlpatterns = [
    path("secretadmin/", admin.site.urls),
]

Теперь ваш административный сайт должен быть доступен по адресу http://localhost:8000/secretadmin.

Django Model и Admin

Некоторые атрибуты модели Django напрямую влияют на работу администратора сайта Django. Наиболее важные:

  1. __str__() используется для определения отображаемого имени объекта
  2. Класс Meta используется для задания различных параметров метаданных (например, ordering и verbose_name)

Вот пример использования этих атрибутов на практике:

# tickets/models.py

class ConcertCategory(models.Model):
    name = models.CharField(max_length=64)
    description = models.TextField(max_length=256, blank=True, null=True)

    class Meta:
        verbose_name = "concert category"
        verbose_name_plural = "concert categories"
        ordering = ["-name"]

    def __str__(self):
        return f"{self.name}"
  1. Мы предоставили форму множественного числа, поскольку множественное число "категории концертов" не является "категориями концертов".
  2. Благодаря атрибуту ordering категории теперь упорядочены по названию.

Для получения информации обо всех опциях класса Meta обратитесь к разделу "Мета-опции модели".

Настройка сайта администратора с помощью класса ModelAdmin

В этом разделе мы рассмотрим, как использовать класс ModelAdmin для настройки администратора сайта.

Отображение списка управления

Атрибут list_display позволяет управлять тем, какие поля модели отображаются на странице списка моделей. Еще одним его достоинством является возможность отображения связанных полей модели с помощью оператора __.

Далее устанавливаем ConcertAdmin на list_display:

# tickets/admin.py

class ConcertAdmin(admin.ModelAdmin):
    list_display = ["name", "venue", "starts_at", "price", "tickets_left"]
    readonly_fields = ["tickets_left"]

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

Новый список выглядит отлично, но есть проблема. Добавив место проведения концерта в list_display, мы создали проблему N + 1. Поскольку Django необходимо получить название места проведения концерта для каждого концерта в отдельности, будет выполнено гораздо больше запросов.

Для того чтобы избежать проблемы N + 1, можно использовать атрибут list_select_related, который работает аналогично методу select_related:

# tickets/admin.py

class ConcertAdmin(admin.ModelAdmin):
    list_display = ["name", "venue", "starts_at", "price", "tickets_left"]
    list_select_related = ["venue"]
    readonly_fields = ["tickets_left"]

Чтобы узнать больше о производительности Django, а также о N + 1, ознакомьтесь с Django Performance Optimization Tips и Automating Performance Testing in Django.

Далее устанавливаем другие ModelAdmins' list_display для мест проведения и билетов:

# tickets/admin.py

class VenueAdmin(admin.ModelAdmin):
    list_display = ["name", "address", "capacity"]


class TicketAdmin(admin.ModelAdmin):
    list_display = [
        "customer_full_name", "concert",
        "payment_method", "paid_at", "is_active",
    ]
    list_select_related = ["concert", "concert__venue"]  # to avoid N + 1

Отображение пользовательских полей списка

Настройка list_display также может быть использована для добавления пользовательских полей. Для добавления пользовательского поля необходимо определить новый метод в классе ModelAdmin.

Добавить поле "Sold Out", которое будет True, если билетов нет:

# tickets/admin.py

class ConcertAdmin(admin.ModelAdmin):
    list_display = ["name", "venue", "starts_at", "tickets_left", "display_sold_out"]
    list_select_related = ["venue"]

    def display_sold_out(self, obj):
        return obj.tickets_left == 0

    display_sold_out.short_description = "Sold out"
    display_sold_out.boolean = True

Мы использовали short_description для задания имени колонки и boolean для указания Django, что эта колонка имеет булево значение. Таким образом, вместо True и False Django отображает значок галочки/крестика. Также нам пришлось добавить наш метод display_sold_out в list_display.

Далее добавим пользовательское поле с именем display_price:

# tickets/admin.py

class ConcertAdmin(admin.ModelAdmin):
    list_display = [
        "name", "venue", "starts_at", "tickets_left", "display_sold_out",  "display_price"
    ]
    # ...

    def display_price(self, obj):
        return f"${obj.price}"

    display_price.short_description = "Price"
    display_price.admin_order_field = "price"

Мы использовали admin_order_field, чтобы сообщить Django, по какому полю этот столбец является упорядочиваемым.

Иногда бывает полезно добавить ссылки на связанные объекты модели вместо того, чтобы просто показывать их отображаемое имя. Чтобы продемонстрировать, как это делается, приведем ссылки на места проведения концертов на странице списка концертов.

Прежде чем это сделать, рассмотрим структуру URL-адресов сайта администратора Django:

Page URL Description
List admin:<app>_<model>_changelist Displays the list of objects
Add admin:<app>_<model>_add Object add form
Change admin:<app>_<model>_change Object change form (requires objectId)
Delete admin:<app>_<model>_delete Object delete form (requires objectId)
History admin:<app>_<model>_history Displays object's history (requires objectId)

Для добавления ссылки на страницу изменения места проведения мероприятия нам необходимо использовать следующий URL:

Format: admin:<app>_<model>_change
Actual: admin:tickets_venue_change

Добавьте метод display_venue к ConcertAdmin следующим образом:

# tickets/admin.py

class ConcertAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
    list_display = [
        "name", "venue", "starts_at", "tickets_left",
        "display_sold_out",  "display_price", "display_venue",
    ]
    list_select_related = ["venue"]

    # ...

    def display_venue(self, obj):
        link = reverse("admin:tickets_venue_change", args=[obj.venue.id])
        return format_html('<a href="{}">{}</a>', link, obj.venue)

    display_venue.short_description = "Venue"

Для реверса URL мы использовали метод reverse и передали obj.venue.id в качестве objectId.

Не забывайте об импорте:

from django.urls import reverse
from django.utils.html import format_html

Дождитесь обновления сервера разработки и перейдите на страницу со списком концертов. Теперь места проведения концертов должны быть кликабельными.

Объекты модели фильтра

Админка Django позволяет легко фильтровать объекты. Чтобы включить фильтрацию, необходимо указать, какие поля или связанные с ними поля модели должны быть доступны для фильтрации. Самое интересное, что Django умеет складывать фильтры - например, фильтровать по двум и более полям одновременно.

Далее добавьте атрибут list_filter к ConcertAdmin следующим образом:

# tickets/admin.py

class ConcertAdmin(admin.ModelAdmin):
    # ...
    list_filter = ["venue"]

Для фильтрации по полям связанного объекта используйте оператор __.

При выборе фильтров следите за тем, чтобы не включать поля со слишком большим количеством значений. Например, tickets_left - неудачный выбор фильтра, поскольку на каждый концерт остается разное количество билетов.

Для более расширенной функциональности фильтрации можно также определить пользовательские фильтры. Для определения пользовательского фильтра необходимо указать опции или так называемые lookups и queryset для каждого lookup.

Например, для фильтрации по тому, продан концерт или нет, создайте SoldOutFilter и включите его в ConcertAdmin в list_filters:

# tickets/admin.py

class SoldOutFilter(SimpleListFilter):
    title = "Sold out"
    parameter_name = "sold_out"

    def lookups(self, request, model_admin):
        return [
            ("yes", "Yes"),
            ("no", "No"),
        ]

    def queryset(self, request, queryset):
        if self.value() == "yes":
            return queryset.filter(tickets_left=0)
        else:
            return queryset.exclude(tickets_left=0)


class ConcertAdmin(admin.ModelAdmin):
    # ...
    list_filter = ["venue", SoldOutFilter]

Не забудьте про импорт:

from django.contrib.admin import SimpleListFilter

Зайдите на свой административный сайт и убедитесь, что фильтры работают как положено.

Поиск объектов модели

Админка Django предоставляет базовую функциональность поиска. Его можно включить, указав, какие поля модели должны быть доступны для поиска с помощью атрибута search_fields. Следует помнить, что по умолчанию Django не поддерживает нечеткие запросы.

Давайте сделаем поиск концертов по их названиям, местам проведения и адресам заведений.

Добавьте атрибут search_fields к ConcertAdmin следующим образом:

# tickets/admin.py

class ConcertAdmin(admin.ModelAdmin):
    # ...
    search_fields = ["name", "venue__name", "venue__address"]

Подождите, пока сервер обновится и протестирует окно поиска.

Django Admin Filters

Модель рукоятки Inlines

Интерфейс администратора позволяет редактировать модели на той же странице, что и родительская модель, с помощью инлайнов. Django предоставляет два типа инлайнов StackedInline и TabularInline. Основное различие между ними заключается в том, как они выглядят.

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

Создайте ConcertInline и добавьте его к VenueAdmin в inlines следующим образом:

# tickets/admin.py

class ConcertInline(admin.TabularInline):
    model = Concert
    fields = ["name", "starts_at", "price", "tickets_left"]

    # optional: make the inline read-only
    readonly_fields = ["name", "starts_at", "price", "tickets_left"]
    can_delete = False
    max_num = 0
    extra = 0
    show_change_link = True


class VenueAdmin(admin.ModelAdmin):
    list_display = ["name", "address", "capacity"]
    inlines = [ConcertInline]

Зайдите на свой административный сайт и перейдите на страницу с информацией о каком-либо объекте. Прокрутите страницу вниз, и там должен появиться встроенный раздел "Концерты".

Django Admin Tabular Inline

Более подробную информацию об инлайнах и о том, как работать с отношениями "многие-ко-многим", можно найти в документах по администрированию сайта Django.

Пользовательские действия администратора

<<<Действия администратора

Django позволяют выполнить "действие" над объектом или группой объектов. С помощью действия можно изменить атрибуты объекта, удалить объект, скопировать его и т.д. Действия используются в основном для часто выполняемых "акций" или массовых изменений.

Прекрасным примером является активация или деактивация билета. Предположим, что у нас есть много билетов, которые мы хотим активировать. Было бы довольно утомительно щелкать по каждому из них, изменять его свойство is_active и сохранять модель. Вместо этого мы можем определить действие, которое будет делать именно это.

Определите действия activate_tickets и deactivate_tickets и добавьте их в TicketAdmin следующим образом:

# tickets/admin.py

@admin.action(description="Activate selected tickets")
def activate_tickets(modeladmin, request, queryset):
    queryset.update(is_active=True)


@admin.action(description="Deactivate selected tickets")
def deactivate_tickets(modeladmin, request, queryset):
    queryset.update(is_active=False)


class TicketAdmin(admin.ModelAdmin):
    # ...
    actions = [activate_tickets, deactivate_tickets]

Откройте еще раз страницу администратора, перейдите к представлению списка билетов, и вы должны увидеть пользовательские действия. Протестируйте их, активируя и деактивируя сразу несколько билетов.

Django Admin Custom Action

Более подробную информацию о действиях администратора Django можно найти в разделе Admin actions.

Override Django Admin Forms

По умолчанию Django автоматически генерирует ModelForm для вашей модели. Эта форма затем используется на странице добавления и изменения. Если вы хотите настроить форму или реализовать уникальную валидацию данных, вам придется переопределить форму.

Чтобы продемонстрировать это, разобьем customer_full_name на два поля ввода и выведем радиокнопки вместо выпадающего списка для способов оплаты.

Далее создайте файл forms.py в приложении tickets:

# tickets/forms.py

from django import forms
from django.forms import ModelForm, RadioSelect

from tickets.models import Ticket


class TicketAdminForm(ModelForm):
    first_name = forms.CharField(label="First name", max_length=32)
    last_name = forms.CharField(label="Last name", max_length=32)

    class Meta:
        model = Ticket
        fields = [
            "concert",
            "first_name",
            "last_name",
            "payment_method",
            "is_active"
        ]
        widgets = {
            "payment_method": RadioSelect(),
        }

    def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance')
        initial = {}

        if instance:
            customer_full_name_split = instance.customer_full_name.split(" ", maxsplit=1)
            initial = {
                "first_name": customer_full_name_split[0],
                "last_name": customer_full_name_split[1],
            }

        super().__init__(*args, **kwargs, initial=initial)

    def save(self, commit=True):
        self.instance.customer_full_name = self.cleaned_data["first_name"] + " " \
                                            + self.cleaned_data["last_name"]
        return super().save(commit)

Здесь:

  1. Мы добавили поля формы first_name и last_name.
  2. Мы использовали класс Meta, чтобы указать, к какой модели относится данная форма и какие поля включать.
  3. На форме __init__() мы заполнили форму, используя данные экземпляра модели.
  4. На форме save() мы объединили first_name и last_name и сохранили ее как customer_full_name.

Далее устанавливаем TicketAdmin в form следующим образом:

# tickets/admin.py

class TicketAdmin(admin.ModelAdmin):
    # ...
    form = TicketAdminForm

Не забудьте про импорт:

from tickets.forms import TicketAdminForm

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

Django Admin Override Form

Override Django Admin Templates

Администрирование сайта Django позволяет настраивать любой его визуальный аспект путем переопределения шаблонов. Для этого достаточно сделать следующее:

  1. Просмотрите исходный код Django и скопируйте оригинальный шаблон.
  2. Вставьте шаблон в "templates/admin" или "templates/registration", соответственно.
  3. Измените шаблон по своему вкусу.

В большинстве случаев можно обойтись лишь изменением части исходного шаблона.

Например, если мы хотим добавить сообщение над формой входа в систему, мы можем наследовать от login.html и затем изменить блок content_title:

<!-- templates/admin/login.html -->

{% extends "admin/login.html" %}

{% block content_title %}
    <p style="background: #ffffcc; padding: 10px 8px">
        This is a really important message.
    </p>
{% endblock %}

Перейдите на страницу входа в систему, и вы должны увидеть желтое сообщение.

Django Admin Override Template

Расширенный поиск с помощью DjangoQL

DjangoQL - это мощный сторонний пакет, позволяющий выполнять расширенные запросы, не опираясь на сырой SQL. Он имеет собственный синтаксис и автодополнение, поддерживает логические операторы и работает для любой модели Django.

Начните с установки пакета:

(env)$ pip install djangoql==0.17.1

Добавить в INSTALLED_APPS в core/settings.py:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "djangoql",
]

Далее добавьте DjangoQLSearchMixin в качестве родительского класса ко всем ModelAdmin, в которых необходимо включить расширенные возможности поиска.

Добавим его в TicketAdmin, например:

# tickets/admin.py

class TicketAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
    # ...

Не забудьте про импорт:

from djangoql.admin import DjangoQLSearchMixin

Теперь для выполнения расширенных запросов можно использовать ту же поисковую строку, что и раньше. Примеры:

  1. is_active = True возвращает активные билеты
  2. payment_method = "ET" or payment_method = "BC" возвращает билеты, купленные за криптовалюту
  3. concert.venue.name ~ "Amphitheatre" возвращает билеты на концерты в амфитеатрах
  4. concert.tickets_left > 500 возвращает билеты на концерты, на которые осталось более 500 билетов

Для получения дополнительной информации о языке DjangoQL ознакомьтесь с DjangoQL language reference.

Импорт и экспорт данных с помощью Django Import Export

В этом разделе мы рассмотрим, как импортировать и экспортировать данные об объектах с помощью django-import-export, который представляет собой отличный пакет для удобного импорта и экспорта данных в различных форматах, включая JSON, CSV и YAML. Пакет также поставляется со встроенной интеграцией с администратором.

Сначала установите его:

(env)$ pip install django-import-export==3.2.0

Далее добавьте его в INSTALLED_APPS в core/settings.py:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "import_export",
]

Соберите статические файлы:

(env)$ python manage.py collectstatic

После этого добавьте ImportExportActionModelAdmin в качестве родительского класса ко всем ModelAdmin, которые вы хотите сделать импортируемыми/экспортируемыми.

Вот пример для TicketAdmin:

# tickets/admin.py

class TicketAdmin(DjangoQLSearchMixin, ImportExportActionModelAdmin):
    # ...

Нам пришлось удалить базовый класс admin.ModelAdmin, поскольку ImportExportActionModelAdmin уже наследует от него. Включение обоих классов привело бы к ошибке TypeError.

Не забудьте про импорт:

from import_export.admin import ImportExportActionModelAdmin

Если вы хотите, чтобы модель была только экспортируемой, используйте ExportActionModelAdmin.

Если вы перейдете на страницу своего тикета, то увидите, что действие экспорта было добавлено. Протестируйте его, выбрав несколько билетов и желаемый формат. Затем нажмите кнопку "Go".

Затем можно протестировать функциональность импорта, импортировав только что экспортированный файл.

Django Import / Export Admin

Сайт администратора с интерфейсом администратора Django

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

Более эффективным подходом к стилизации сайта администратора является использование пакета django-admin-interface. Этот пакет содержит красивые готовые темы интерфейса администратора и позволяет легко настраивать различные аспекты сайта администратора, включая изменение цветов, заголовка, favicon, логотипа и т.д.

Начните с установки через pip:

(env)$ pip install django-admin-interface==0.26.0

Далее добавляем admin_interface и colorfield к INSTALLED_APPS перед django.contrib.admin:

# core/settings.py

INSTALLED_APPS = [
    #...
    "admin_interface",
    "colorfield",
    #...
    "django.contrib.admin",
    #...
]

X_FRAME_OPTIONS = "SAMEORIGIN"              # allows you to use modals insated of popups
SILENCED_SYSTEM_CHECKS = ["security.W019"]  # ignores redundant warning messages

Миграция базы данных:

(env)$ python manage.py migrate

Собирать статические файлы:

(env)$ python manage.py collectstatic --clear

Запустите сервер разработки и перейдите по адресу http://localhost:8000/secretadmin. Вы заметите, что ваш сайт администрирования Django выглядит более современно, и на нем появится раздел "Admin Interface".

Django Admin Interface Default Theme

Нажмите кнопку "Admin Interface > Themes", чтобы увидеть все установленные на данный момент темы. По умолчанию должна быть только одна тема под названием "Django". При желании можно установить еще три темы с помощью фиксов:

(env)$ python manage.py loaddata admin_interface_theme_bootstrap.json
(env)$ python manage.py loaddata admin_interface_theme_foundation.json
(env)$ python manage.py loaddata admin_interface_theme_uswds.json

Нажав на существующую тему, можно настроить все перечисленные ранее аспекты.

Django Admin Interface Theme Customization

Заключение

В этой статье мы рассмотрели многие концепции настройки администратора Django. Теперь вы должны уметь применять эти концепции на практике и адаптировать админку Django под нужды вашего проекта.

Вернуться на верх