Django галерея - проще некуда

13373 18

    Давно пользуюсь галереей photologue, но как часто случается, чужой функционал неповоротлив и избыточен. А хочется чего-то легкого и понятного. Так как ничего подходящего мною в сети не найдено, решил последовать советам бывалых и написать свой собственный велосипед.

    Открыв свою настольную книжку Django - Разработка веб-приложений на Python, внимательно изучив и "протопав" все шаги своими руками 0_о, понимаю, что книга безбожно отстает. Предложенное решение мне откровенно не нравится.

    В связи с этим начинаю обрабатывать полумёртвый код "напильником".

    Сразу выделяю ряд непонятных мне и не сработавших в моей 1.4 джанге решений автора:

1. Разбиение галереи на альбомы в модели. Мне это не надо, я делаю простой велосипед.
2. Создание собственного класса на основе ImageField c добавлением создания и воспроизведения миниатюр загружаемых изображений. Прошу не пинать ногами, но я не понимаю зачем ради такого малого изменения класса создавать целое поле, тем более, что код явно устарел, мне не понятен до конца и просто не работает.
3. Предложенный вариант удаления записей из модели галереи не удаляет файлы с физического носителя. Гуру джанги говорят, что это правильное поведение, возможно, но не в моем случае.

    В итоге размышлений и некоторых танцевальных зарисовок с нашим народным инструментом, прихожу к рабочей версии модели:

# -*- coding:utf-8 -*- 
from django.db import models
from django.contrib import admin
# Импортируем класс для создания миниатюр из PIL. Ставится отдельно
from PIL import Image 
import os

# Изменение (filename, URL) вставкой '.mini' и изменение расширения на jpg
def _add_mini(s):
    parts = s.split(".")
    parts.insert(-1, "mini")
    if parts[-1].lower() not in ['jpeg', 'jpg']:
        parts[-1] = 'jpg'
    return ".".join(parts)

# Удаление миниатюры с физического носителя.
def _del_mini(p):
    mini_path = _add_mini(p)
    if os.path.exists(mini_path):
        os.remove(mini_path)

# Основной класс модели галереи
class Photo(models.Model):
    title = models.CharField(max_length=250)
    image = models.ImageField(upload_to='gallery')
    captions = models.CharField(max_length=250, blank=True)

    class Meta:
        verbose_name = ('Иллюстрация')
        verbose_name_plural = ('Иллюстрации')
        ordering = ['title']

    def __unicode__(self):
        return self.title

# Добавляем к свойствам объектов модели путь к миниатюре
    def _get_mini_path(self):
        return _add_mini(self.image.path)
    mini_path = property(_get_mini_path)

# Добавляем к свойствам объектов модели урл миниатюры	
    def _get_mini_url(self):
        return _add_mini(self.image.url)
    mini_url = property(_get_mini_url)

# Добавляем к свойствам объектов модели html код для отображения миниатюры
# Сделано в модели для упрощения вывода в админке. Смотрим далее.	
    def get_mini_html(self):
        html = '<a class="image-picker" href="%s"><img src="%s" alt="%s"/></a>'
        return html % (self.image.url, _add_mini(self.image.url), self.captions)
    mini_html = property(get_mini_html)
    get_mini_html.short_description = ('Иллюстрация')
    get_mini_html.allow_tags = True

# Создаем свою save
# Добавляем:
# - создание миниатюры
# - удаление миниатюры и основного изображения 
#   при попытке записи поверх существующей записи
    def save(self, force_insert=False, force_update=False, using=None):
        try:
            obj =  Photo.objects.get(id=self.id)
            if obj.image.path != self.image.path:
                _del_mini(obj.image.path)
                obj.image.delete()
        except:
            pass
        super(Photo, self).save()
        img = Image.open(self.image.path)
        img.thumbnail(
            (128, 128),
            Image.ANTIALIAS
        )
        img.save(self.mini_path, 'JPEG')

# Делаем свою delete с учетом миниатюры		
    def delete(self, using=None):
        try:
            obj = Photo.objects.get(id=self.id)
            _del_mini(obj.image.path)
            obj.image.delete()
        except (Photo.DoesNotExist, ValueError):
            pass
        super(Photo, self).delete()
        
    def get_absolute_url(self):
        return ('photo_detail', None, {'object_id': self.id})

