Действия администратора¶
Основной рабочий процесс администратора 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)
That’s actually all there is to writing an action! However, we’ll take one
more optional-but-useful step and give the action a «nice» title in the admin.
By default, this action would appear in the action list as «Make published» –
the function name, with underscores replaced by spaces. That’s fine, but we
can provide a better, more human-friendly name by using the
action()
decorator on the make_published
function:
from django.contrib import admin
...
@admin.action(description='Mark selected stories as published')
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
Примечание
This might look familiar; the admin’s
list_display
option uses a similar
technique with the display()
decorator to
provide human-readable descriptions for callback functions registered
there, too.
Добавление действий в 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
, позволяя действию вызывать любой из методов, предоставляемых администратором.
For example, we can use self
to flash a message to the user informing them
that the action was successful:
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)
Это делает действие соответствующим тому, что делает сам администратор после успешного выполнения действия:
Действия, предоставляющие промежуточные страницы¶
По умолчанию после выполнения действия пользователь перенаправляется обратно на исходную страницу списка изменений. Однако некоторые действия, особенно более сложные, должны возвращать промежуточные страницы. Например, встроенное действие удаления запрашивает подтверждение перед удалением выбранных объектов.
To provide an intermediary page, return an HttpResponse
(or subclass) from your action. For example, you might write an export function
that uses Django’s serialization functions to
dump some selected objects as 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')
После выполнения вышеуказанных действий это действие больше не будет доступно на всем сайте.
If, however, you need to reenable a globally-disabled action for one particular model, list it explicitly in your
ModelAdmin.actions
list:# 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
Установка разрешений для действий¶
Actions may limit their availability to users with specific permissions by
wrapping the action function with the action()
decorator and passing the permissions
argument:
@admin.action(permissions=['change'])
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
Действие make_published()
будет доступно только тем пользователям, которые прошли проверку ModelAdmin.has_change_permission()
.
If permissions
has more than one permission, the action will be available
as long as the user passes at least one of the checks.
Available values for permissions
and the corresponding method checks are:
'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)
В этом случае он не будет добавлять никаких атрибутов к функции.