Действия администратора
Основной рабочий процесс административной панели Django, по сути, сводится к «выбору объекта и его изменению». Это хорошо работает для большинства случаев использования. Однако, если вам нужно внести одно и то же изменение во множество объектов одновременно, этот рабочий процесс может быть довольно утомительным.
В таких случаях административная панель Django позволяет записывать и регистрировать «действия» — простые функции, которые вызываются со списком выбранных объектов на странице списка изменений.
Если вы посмотрите на любой список изменений в административной панели, вы увидите это в действии; Django поставляется с действием «удалить выбранные объекты», доступным для всех моделей. Например, вот модуль пользователей из встроенного приложения Django django.contrib.auth:
Предупреждение
Действие «удалить выбранные объекты» использует QuerySet.delete() по соображениям эффективности, что имеет важное замечание: метод delete() вашей модели не будет вызван.
Если вы хотите изменить это поведение, просто напишите пользовательское действие, которое выполняет удаление нужным вам способом — например, вызвав 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): # __unicode__ on Python 2
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.http import HttpResponse
from django.core import serializers
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(MyModelAdmin, self).get_actions(request) if request.user.username[0].upper() != 'J': if 'delete_selected' in actions: del actions['delete_selected'] return actions
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/ref/contrib/admin/actions/