Spec-Zone.ru › Django 2.0

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

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

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

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

../../../_images/admin-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):
        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 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().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/2.0/ref/contrib/admin/actions/

Spec-Zone.ru

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