понедельник, 4 марта 2013 г.

lightbox vs elastislide

В одном из веб-проектов потребовалось реализовать галерею изображений.

Ранее уже успешно использовал плагин jquery.elastislide.js.

Применил и на этот раз.

Кроме того, надо было сделать так, чтобы по клику на фотку она открывалась в "полный рост" - наиболее популярным решением сейчас является для этого lightbox , его я и попытался применить.

Все сделал по манам, верстка правильная, ошибок в консоли  js нет, но изображение по клику не открывается - lightbox не фурычит.

Сразу заподозрил конфликт обработчиков событий. Так и оказалось.

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

$('#carousel').elastislide({imageW :300,margin: 30,minItems: 2, onClick: null});



воскресенье, 24 февраля 2013 г.

Небольшая ремарка по django-imagestore

Тому, кто хочет организовать фотогалерею на django, могу посоветовать в  качестве основного приложения django-imagestore .
Приличный функционал, возможность расширения засчет переопределения моделей альбомов и фотографий, более-менее удобная админка.
Но как всегда не без греха. Как минимум одна проблема в этом модуле есть - а именно проблема каскадного удаления..
Допустим, у вас есть альбом, в нем много фотографий.

Вы из админки удаляете фотографию (не выключаете из альбома, а просто удаляете).
Мелким шрифтом , конечно, показывается предупреждение о том, что будут удалены все сущности. Но  кто их читает ?)

Вот и получается - хотели удалить фотку, а удалили весь альбом.




А если на альбом ссылалась еще какая-нибудь сущность, то и она успешно удалится.
На подобное я напарывался в django-lfs.
Чтобы решить эту проблему , необходимо либо пропатчить поле Image.album, добавив в него  on_delete=models.SET_NULL.

 Либо переопределить Image ,  как это позволяет делать imagestore и уже аналогично решить эту проблему на уровне своей модели.

воскресенье, 17 февраля 2013 г.

Слайды: кастомизация интерфейса django-admin

На просторах интернета попалась следующая презентация, которая может задать вектор начинающему джанговоду в направлении того, как можно изменить стандартный интерфейс админки django.

Не могу не поделиться



вторник, 12 февраля 2013 г.

How to improve Test::Class to conrol test ttl

To  make Test::Class control test-suites time-to-live you could inherit Test::Class and redefine runtests method..

For example:

package TTL::Test::Class;
use base qw(Test::Class);
use Test::More;
use POSIX ":sys_wait_h";
use constant DEFAULT_TIMEOUT => 60;
# переопределяем метод запуска тестов 
sub runtests{
    my $t = DEFAULT_TIMEOUT;                                           
    my $pid = fork;                         
    my $child_ret_code;                           
    if( $pid > 0 ){ # parent process                                         
       #child waiting loop
       my $ok_flag = 0;                                                  
       for(my $i=0; $i < $t; $i++){                                       
          if(waitpid($pid, WNOHANG)){                                           
               $child_ret_code = $?/256;                                       
               ok($child_ret_code);                                             
               $ok_flag = 1;                                                  
               last;                                                            
          };                                                                              
          sleep(1);
       };
       if( not $ok_flag ){
         diag "Killing test by its timetolive...";
         kill TERM =&gt; 
         $pid;
         return $ok_flag;
       };
       return $child_ret_code;                                                  
    } elsif( $pid == 0 ) { #child                                               
        exit $self->SUPER::runtests;                                         
    } elsif ( not defined($pid) ) { #unsuccessfull fork                         
        die "Cannot fork child process: $!";                                    
   }                                                                            
} 
1;

I think, we don't need SIGCHILD here..

source

вторник, 5 февраля 2013 г.

django-sitetree: key error 'request'

Сообщение об ошибке при работе с django-sitetree  в админке django :

говорит о том , что необходимо добавить

KeyError at /admin/sitetree/tree/1/

'request'
Request Method: GET
Request URL: http://localhost:8080/admin/sitetree/tree/1/
Django Version: 1.4.3
Exception Type: KeyError
Exception Value:
'request'


"django.core.context_processors.request", в TEMPLATE_CONTEXT_PROCESSORS

