• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

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

Повседневный алгоритм работы с административным интерфейсом Django выглядит как «выделить объект, затем изменить его.» Он подходит для большинства случаев. Тем не менее, когда потребуется выполнить одно и то же действие над множеством объектов, то такое поведение интерфейса начинает напрягать.

В таких случаях административный интерфейс Django позволяет вам создать и зарегистрировать «действия» – простые функции, которые вызываются для выполнения неких действий над списком объектов, выделенных на странице интерфейса.

Если вы взгляните на любой список изменений на интерфейсе администратора, вы увидите эту возможность в действии. Django поставляется с действием «удалить выделенные объекты», которое доступно для всех моделей. Например, рассмотрим пользовательский модуль из встроенного в Django приложения django.contrib.auth:

../../../../_images/admin-actions.png

Предупреждение

Действие «удалить выделенные объекты» использует метод QuerySet.delete() по соображениям эффективности, который имеет важный недостаток: метод delete() вашей модели не будет вызван.

Если вам потребуется изменить такое поведение, то просто напишите собственное действие, которое выполняет удаление в необходимой вам манере, например, вызывая Model.delete() для каждого выделенного элемента.

Подробности по пакетному удалению смотрите в документации по удалению объектов.

Читайте дальше о том, как создавать собственные действия для списка объектов.

Создание действий

Простейшим способом понять работу действий является их изучение на примерах. Значит, пришло время изучить их внимательнее.

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

from django.db import models

STATUS_CHOICES = {
    "d": "Draft",
    "p": "Published",
    "w": "Withdrawn",
}


