Spec-Zone.ru › Django 2.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)

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

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

Примечание

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

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

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

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

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

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']

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = "Mark selected stories as published"

Обратите внимание, во-первых, что мы переместили make_published в метод и переименовали параметр modeladmin в self, а во-вторых, что мы теперь поместили строку 'make_published' в actions вместо прямой ссылки на функцию. Это говорит ModelAdmin найти действие в качестве метода.

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

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

class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status='p')
        if rows_updated == 1:
            message_bit = "1 story was"
        else:
            message_bit = "%s stories were" % rows_updated
        self.message_user(request, "%s successfully marked as published." % message_bit)

Это согласует действие с тем, что делает сама админ-панель после успешного выполнения действия:

../../../_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

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

Новое в Django 2.1.

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

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.allowed_permissions = ('change',)

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

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

Доступные значения для allowed_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']

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.allowed_permissions = ('publish',)

    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 Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.2/ref/contrib/admin/actions/

Spec-Zone.ru

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