Spec-Zone.ru › Django 1.8

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

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

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

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

../../../_images/user_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):              # __unicode__ on Python 2
        return self.title

Распространённая задача, которую мы можем выполнить с такой моделью, — это изменить статус статьи с «черновик» на «опубликовано». Мы могли бы легко сделать это в админ-панели по одной статье за раз, но если бы мы хотели пакетно опубликовать группу статей, это было бы утомительно. Поэтому давайте напишем действие, которое позволит нам изменить статус статьи на «опубликовано».

Написание функций действий

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

  • Текущий ModelAdmin
  • Объект HttpRequest, представляющий текущий запрос,
  • Объект QuerySet, содержащий набор объектов, выбранных пользователем.

Нашей функции «опубликовать статьи» не потребуются объекты 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/article_actions.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/article_actions_message.png

Действия, предоставляющие промежуточные страницы

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

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

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

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 import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect

def export_selected_objects(modeladmin, request, queryset):
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    ct = ContentType.objects.get_for_model(queryset.model)
    return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(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(MyModelAdmin, self).get_actions(request)
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']
        return actions

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

Spec-Zone.ru

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