только  и всего

четверг, 31 января 2013 г.

django-lfs: EOFError при работе в режиме кеширования

На одном моем сервере "крутится" магазинчик под управлением Django-lfs

  • django 1.4
  • nginx 1.1.9
  • uwsgi 1.2.3
Время от времени на тревожный email приходят сообщения об ошибке вида:

EOFError

TRACEBACK:
  File "..../pydocs/env/lib/python2.7/site-
packages/Django-1.4.1-py2.7.egg/django/core/handlers/base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)

  File "..../pydocs/deploy/../lfs/catalog/views.py", line 267, in category_view
    inline = category_products(request, slug, start)

  File "...../pydocs/deploy/../lfs/catalog/views.py", line 358, in category_products
    temp = cache.get(cache_key)

  File "....../pydocs/env/lib/python2.7/site-packages/Django-1.4.1-py2.7.egg/django/core/cache/backends/db.py", line 75, in get
    return pickle.loads(base64.decodestring(value))

Ошибка "мигающая".

Пока лишь понятно, что копать в сторону python модуля pickle .

Поверхностный анализ кода этого pickle пока не помог в поиске причины.

Ждет меня дебаг продолжительный, судя по всему :)



четверг, 27 декабря 2012 г.

django-lfs: грабли с каскадным удалением

Текущая известная мне версия django-lfs писалась для django версий еще младших, чем 1.3. До этой версии включительно ORM этого фреймворка содержал досадную "особенность"  - ненастраиваемое каскадное удаление сущностей , ссылающихся на удаляемый объект.
Кто не знал этой особенности, получал граблями по лбу либо при тестировании, либо уже, что тоже бывало , при  эксплуатации.
Классический фейл с django-lfs: оператор интернет магазина решает удалить производителя, ставшего ненужным. На этот момент к производителю были привязаны пара сотен товаров. 
Manage-интерфейс lfs настолько дружелюбен, что не предупреждает юзера об удалении этих пары сотен товаров, заодно с удалением производителя. Но ведь так можно и до инфаркта людей довести! ) В общем эдакое  "кто не спрятался  я не виноват"  кто не делает бекап, вбивает все заново))
Более опытные django-воды обходили подобные проблемы либо на уровне БД либо в хуке для delete-операции, явно обнуляя соответствующие поля в ссылающихся таблицах.

Начиная с версии 1.3 появилась возможность настраивать ORM при удалении объекта. Для этого в описании поля модели надо указывать: on_delete=models.SET_NULL

Круто, только вот разрабы lfs об этом не позаботились, так что патчить модели надо самим.

Ну и в завершение.. чтобы избегать подобных граблей:

1. не надейтесь на протестированность сторонних решений, особенно , если они бесплатные
2. постоянно углубляйте знания инструментария, которым пользуетесь .. в данном случае курить djangoproject.com
3. ежедневный бекап БД спасет от большинства подобных фейлов ( а лучше бекапиться несколько раз в день )

воскресенье, 16 декабря 2012 г.

django-lfs: улучшение механизма работы с картинками

В предыдущем посте я упоминал об ошибке, которая возникнет , если пользователь сайта на django-lfs будет загружать картинки с именем файла , длиной более 99 символов.

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

Я решил сделать так:

1. принимаем картинку
2. запоминаем расширение файла
3. получаем хеш (например md5) от имени  + текущее время (чтобы не получать одинаковых хешей для разных картинок с одинаковыми именами)
4. получаем новое имя конкатенацией хеша и расширения
5. сохраняем

Наилучшим местом в коде, куда вклинить этот алгоритм является lfs/core/fields/thumbs.py

Подключаем вспомогательные библиотеки:
+++ b/pydocs/lfs/core/fields/thumbs.py Sun Dec 16 19:17:04 2012 +0400 @@ -7,6 +7,13 @@
except ImportError:
from PIL import Image

+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+from time import time

