Действия администратора
Основной рабочий процесс админ-панели Django, по сути, сводится к «выбору объекта, а затем его изменению». Это хорошо работает для большинства случаев использования. Однако, если вам нужно внести одно и то же изменение во множество объектов сразу, этот рабочий процесс может быть довольно утомительным.
В таких случаях админ-панель Django позволяет создавать и регистрировать «действия» — функции, которые вызываются со списком выбранных объектов на странице изменения.
Если вы посмотрите на любую страницу списка изменений в админ-панели, вы увидите эту возможность в действии; Django поставляется с действием «удалить выбранные объекты», доступным для всех моделей. Например, вот модуль пользователей из встроенного приложения Django django.contrib.auth:
Предупреждение
Действие «удалить выбранные объекты» использует 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')
Примечание
Для лучшей производительности мы используем метод 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)
Этот код даст нам страницу списка изменений админ-панели, которая будет выглядеть примерно так:
Вот и все! Если вы хотите написать свои собственные действия, теперь вы знаете достаточно, чтобы начать. Остальная часть этого документа охватывает более продвинутые методы.
Обработка ошибок в действиях
Если есть предсказуемые условия возникновения ошибок при выполнении вашего действия, вы должны вежливо сообщить пользователю о проблеме. Это означает обработку исключений и использование 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)
Это делает действие соответствующим тому, что делает сам администратор после успешного выполнения действия:
Действия, предоставляющие промежуточные страницы
По умолчанию после выполнения действия пользователь перенаправляется обратно на исходную страницу списка изменений. Однако некоторые действия, особенно более сложные, потребуют возвращения промежуточных страниц. Например, встроенное действие удаления запрашивает подтверждение перед удалением выбранных объектов.
Для предоставления промежуточной страницы верните 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.1/ref/contrib/admin/actions/