Spec-Zone.ru › Django 5.1

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

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

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

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

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

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

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

Если вы хотите переопределить это поведение, вы можете переопределить ModelAdmin.delete_queryset() или написать пользовательское действие, которое выполняет удаление по вашему желанию — например, вызвав 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, содержащий набор объектов, выбранных пользователем.

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

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

Примечание

Для лучшей производительности мы используем метод обновления набора объектов. Другие типы действий могут потребовать обработки каждого объекта индивидуально; в этих случаях мы будем итерироваться по набору объектов:

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) [source]

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

from django.contrib import admin

admin.site.add_action(export_selected_objects)

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

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

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

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

Отключение действия на уровне сайта

AdminSite.disable_action(name) [source]

Если вам нужно отключить действие на уровне сайта, вы можете вызвать AdminSite.disable_action().

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

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) [source]

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

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

Например, если вы хотите, чтобы пользователи, чьи имена начинаются с «J», могли удалять объекты оптом:

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 более одного разрешения, действие будет доступно, если пользователь пройдёт хотя бы одну из проверок.

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

  • 'add': ModelAdmin.has_add_permission()
  • 'change': ModelAdmin.has_change_permission()
  • 'delete': ModelAdmin.has_delete_permission()
  • 'view': ModelAdmin.has_view_permission()

Вы можете указать любое другое значение, если реализуете соответствующий метод 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) [source]

Этот дескриптор может быть использован для установки специфических атрибутов на пользовательские функции действий, которые могут быть использованы с 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 модели.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/ref/contrib/admin/actions/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API