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