Генерируем новое имя файла перед сохранением:
def save(self, name, content, save=True):
+ parts = name.split('.')
+ ext = '.%s' % parts[-1] if len(parts) > 1 else ''
+ name = md5("%s-%s"%(name.encode('utf-8'), time())).hexdigest()
+ name = "%s%s"%(name,ext)
super(ImageWithThumbsFieldFile, self).save(name, content, save)
После этого все имена картинок будут одинаковой длины и о том , зачем юзеру такие длинные файлы, можно не думать. p.s. дальнейшее улучшение механизма работы с картинками будет - проверка файла на то, в допустимом графическом ли он формате или нет. Сейчас django-lfs принимает на вход все подряд. Судя по всему, безопасности это практически не угрожает , но 404 по запросам картинок - это тоже нехорошо.

django-lfs: пара тройка неприятных багов

Хочу рассказать о нескольких неприятных моментах (читай "ошибках"), с которыми совсем недавно столкнулся при использовании django-lfs:

Ошибки при сохранении картинок с длинными именами

Для хранения картинок в django-lfs используется специальный тип поля, пронаследованный от стандартного ImageFile. В этом стандартном типе дефолтная длина файла картинки 100 символов.
В модели , которая используется для хранения картинок товаров , например, max_length явно не указывается. В итоге при попытке отобразить превьюхи (thumbs) оригинальной картинки с длинным именем ( >= 100 символов), случается поначалу непонятный IndexOutOfRange..Казалось бы причем тут массивы...

Вот такой замечательный код есть..

class ImageWithThumbsFieldFile(ImageFieldFile):
    """
    See ImageWithThumbsField for usage example
    """
    def __init__(self, *args, **kwargs):
        super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
        self.sizes = self.field.sizes

        if self.sizes:
            def get_size(self, size):
                if not self:
                    return ''
                else:
                    split = self.url.rsplit('.', 1)
                    thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
                    return thumb_url

падает, как нетрудно догадаться,  в строчке

                    thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])

Причина в том, чтo в rsplit передается усеченное имя файла.. Как видим, никакими дополнительными проверками авторы себя не обременили, поэтому дело и доходит до "пятисоток"..

Запретить пользователям не аплоадить такие картинки - нельзя.

Планирую следующий фикс: все картинки, поступающие в django-lfs сохранять под сгенерированными уникальными именами заранее фиксированной длины.

А пока для своих проектов на django-lfs сделал небольшой костыль в виде max_length = 2048 в описании модели картинки.

Глюки с редактированием товаров в manage-интерфейсе

django-lfs позиционируется как шустрый фреймворк для ешопов. Шустрость его в частности основана на активном использовании кеширования бд.

Баг , который я сейчас опишу, как раз  и проявляется при использовании кеширования.

Допустим , вы используете кеширование в таблице БД. Выставляете время жизни объектов в кеше , например , в 60 секунд.

Пользователь магазина наконец-то решает заняться наполнением. Создает товар, редактирует "данные" и сохраняет их . Затем переходит на "категории" и вносит товар в нужные категории. "Сохранить категории". Лезет на сайт, а товара там нет... Лезет в интерфейс управления и видит, что "активно" , не выставлен, цены нет и вообще вкладка "данные" такая, как будто он к ней не прикасался.
Причина в том, что между сохранением "данных" и  сохранением  "категорий" не прошло минуты и при сохранении  категорий, товар взялся из кеша.

Вопрос, почему не был обновлен кеш при сохранении "данных" , пока открыт. Выясню и исправлю, ведь без кеширования как-то несолидно :)

Да и есть такие django-модули, которые без
 кеширования  вообще не работают. Например, используемый многими supercaptcha.

UPD: 
Глюк с редактированием товаров исправляется отправкой сигнала product_changed в products.py (после сохранения формы данных),  а также с отправкой аналогичного сигнала при изменении категорий товара.


суббота, 1 декабря 2012 г.

django: Работаем с формами оптимально

В стандартном джанговском мануале как нигде наглядно и понятно разписаны основы и более продвинутые но все же классические техники работы с формами.

Ниже делюсь слайд-материалами , из которых станет ясно, как оптимизировать работу с формами в django, сократив количество кода, а следовательно и вероятность ошибок...

В общем, всем django-разработчикам советую:  ОЗНАКОМИТЬСЯ