class Article(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

    def __str__(self):
        return self.title

Стандартной задачей, которую мы возможно будем выполнять с подобной моделью, будет изменение состояний статьи с «черновик» на «опубликовано». Мы легко сможем выполнить это действие в интерфейсе администратора для одной статьи за раз, но если потребуется выполнить массовую публикацию группы статей, то вы столкнётесь с нудной работой. Таким образом, следует написать действие, которое позволит нам изменять состояние статьи на «опубликовано.»

Создание функций для действий

Сначала нам потребуется написать функцию, которая вызывается при выполнении действия в интерфейсе администратора. Функции действий - это обычные функции, которые принимают три аргумента:

  • Экземпляр класса ModelAdmin,

  • Экземпляр класса HttpRequest, представляющий текущий запрос,

  • Экземпляр класса QuerySet, содержащий набор объектов, которые выделил пользователь.

Наша функция «опубликовать-эти-статьи» не нуждается в экземпляре ModelAdmin или в объекте реквеста, но использует выборку:

def make_published(modeladmin, request, queryset):
    queryset.update(status="p")

Примечание

В целях улучшения производительности, мы используем метод выборки update method. Другие типы действий могут обрабатывать каждый объект индивидуально. В таких случаях мы просто выполняем итерацию по выборке:

for obj in queryset:
    do_something_with(obj)

Вот, собственно, и все, что нужно для написания действия! Однако мы сделаем еще один необязательный, но полезный шаг и дадим действию «красивый» заголовок в админке. По умолчанию это действие будет отображаться в списке действий как «Опубликовать» — имя функции с заменой подчеркиваний на пробелы. Это нормально, но мы можем предоставить лучшее, более удобное для человека имя, используя декоратор action() в функции make_published:

from django.contrib import admin

...


@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
    queryset.update(status="p")

Примечание

Это может показаться знакомым; опция администратора list_display использует аналогичную технику с декоратором display(), чтобы предоставить удобочитаемые описания для зарегистрированных там функций обратного вызова.

Добавление действий в класс ModelAdmin

Затем мы должны проинформировать наш класс ModelAdmin о новом действии. Это действие аналогично применению любой другой опции конфигурации. Таким образом, полный пример admin.py с определением действия и его регистрации будет выглядеть так:

from django.contrib import admin
from myapp.models import Article


@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
    queryset.update(status="p")


class ArticleAdmin(admin.ModelAdmin):
    list_display = ["title", "status"]
    ordering = ["title"]
    actions = [make_published]


admin.site.register(Article, ArticleAdmin)

Этот код предоставит нам список моделей в интерфейсе администратора, который выгладит примерно так:

../../../../_images/adding-actions-to-the-modeladmin.png

Вот и всё! Если вам хочется поскорее создать свои действия, приступайте, у вас есть необходимые знания. Остальная часть документа просто описывает более продвинутые вещи.

Обработка ошибок в действиях

При наличии предполагаемых условий возникновения ошибки, которая может возникнуть во время работы вашего действия, вы должны аккуратно проинформировать пользователя о проблеме. Это подразумевает обработку исключений и использование метода django.contrib.admin.ModelAdmin.message_user() для отображения описания проблемы в отклике.

Продвинутые методики работы с действиями

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

Действия как методы ModelAdmin

Вышеприведённый пример показывает действие make_published, определённое в виде обычной функции. Это нормальный подход, но к нему есть претензии с точки зрения дизайна кода: так как действия связано с объектом Article, то правильнее будет внедрить это действие в сам объект ArticleAdmin.

Вы можете сделать это так:

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ["make_published"]

    @admin.action(description="Mark selected stories as published")
    def make_published(self, request, queryset):
        queryset.update(status="p")

Следует отметить, что сначала мы переместили make_published в метод и переименовали параметр modeladmin в self, а затем поместили строку make_published в атрибут actions вместо прямой ссылки на функцию. Всё это указывает классу ModelAdmin искать действие среди своих методов.

Определение действий в виде методов предоставляет действиям более прямолинейный, идеоматический доступ к самому объекту ModelAdmin, позволяя вызывать любой метод, предоставляемый интерфейсом администратора.

Например, мы можем использовать self, чтобы показать пользователю сообщение о том, что действие было успешным:

from django.contrib import messages
from django.utils.translation import ngettext


class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        updated = queryset.update(status="p")
        self.message_user(
            request,
            ngettext(
                "%d story was successfully marked as published.",
                "%d stories were successfully marked as published.",
                updated,
            )
            % updated,
            messages.SUCCESS,
        )

Это обеспечивает действие функционалом, аналогичным встроенным возможностям интерфейса администратора:

../../../../_images/actions-as-modeladmin-methods.png

Действия, у которых есть промежуточные страницы

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

Чтобы предоставить промежуточную страницу, верните HttpResponse (или подкласс) из вашего действия. Например, вы можете написать функцию экспорта, которая использует функции сериализации Django для вывода некоторых выбранных объектов в формате JSON:

from django.core import serializers
from django.http import HttpResponse


def export_as_json(modeladmin, request, queryset):
    response = HttpResponse(content_type="application/json")
    serializers.serialize("json", queryset, stream=response)
    return response

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

from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect


def export_selected_objects(modeladmin, request, queryset):
    selected = queryset.values_list("pk", flat=True)
    ct = ContentType.objects.get_for_model(queryset.model)
    return HttpResponseRedirect(
        "/export/?ct=%s&ids=%s"
        % (
            ct.pk,
            ",".join(str(pk) for pk in selected),
        )
    )

Как вы можете видеть, код самого действия очень прост. Вся сложная логика будет располагаться в вашем представлении экспорта. Здесь потребуется работать с объектами любого типа, следовательно тут надо работать через ContentType.

Написание самого представления оставлено читателю в качестве домашнего задания.

Делаем действия видимыми всему сайту

AdminSite.add_action(action, name=None)

Некоторые действия лучше всего делать, если они доступны любому объекту на сайте администратора — действие экспорта, определенное выше, будет хорошим кандидатом. Вы можете сделать действие глобально доступным, используя AdminSite.add_action(). Например:

from django.contrib import admin

admin.site.add_action(export_selected_objects)

Это делает действие «export_selected_objects» глобально доступным как действие с именем «export_selected_objects». Вы можете явно указать имя действия — хорошо, если позже вы захотите программно удалить действие <disabling-admin-actions>` — передав второй аргумент в AdminSite.add_action():

admin.site.add_action(export_selected_objects, "export_selected")

Отключение действий

Иногда требуется отключать определённые действия, особенно зарегистрированные глобально, для определённых объектов. Существует несколько способов для этого:

Отключение глобального действия

AdminSite.disable_action(name)

Если вам нужно отключить действие <adminsite-actions> для всего сайта, вы можете вызвать AdminSite.disable_action().

Например, вы можете использовать данный метод для удаления встроенного действия «delete selected objects»:

admin.site.disable_action("delete_selected")

После этого действие больше не будет доступно глобально.

Однако если вам необходимо повторно включить глобально отключенное действие для одной конкретной модели, явно укажите его в списке ModelAdmin.actions:

# Globally disable delete selected
admin.site.disable_action("delete_selected")


# This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin):
    actions = ["some_other_action"]
    ...


# This one will
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ["delete_selected", "a_third_action"]
    ...

Отключение всех действия для определённого экземпляра ModelAdmin

Если вам требуется запретить пакетные действия для определённого экземпляра ModelAdmin, просто установите атрибут ModelAdmin.actions в None:

class MyModelAdmin(admin.ModelAdmin):
    actions = None

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

Условное включение и отключение действий

ModelAdmin.get_actions(request)

Наконец, вы можете включать или отключать действия по некоему условию на уровне запроса (и, следовательно, на уровне каждого пользователя), просто переопределив метод ModelAdmin.get_actions().

Он возвращает словарь разрешённых действий. Ключами являются имена действий, а значениями являются кортежи вида (function, name, short_description).

Например, вы можете использовать данный метод для удаления встроенного действия «delete selected objects»:

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request):
        actions = super().get_actions(request)
        if request.user.username[0].upper() != "J":
            if "delete_selected" in actions:
                del actions["delete_selected"]
        return actions

Установка разрешений на действия

Действия могут ограничить свою доступность для пользователей с определенными разрешениями, обернув функцию действия декоратором action() и передав аргумент permissions:

@admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
    queryset.update(status="p")

Действие make_published() будет доступно только пользователям, прошедшим проверку ModelAdmin.has_change_permission().

Если permissions имеет более одного разрешения, действие будет доступно до тех пор, пока пользователь пройдет хотя бы одну из проверок.

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

Вы можете указать любое другое значение, если вы реализуете соответствующий метод has_<value>_permission(self, request)`` в ModelAdmin.

Например:

from django.contrib import admin
from django.contrib.auth import get_permission_codename


class ArticleAdmin(admin.ModelAdmin):
    actions = ["make_published"]

    @admin.action(permissions=["publish"])
    def make_published(self, request, queryset):
        queryset.update(status="p")

    def has_publish_permission(self, request):
        """Does the user have the publish permission?"""
        opts = self.opts
        codename = get_permission_codename("publish", opts)
        return request.user.has_perm("%s.%s" % (opts.app_label, codename))

Декоратор action

action(*, permissions=None, description=None)

Этот декоратор можно использовать для установки определенных атрибутов в функциях пользовательских действий, которые можно использовать с actions:

@admin.action(
    permissions=["publish"],
    description="Mark selected stories as published",
)
def make_published(self, request, queryset):
    queryset.update(status="p")

Это эквивалентно установке некоторых атрибутов (с оригинальными, более длинными именами) непосредственно в функцию:

def make_published(self, request, queryset):
    queryset.update(status="p")


make_published.allowed_permissions = ["publish"]
make_published.short_description = "Mark selected stories as published"

Использование этого декоратора не является обязательным для создания функции действия, но может быть полезно использовать его без аргументов в качестве маркера в исходном коде для определения цели функции:

@admin.action
def make_inactive(self, request, queryset):
    queryset.update(is_active=False)

В этом случае к функции не будут добавлены атрибуты.

Описания действий имеют формат % и могут содержать заполнители '%(verbose_name)s'` и ``'%(verbose_name_plural)s', которые заменяются соответственно на модели verbose_name и verbose_name_plural.

Back to Top