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

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

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

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

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

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

from django.contrib import admin

...

@admin.action(description='Mark selected stories as published')
def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

Примечание

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

Изменено в Django 3.2:

descriptionАргумент action() декоратора эквивалентен установка short_descriptionатрибута функции действия непосредственно в предыдущих версиях. Прямая установка атрибута по-прежнему поддерживается для обратной совместимости.

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

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

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

@admin.action(description='Mark selected stories as published')
def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

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

    @admin.action(description='Mark selected stories as published')
    def make_published(self, request, queryset):
        queryset.update(status='p')

Обратите внимание , что первыми мы перешли 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( действие , имя = Нет )

Некоторые действия лучше, если они доступны для любого объекта на сайте администратора - действие экспорта, определенное выше, было бы хорошим кандидатом. Вы можете сделать действие глобально доступным, используя 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( имя )

Если вам нужно отключить действие на уровне сайта, вы можете позвонить 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( запрос )

Наконец, вы можете условно включить или отключить действия для каждого запроса (и, следовательно, для каждого пользователя), переопределив 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

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

Действия могут ограничить их доступность для пользователей с определенными разрешениями, заключив функцию действия в action() декоратор и передав permissionsаргумент:

@admin.action(permissions=['change'])
def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

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

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

Доступные значения permissionsи соответствующие проверки методов:

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

    @admin.action(permissions=['publish'])
    def make_published(self, request, queryset):
        queryset.update(status='p')

    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 3.2:

permissionsАргумент action() декоратора эквивалентен установка allowed_permissionsатрибута функции действия непосредственно в предыдущих версиях. Прямая установка атрибута по-прежнему поддерживается для обратной совместимости.

actionДекоратор

action( * , разрешения = Нет , описание = Нет )
Новое в Django 3.2.

Этот декоратор можно использовать для установки определенных атрибутов в функциях настраиваемых действий, которые можно использовать с actions:

@admin.action(
    permissions=['publish'],
    description='Mark selected stories as published',
)
def make_published(self, request, queryset):
    queryset.update(status='p')

Это эквивалентно установке некоторых атрибутов (с оригинальными более длинными именами) непосредственно в функции:

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

Использование этого декоратора не обязательно для создания функции действия, но может быть полезно использовать его без аргументов в качестве маркера в исходном коде для определения цели функции:

@admin.action
def make_inactive(self, request, queryset):
    queryset.update(is_active=False)

В этом случае он не будет добавлять атрибутов к функции.

Copyright ©2021 All rights reserved