Действия администратора
Основной рабочий процесс администратора 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, содержащий набор объектов, выбранных пользователем.
Нашей функции 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)
Этот код предоставит нам список изменений администратора, который будет примерно таким:
Это все, что нужно! Если вы хотите написать свои собственные действия, теперь у вас достаточно знаний для начала. Остальная часть документа только охватывает более продвинутые техники.
Обработка ошибок в действиях
Если есть предсказуемые условия возникновения ошибок во время выполнения вашего действия, вы должны вежливо проинформировать пользователя о проблеме. Это означает обработку исключений и использование 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)
Это делает действие совместимым с тем, что делает сам администратор после успешного выполнения действия:
Действия, предоставляющие промежуточные страницы
По умолчанию после выполнения действия пользователь просто перенаправляется обратно на исходную страницу списка изменений. Однако некоторым действиям, особенно более сложным, потребуется возвращать промежуточные страницы. Например, встроенное действие удаления запрашивает подтверждение перед удалением выбранных объектов.
Чтобы предоставить промежуточную страницу, просто верните объект 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
Установка разрешений для действий
Действия могут ограничить свою доступность для пользователей с определёнными разрешениями, установив атрибут 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/2.1/ref/contrib/admin/actions/