Spec-Zone.ru › Django 3.2

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

Основной рабочий процесс административной панели 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')

Примечание

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

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

Изменено в Django 3.2:

Аргумент description декоратора action() эквивалентен установке атрибута short_description на функции действия напрямую в предыдущих версиях. Напрямую установка атрибута по-прежнему поддерживается для обратной совместимости.

Добавление действий в 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». Вы можете явно указать имя действия — это полезно, если вы хотите впоследствии программно удалить действие — передав второй аргумент в AdminSite.add_action():

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

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

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

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

AdminSite.disable_action(name)

Если вам нужно отключить действие на уровне сайта, вы можете вызвать 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)

Наконец, вы можете условно включать или отключать действия на основе каждого запроса (и, следовательно, на основе каждого пользователя), переопределяя 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))
Изменено в Django 3.2:

Аргумент permissions декоратора action() эквивалентен установке атрибута allowed_permissions на функцию действия напрямую в предыдущих версиях. Настройка атрибута напрямую всё ещё поддерживается для обратной совместимости.

Декоратор action

action(*, permissions=None, description=None)
Добавлена в Django 3.2.

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

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

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

Spec-Zone.ru

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