# Класс для админки с отображением миниатюры в листе изображений (get_mini_html)
# и возможностью физического, пакетного удаления 
# изображений и миниатюр (full_delete_selected)
class PhotoAdmin(admin.ModelAdmin): admin.site.disable_action('delete_selected') def full_delete_selected(self, request, obj): for o in obj.all(): o.delete() full_delete_selected.short_description = 'Удалить выбранные иллюстрации' actions = ['full_delete_selected'] list_display = ('title', 'captions', 'get_mini_html') admin.site.register(Photo, PhotoAdmin)

    На этом собственно создание простейшей галереи и заканчивается. Далее в шаблонах используем модель как обычно с учетом созданых нами свойств объектов: mini_path, mini_url и если пригодится mini_html.

    Галерея работает, но нет предела совершенству!

    Возможно я что-то упустил, поэтому если есть замечания - пишите.

Комментарии

14 сентября 2015 г. 12:55 owlman
Рекомендую с данным рецептом использовать приложение django_cleanup, чтобы Ваши диски отчищались от графического хлама.
2 марта 2013 г. 17:36 owlman
Collectstatic я не пользуюсь. В сайтах на одном хостинге предпочитаю символьные ссылки.
2 марта 2013 г. 13:14 Влад
STATICFILES_DIRS = (
'/home/user/project/public/static/',
) - но тогда у вас не получится сделать collectstatic, потому что этот путь совпадает с STATIC_ROOT. Я сейчас разбираюсь с этими настройками и нифига не понимаю :(
1 марта 2013 г. 12:27 owlman
STATICFILES_DIRS = (
'/home/user/project/public/static/',
)
28 февраля 2013 г. 17:39 Влад
Пардон, а в STATICFILES_DIRS у Вас что?
23 февраля 2013 г. 10:17 owlman
Проверьте пути к файлам-изображениям. Я так понимаю картинки должны лежать в папках-альбомах. Пути к мини-образам также должны быть изменены. Я думаю проблема в этом.
22 февраля 2013 г. 14:13 Влад
Здравствуйте!
Сделал альбомы, добавив class Albom(models.Model): и в Photo albom = models.ForeignKey('auto.Albom'). Все нормально, но когда делаю, чтобы в админке галлереи вкладывались в альбомы class PhotoInline(admin.TabularInline): Перестает отображаться поле 'get_mini_html'. Если альбомы и галлереи отображать независимо, то поле есть. Как это можно пофиксить? Спасибо!
21 февраля 2013 г. 15:58 owlman
В settings.py:

MEDIA_ROOT = '/home/user/project/public/media'
MEDIA_URL = '/media/'
STATIC_ROOT = '/home/user/project/public/static/'
STATIC_URL = '/static/'

В настройках Апача аналогично:
http://owlman.net/django/django-apache2-nginx-mod_wsgi/
21 февраля 2013 г. 15:00 Влад
А в Вашем случае в какую папку сохраняется? (в смысле, как она настроена в settings)
21 февраля 2013 г. 14:34 owlman
Неправильно поняли. :)
Файлы сохраняются в /media/gallery/
почему media и чем он отличается от /static/ лучше в доках джанги почитать.

Если же Вам сохранять надо именно в статику (что не очень хорошо по сображениям безопасности) поиграйте с путями в settings.py и/или с алиасами в настройках http сервера.
21 февраля 2013 г. 13:13 Влад
А как сделать, чтобы фото сохранялись в папку со статическими файлами? Как я понял upload_to='gallery' создает путь относительно папки представления (там где settings.py), а не от корня проекта. А статика лежит в корень/files/static/img
Спасибо!
19 февраля 2013 г. 10:09 owlman
Намекаю :)
Делайте модель связанную с фотографиями OneToMany и по аналогии с вышеописанным кодом делайте каталоги.
12 февраля 2013 г. 19:28 Влад
Здравствуйте!
Намекните, пожалуйста, как все-таки сделать альбомы. Т.е. как добавить возможность создания нескольких галлерей с разными имена?
Спасибо!
26 октября 2012 г. 13:25 owlman
Очерь рад, что Вам мой пост помог. Такие комментарии хорошо мотивируют придумывать и писать дальше. Спасибо. :)
26 октября 2012 г. 11:53 Сергей
Огромнейшее спасибо!!! Я как раз начал разбираться с django и в этом посте нашел многое, что сейчас изучаю: и про миниатюры, и ! самое главное! по нормальному отображению в админке!!! спасибо автору огроменное. Побольше бы таких постов для новичков. Ничего лишнего, как раз для изучения !!!
20 июня 2012 г. 16:14 Ильнур
спасибо большое.
почитаю
18 июня 2012 г. 13:31 owlman
Спасибо за отзыв.
Про то как реализован счетчик просмотров я писал ранее в посте "Django подсчет количества просмотров".
Ссылка справа в "Самых читаемых", вторая.
18 июня 2012 г. 13:02 Ильнур
очень интересный блог.

сам начал изучать django

хотелось бы почитать, как вы реализовали функцию, количество просмотров поста.

спасибо

:)

Контактные данные

 Россия, г. Москва