• 3.2
  • 5.0
  • 6.1
  • Версия документации: 3.1

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

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

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

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

Примечание

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

for obj in queryset:
    do_something_with(obj)

That’s actually all there is to writing an action! However, we’ll take one more optional-but-useful step and give the action a «nice» title in the admin. By default, this action would appear in the action list as «Make published» – the function name, with underscores replaced by spaces. That’s fine, but we can provide a better, more human-friendly name by giving the make_published function a short_description attribute:

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

Примечание

This might look familiar; the admin’s list_display option uses the same technique to provide human-readable descriptions for callback functions registered there, too.

Добавление действий в класс 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().

Например, вы можете использовать данный метод для удаления встроенного действия «delete selected objects»:

admin.site.disable_action('delete_selected')

После этого действие больше не будет доступно глобально.

If, however, you need to re-enable a globally-disabled action for one particular model, list it explicitly in your ModelAdmin.actions list:

# 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).

Например, вы можете использовать данный метод для удаления встроенного действия «delete selected objects»:

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

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

Actions may limit their availability to users with specific permissions by setting an allowed_permissions attribute on the action function:

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

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

If allowed_permissions has more than one permission, the action will be available as long as the user passes at least one of the checks.

Available values for allowed_permissions and the corresponding method checks are:

Вы можете указать любое другое значение, если вы реализуете соответствующий метод 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))
Back to Top