Показаны сообщения с ярлыком admin. Показать все сообщения
Показаны сообщения с ярлыком admin. Показать все сообщения

вторник, 12 мая 2009 г.

Последний редактор в admin

Хочу привести пример, как вывести последнего редактора объекта в админке.
Все действия над объектами в django.contrib.admin логируются моделью django.contrib.admin.models.LogEntry, поэтому нам достаточно просто найти последнее действие для выбранного объекта, и получить из него автора.
Пишем:

from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType

def last_editor(obj):
""" Return last editor for object """
ct = ContentType.objects.get_for_model(obj)
pk = obj.pk
l = LogEntry.objects.filter(content_type=ct, object_id=pk).order_by('-action_time')
if l.count():
l = l[0]
return '%s %s - %s' % (l.user.first_name or l.user.username,
l.user.last_name,
l.action_time.strftime('%d %h at %H:%M'))
else:
return _('Nobody or UFO')
last_editor.short_description = _('Last editor')

Подключаем в list_display необходимого ModelAdmin:

class PostAdmin(admin.ModelAdmin):
list_display = ('title', last_editor)

Открываем список постов в админке:

Только важно понимать, что редактирование объектов извне или изнутри приложения — не логируется, только через административный интерфейс. Читать дальше.

Новое в транке Django 1.1

В Django 1.1 Beta 1 попало достаточно интересных вещей, одна из них — это действия над группой элементов в django.contrib.admin рассмотренная в прошлой записи.
Сейчас я хотел бы рассмотреть другие интересные изменения:

  • Новые методы QuerySetdefer, only
  • Редактируемые поля в списке объектов django.contrib.admin
  • "Неуправляемые" модели
  • Прокси модели

defer и only

По умолчанию ORM в Django загружает из базы данных при запросе, все поля объекта. В случае когда преобразование записи базы данных в объект Python занимает долгое время, или полей очень много, или какое-либо из полей содержит большое количество данных — это невыгодно.
Теперь мы можем сказать ORM выбирать только определенные поля из таблицы, с рассчетом на то, что другие не будут использованы.
Приведу пример, допустим у нас есть модель Book
from django.db import models
from django.contrib.auth.models import User

class Book(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length=255, db_index=True)
content = models.TextField()
def __unicode__(self): return self.title

В поле title — хранится название книги, а в content — текст книги целиком. Если нам необходимо вывести весь список книг, мы сделаем запрос:
books = Book.objects.all()
На что джанго ответит таким запросом в БД:
SELECT "testapp_book"."id", "testapp_book"."author_id", "testapp_book"."title", "testapp_book"."content" FROM "testapp_book" LIMIT 21
Как видим, content — в списке выбираемых полей. Для нас это критично производительностью. А теперь поступим по другому и используем defer:
books = Book.objects.all().defer('content')
И увидим следующий запрос:
SELECT "testapp_book"."id", "testapp_book"."author_id", "testapp_book"."title" FROM "testapp_book" LIMIT 21
Ну вот, жизнь налаживается. Если теперь мы обратимся к полю content экземпляра модели Book:
print books[0].content
То джанго спросит у БД следующее:
SELECT "testapp_book"."content" FROM "testapp_book" WHERE "testapp_book"."id" = 1
Как видим, defer — это отложенная загрузка полей. Чтобы отключить ее для объекта QuerySet достаточно вызвать метод defer с аргументом None. Чтобы отложить загрузку нескольких полей, достаточно перечислить их как аргументы для defer:
Book.objects.defer('author_id', 'content')
Теперь об only. Only — это метод QuerySet, который работает похожим образом с defer, только наоборот. С помощью only мы можем перечислить поля которые нам необходимо получить, а все остальные выбраны из базы не будут. Например если нам необходим только title книги и логин ее создателя, то запрос к ORM будет следующим:
books = Book.objects.select_related().only('title', 'author__username')

Редактируемые поля в админке

Следующее новшество — это возможность редактирования полей объекта, на странице списка всех объектов. Вот как это выглядит:



Вот как это включается в ModelAdmin:
class BookAdmin(admin.ModelAdmin):
def author_name(obj):
return '%s' % obj.author.username
author_name.short_description = 'Author username'
list_display = ['id', 'title', author_name]
list_editable = ['title']
Ключевая строка:
list_editable = ['title']
Собственно она и говорит о том какие поля будут редактируемыми. Есть небольшой нюанс. По умолчанию первое выводимое поле в таблицы — есть ссылка на объект. Поэтому если мы делаем первое поле редактируемым, мы обязательно должны указать в display_links — какие поля будут ссылками, иначе Django скажет нам что-то вроде этого:
'BookAdmin.list_editable[0]' refers to 'title' which is not defined in 'list_display'.

Неуправляемые модели

В Meta-опциях модели, появилась новая опция managed, по-умолчанию равная True. Эта опция включает или исключает модель из поля зрения syncdb. Это полезно, когда у нас уже есть таблица в базе данных, и мы просто создаем для нее модель. В таком случае syncdb при запуске, не будет трогать существующую таблицу, и пытаться создать новую для модели.

Прокси модели

Прокси модели — это модели, которые не имеют реального отображения в базе данных. Они имеют одну и ту же таблицу в БД, как и проксируемая модель. Для чего это нужно? Допустим нам необходимо, чтобы модель User имела ordering по-умолчанию по емэйлу автора, и некоторые дополнительные функции. Вот так будет выглядеть наша прокси модель:
from django.db import models
from django.contrib.auth.models import User

class MyUser(User):

def say(self, message):
print '%s say %s' % (self.username, message)
class Meta:
proxy = True
ordering = ['email']
И вот так мы ее можем использовать:
>>> u = MyUser.objects.get(username='n0uk')
>>> u.say('hello')
n0uk say hello

Интересно? Читаем документацию:

Читать дальше.

понедельник, 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.

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