Действия администратора¶
Основной рабочий процесс администратора Django в двух словах звучит так: «выберите объект, затем измените его». Это хорошо подходит для большинства случаев использования. Однако, если вам нужно внести одно и то же изменение во множество объектов одновременно, этот рабочий процесс может быть довольно утомительным.
В этих случаях админка Django позволяет писать и регистрировать «действия» - функции, которые вызываются с помощью списка объектов, выбранных на странице списка изменений.
Если вы посмотрите на любой список изменений в админке, вы увидите эту возможность в действии; Django поставляется с действием «удалить выбранные объекты», доступным для всех моделей. Например, вот модуль пользователя из встроенного в Django приложения django.contrib.auth
:
Предупреждение
Действие «удалить выбранные объекты» использует QuerySet.delete()
из соображений эффективности, что имеет важную оговорку: метод вашей модели delete()
не будет вызван.
Если вы хотите отменить это поведение, вы можете переопределить ModelAdmin.delete_queryset()
или написать пользовательское действие, которое будет выполнять удаление выбранным вами способом - например, вызывая Model.delete()
для каждого из выбранных элементов.
Более подробную информацию о массовом удалении см. в документации по object deletion.
Читайте дальше, чтобы узнать, как добавить свои собственные действия в этот список.
Действия при написании¶
Проще всего объяснить действия на примере, поэтому давайте погрузимся в процесс.
Частым случаем использования действий администратора является массовое обновление модели. Представьте себе новостное приложение с моделью 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
или объект request, но мы будем использовать queryset:
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
Примечание
Для лучшей производительности мы используем update method для queryset. В других типах действий может потребоваться работать с каждым объектом отдельно; в этих случаях мы будем выполнять итерации по набору queryset:
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()
, чтобы предоставить человекочитаемые описания для функций обратного вызова, зарегистрированных там же.
Аргумент 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)
Этот код даст нам список изменений администратора, который будет выглядеть примерно так:
Вот, собственно, и все! Если вам не терпится написать свои собственные действия, вы теперь знаете достаточно, чтобы начать. Остальная часть этого документа посвящена более продвинутым техникам.
Обработка ошибок в действиях¶
Если во время выполнения вашего действия могут возникнуть предсказуемые ошибки, вы должны изящно проинформировать пользователя о проблеме. Это означает обработку исключений и использование 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
(или подкласс) из вашего действия. Например, вы можете написать функцию экспорта, которая использует serialization functions от 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». Вы можете явно дать действию имя - хорошо, если вы позже захотите программно remove the action> - передав второй аргумент вAdminSite.add_action()
:admin.site.add_action(export_selected_objects, 'export_selected')
Отключение действий¶
Иногда вам нужно отключить определенные действия - особенно те, которые registered site-wide - для определенных объектов. Есть несколько способов отключить действия:
Отключение действия в масштабах всего сайта¶
-
AdminSite.
disable_action
(name)[исходный код]¶ Если вам нужно отключить site-wide 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
не отображать и не разрешать никаких действий, включая любые site-wide actions.
Условное включение или отключение действий¶
-
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))
Аргумент permissions
в декораторе action()
эквивалентен установке атрибута allowed_permissions
непосредственно в функции действия в предыдущих версиях. Установка атрибута напрямую по-прежнему поддерживается для обратной совместимости.
Декоратор action
¶
-
action
(*, permissions=None, description=None)[исходный код]¶ - New in 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)
В этом случае он не будет добавлять никаких атрибутов к функции.