понедельник, 11 мая 2009 г.

Действия над группой объектов в django.contrib.admin

Не так давно, в джанго-админке появились Admin Actions (http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/#ref-contrib-admin-actions). О них я и хотел бы сегодня рассказать. В процессе записи такие действия я буду называть AdminAction.

Что это, и зачем оно мне нужно?

На самом деле необходимость такой вещи назрела уже давно. Допустим я хочу удалить все объекты определенной модели из админки (представим, что у меня их ~100 штук), не имея доступа к базе данных. Задача превращается в довольно муторную, и во всех нормальных продуктах давно уже можно пару раз жмакнуть по галочке "Выделить все" и удалить все выделенное за следующие пару кликов. Поэтому выкручивались джангисты как могли. А теперь все просто.

Смотрим как это просто

AdminAction это обычная функция, которая принимает три аргумента.
  1. Экземпляр ModelAdmin, к которому прикручено действие.

  2. HttpRequest объект, пришедший от пользователя.

  3. QuerySet выделенных объектов.
Представим, что у нас есть приложение testapp. В нем есть модель Post
class Post(models.Model):
""" Post model. """

title = models.CharField(max_length=25)
content = models.TextField()
is_draft = models.BooleanField(default=True)

def __unicode__(self): return self.title

Boolean-поле is_draft сигнализирует о том, что запись черновик, и публиковать ее смысла нет. Сделаем свой AdminAction, который будет публиковать выделенные посты, т.е. ставить у постов is_draft=False.

Открываем admin.py и приводим его к такому виду:
from django.contrib import admin
from testapp.models import Post

def make_published(modeladmin, request, queryset):
""" Make all posts published. """
queryset.update(is_draft=False)
make_published.short_description = "Mark selected posts as published"

class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'is_draft')
actions = [make_published]

admin.site.register(Post, PostAdmin)

Что мы тут видим. Видим функцию make_published которая как-раз принимает те три аргумента о которых я писал выше. В PostAdmin видим строчку actions = [make_published]. Эта строчка подключает наш AdminAction к PostAdmin. Внутри функции make_published мы просто вызываем метод update у QuerySet и устанавливаем для всех выделенных объектов is_draft=False. Вот и все. Запускаем приложение и смотрим как это работает:



Логичнее было-бы, плодить новые функции внутри класса PostAdmin, и не выносить их наружу, т.к. больше они нигде не используются. Это возможно, просто перепишем наш пример следующим образом:
from django.contrib import admin
from testapp.models import Post

class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'is_draft')
actions = ['make_published']

def make_published(self, request, queryset):
""" Make all posts published. """
queryset.update(is_draft=False)
make_published.short_description = "Mark selected posts as published"

admin.site.register(Post, PostAdmin)

Что изменилось? Ну во-первых функция make_published перекочевала в PostAdmin. Во-вторых вместо первого аргумента теперь self. И в третьих в actions она указана строкой. Запускаем, и проверяем, что все работает.

Необходимые вещи

Логично было бы показывать пользователю, сколько постов реально было опубликовано при действии. Для этого мы воспользуемся методом ModelAdmin.message_user(request, message).

Пишем:
from django.contrib import admin
from testapp.models import Post

class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'is_draft')
actions = ['make_published']

def make_published(self, request, queryset):
""" Make all posts published. """
count = queryset.filter(is_draft=True).update(is_draft=False)
self.message_user(request, '%s posts mark as published.' % count)
make_published.short_description = "Mark selected posts as published"

admin.site.register(Post, PostAdmin)

Проверяем:




Отлично. Все работает как нужно.

Интересные возможности

Помимо рассмотренного выше, есть и другие возможности, для гибкой настройки административного интерфейса.

AdminAction может возвращать объект типа HttpResponse, который будет отдан напрямую пользователю. Используя это мы можем делать экспорт моделей, сериализацию выбранных объектов в JSON, предпросмотр нескольких объектов, и вообще, все что придет нам в голову.

Включаем, выключаем

Мы рассмотрели возможность подключения AdminAction к определенному ModelAdmin. Но как могли заметить, стандартное действие удаляющее выделенные объекты, никуда при этом не делось. А что, если нам не нравится стандартный AdminAction, который позволяет удалять выделенные объекты? Это не проблема - AdminAction's можно активировать и деактивировать для всего сайта в целом. Например, чтобы выключить тот самый delete_selected, мы должны написать в admin.py:
admin.site.disable_action('delete_selected')
После этого, мы можем включить действие delete_selected там, где оно нужно, просто указав в списке actions у определенного ModelAdmin. А чтобы активировать наше действие, например - make_all_good для всего сайта в целом, достаточно написать:
admin.site.add_action(make_all_good, 'make_good_selected')

Второй аргумент - не обязателен. Просто используя его мы сможем потом также отключить наше действие, как выше отключили delete_selected.

Чтобы выключить все действия для определенной ModelAdmin, достаточно присвоить action=None, внутри класса.

А если мы захотим иметь разный набор действий в зависимости от каких-либо условий, например от группы пользователя, то нам достаточно переопределить метод ModelAdmin.get_actions(self, request). Который возвращает список actions.

На мой взгляд - меганужная и ожидаемая фича.

Комментариев нет:

Отправить комментарий