Spec-Zone.ru › Django 3.0

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

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

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

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

Примечание

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

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, чтобы отобразить пользователю сообщение о том, что действие выполнено успешно:

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

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

Действия могут ограничить свою доступность для пользователей со специфическими разрешениями, установив атрибут 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/3.0/ref/contrib/admin/actions/

Spec-Zone.ru

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