Действия администратора
Основной рабочий процесс админ-панели 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")
Примечание
Для достижения наилучшей производительности мы используем метод обновления набора. Другие типы действий могут потребовать обработки каждого объекта индивидуально; в этих случаях мы бы перебирали набор:
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() для предоставления удобочитаемых описаний для функций обратного вызова, зарегистрированных там тоже.
Добавление действий в 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)
Этот код даст нам страницу изменений админ-панели, которая будет выглядеть примерно так:
На самом деле, это всё, что нужно! Если вы хотите написать свои собственные действия, теперь вы знаете достаточно, чтобы начать. Остальная часть документа описывает более продвинутые методы.
Обработка ошибок в действиях
Если есть предсказуемые условия возникновения ошибок во время выполнения вашего действия, вы должны вежливо сообщить пользователю о проблеме. Это означает обработку исключений и использование 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,
)
Это позволяет действию соответствовать тому, что делает сама админка после успешного выполнения действия:
Действия, предоставляющие промежуточные страницы
По умолчанию после выполнения действия пользователь перенаправляется обратно на исходную страницу списка изменений. Однако некоторым действиям, особенно более сложным, потребуются промежуточные страницы. Например, встроенное действие удаления запрашивает подтверждение перед удалением выбранных объектов.
Для предоставления промежуточной страницы верните объект 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)[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
Установка разрешений для действий
Действия могут ограничивать свой доступ для пользователей со специфическими разрешениями, обернув функцию действия декоратором action() и передав аргумент permissions:
@admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
queryset.update(status="p")
Действие make_published() будет доступно только тем пользователям, которые пройдут проверку ModelAdmin.has_change_permission().
Если у permissions более одного разрешения, действие будет доступно, если пользователь пройдёт хотя бы одну из проверок.
Доступные значения для 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"]
@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))
Декоратор action
-
action(*, permissions=None, description=None)[source] -
Этот декоратор можно использовать для задания определённых атрибутов для пользовательских функций действий, которые могут быть использованы с
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)В этом случае он не добавит никаких атрибутов к функции.
Описание действия форматируется с использованием %-формата и может содержать
'%(verbose_name)s'и'%(verbose_name_plural)s'заполнитель, которые заменяются, соответственно, модельюverbose_nameиverbose_name_plural.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/ref/contrib/admin/actions/