Pull to refresh

Допиливаем админку бензопилой. Часть I — Thumbnails

Reading time 4 min
Views 11K
Вставлю свои пять копеек о том, насколько классная и гибкая штука — админка Django. Многие упорно не хотят понять, что это не игрушка, а вполне себе приложение production-качества, которое просто глупо не использовать, если есть такая возможность.

И дело здесь совсем не в том, что разработчики такие молодцы, что предусмотрели так много возможностей кастомизирования, чтобы покрыть все нужды юзеров — это конечно же не совсем так. Тем не менее в распоряжении разработчика всегда есть другое мощнейшее средство кастомизации любых веб-приложений вообще. Таким средством является Javascript. Разработчики Django, прекрасно это осознавая, предоставляют нам все средства для подключения кастомного JS/CSS, что по сути, вобще снимает какие-либо ограничения на то, что мы хотим слепить из нашей админки.

Конечно в этом нет ничего военного и большинство опытных разработчиков так и делают, я лишь попытаюсь описать парочку жизненных рецептов, на основании которых можно, подключив немного фантазии, решать практически любые вопросы связанные с кастомизированием Django-админки. Если конкретнее, то заниматься будем тем, что сделаем акуратные thumbnails для наших ImageField'ов, подключим TinyMCE, и научим его вставлять картинки используя саму Django-admin в качестве файл-менеджера для выбора картинок.

Итак предположим, что мы хотим взять обычные flatpages и дополнить их возможностью создавать страницы с помощью WYSIWYG-редактора, в том числе заливать и вставлять картинки (что за страницы без картинок?)

Для начала создадим простую модель FlatPicture и научим её делать thumbnails. Для этого воспользуемся библиотекой PIL (стандартнее некуда). С её помощью создавать thumbnails не просто, а очень просто. Всю логику работы с thumbnails вынесем в отдельный файл (да-да все 10 stupid simple строчек)

#utils.py

import os
from PIL import Image

def get_thumbnail_url(image_url, size=150):
    thumbs_part = 'thumbs_' + str(size)
    image_url_parts = image_url.rsplit('/', 1)
    return image_url_parts[0] + '/' + thumbs_part + '/' + image_url_parts[1]

def get_thumbnail_path(image_path, size=150):
    thumbs_dir = 'thumbs_' + str(size)
    dirname, filename = os.path.split(image_path)
    dirname = os.path.join(dirname, thumbs_dir)
    if not os.path.exists(dirname):
        os.mkdir(dirname, 0755)
    return os.path.join(dirname, filename)

def create_thumbnail(image_path, size=150):
    thumb_path = get_thumbnail_path(image_path, size)
    delete_thumbnail(image_path, size)
    img = Image.open(image_path)
    img.thumbnail((size, size), Image.ANTIALIAS)
    img.save(thumb_path)

def delete_thumbnail(image_path, size=150):
    thumb_path = get_thumbnail_path(image_path, size)
    if os.path.exists(thumb_path):
        os.remove(thumb_path)


Эти функции нам очень пригодятся для того, чтобы прикрутить thumbnails к нашей модели:

from django.db.models.signals import post_save, pre_delete
from utils import *

class FlatPicture(models.Model):
    picture = models.ImageField(upload_to='uploads/flatpictures', max_length=250)
    description = models.CharField(max_length=250, blank=True)

    def get_thumbnail_html(self):
        html = '<a class="image-picker" href="%s"><img src="%s" alt="%s"/></a>'
        return html % (self.picture.url, get_thumbnail_url(self.picture.url), self.description)
    get_thumbnail_html.short_description = _('thumbnail')
    get_thumbnail_html.allow_tags = True

def post_save_handler(sender, **kwargs):
    create_thumbnail(kwargs['instance'].picture.path)
post_save.connect(post_save_handler, sender=FlatPicture)

def pre_delete_handler(sender, **kwargs):
    delete_thumbnail(kwargs['instance'].picture.path)
pre_delete.connect(pre_delete_handler, sender=FlatPicture)


Вот так вот дёшево и сердито у нас уже автоматически создаются и удаляются thumbnails нужного нам размера. Хотя стоит только бросить взгляд на форму редактирования и список картинок, как становится очевидно что чего-то явно не хватает…



… Конечно же и в списке и на форме хочется видеть thumbnail текущей картинки, а не догадываться по названию что там. Таким образом плавно подходим к теме этого поста. Нет, нам не нужно переопределять десяток шаблонов и писать новые виджеты. На помощь нам придёт Javascript.

Для наглядности я использую jQuery, подлючаю его (вместе с TinyMCE) опять же stupid simple — переопределив шаблон 'admin/base_site.html'. Это довольно удобно подключить эти библиотеки один раз «глобально», если они у вас используются по всей админке:

{% extends "admin/base.html" %}
{% load i18n %}

{% block title %}{{ title }} | Админка example.com{% endblock %}

{% block branding %}
<h1 id="site-name">Админка example.com</h1>
{% endblock %}

{% block nav-global %}{% endblock %}

{% block extrastyle %}
<script type="text/javascript" src="/media/lib/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/media/lib/tiny_mce/jquery.tinymce.js"></script>
<script type="text/javascript" src="/media/js/admin/base.js"></script>
{% endblock %}


C задачей отображения thumbnail в списке нам поможет справиться метод 'get_thumbnail_html', который мы уже предусмотрительно добавили в модель. А для того чтобы отобразить thumb на форме, нам понадобится следующий JS, который можно будет впоследствии подключать к любой форме, для которой понадобится такая функциональность:

$().ready(function() {
  $('a[target=_blank]').each(function() {
      if ($(this).html().indexOf('uploads/') == 0) {
          var path = $(this).attr('href');
          path = path.substring(0, path.lastIndexOf('/'))
                  + '/thumbs_150'
                  + path.substring(path.lastIndexOf('/'));
          $(this).parent().after('<a style="margin-left: 10em" href="'
                  + $(this).attr('href') + '" target="_blank"><img src="'
                  + path + '" alt="image"/></a>');
      }
  });
});


Теперь лёгким движением руки добавляем оба этих элемента в 'admin.py'…

class FlatPictureAdmin(admin.ModelAdmin):
    class Media:
        js = ['js/admin/display_thumbs.js']
    list_display = ['get_thumbnail_html', '__unicode__', 'description']
    list_display_links = ['__unicode__']
admin.site.register(FlatPicture, FlatPictureAdmin)


… и любуемся на результат:



На этом пока всё. В следующей серии о том, как заставить TinyMCE использовать эту страницу со списком в качестве файл-менеджера картинок.
Tags:
Hubs:
+37
Comments 58
Comments Comments 58

Articles