четверг, 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-разработчикам советую:  ОЗНАКОМИТЬСЯ


пятница, 30 ноября 2012 г.

django-lfs: добавляем произвольные формы в Page

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

Иногда надо, чтобы статическая страница включала в себя какую-нибудь форму. Ничего не мешает, конечно,  в html-код этой страницы впихнуть нужную конструкцию <form>...</form>  через wisywig, добавить соответствующий обработчик в urls и views,  а затем ненадолго успокоиться...
.. до тех пор , пока пользователь сайта в manage интерфейсе случайно не испохабит этот код формы , так что эта хрупкая конструкция перестанет работать...

Вот что я придумал, чтобы оживить модуль Page условно произвольными формами:

1. список форм

Создаем модуль (py-файл ) с классами нужных нам на данный момент форм

2. обновление модели

Добавляем в модель Page списочное поле , значениями списка которых являются классы из нашего набора форм

3. обработчики форм

для каждой из набора форм создаем по обработчику, в котором будет логика обработки данного конкретного класса форм

4. актуализируем page_view

переделываем page_view так, чтобы брать из модели текущий используемый класс формы и передаем в шаблон соответствующую форму, созданную на лету

тут же , если POST, вызываем обработчик формы (для каждого класса формы свой)...

обработчик делаем такими, что они выполняют определенную логику и по необходимости меняют контекст, возвращая управления в вызывающую их вьюху (page_view)...

Таким образом, мы получаем возможность добавления произвольной формы из нашего набора в произвольную статическую страницу (Page) в django-lfs, что делает этот фреймворк для имагов гораздо более гибким...

p.s. код и тонкости (которые есть всегда) не публикую... если кого-то заинтересовала реализация этого, пишите - охотно поделюсь ....


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

понедельник, 19 ноября 2012 г.

foundation css framework + django-lfs

Популярный адаптивный фреймворк foundation идет со своей js-библиотекой, без которой этот фреймворк использовать бесполезно.
django-lfs также идет с несколькими нужными js-либами, среди которых lightbox, cookie...

Чтобы этот зоопарк библиотек заработал вместе foundation-овские js-файлы следует подключать до lfs-ных, но , естественно, после подключения jquery - иначе будут конфликты.

четверг, 15 ноября 2012 г.

Некоторые размышления об открытых e-commerce решениях

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

Среди подобных решений есть вылизанные и очень массовые, можно сказать , попсовые типа joomla, а есть варианты для хардкорщиков и тех, кто изначально планирует "пилить" под себя.

Но даже движки из первой когорты подвергаются правкам. И чем больше правок, тем более высока вероятность того, что безболезненный апдейт на новую версию будет затруднителен или даже невозможен. Мне хорошо знакомо то чувство, когда ты практически форкнул такой бесплатный проект, много чего добавил , кое-что исправил, где-то грешным делом понаставил костылей (чего греха таить) , и тут под фанфары разработчики движка выкатывают новую версию, с кучей плюшек, обновлений, улучшений. Анализируя логи коммитов ты понимаешь , что свой форк поддерживать теперь и развивать только тебе и на халявные функциональные апдейты рассчитывать нечего... Чувства противоречивые, но лично во мне побеждают положительные эмоции - развивать самому мне нравится больше и , пожалуй, даже надежнее.

Перенос данных из sqlite в mysql

sqlite позволяет легко сдампить содержимое бд в файл - для этого используется команда .dump.

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

Причина в том, что синтаксис sql - запросов несколько отличается у этих субд.

Таким образом дамп необходимо преобразовать к виду, пригодному для загрузки в mysql.

Кроме того, может возникнуть проблема с целочисленными PRIMARY KEY после эскпорта, которая будет сопровождаться сообщением типа 'Duplicate entry '0' for key 'PRIMARY''

Причина в том, что в дампе запросы CREATE TABLE не содержат опции AUTO_INCREMENT для PRIMARY_KEY полей. В итоге данные залиты, приложение/сайт работает на чтение, но при попытке что-либо сохранить возникает такая ошибка.

Тут можно либо модифицировать соответствующие поля, добавив им эту опцию. Либо допилить дамп, добавив в него AUTO_INCREMENT для нужных полей

  `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY

и после этого пересоздать и перезалить базу.


Если кому нужен скрипт для конвертации sqlite-формата в mysql - обращайтесь. Поделюсь с удовольствием.


четверг, 8 ноября 2012 г.

Oracle восстановить пароль для sysdba

Если забыт пароль для system Ораклового инстанса, то всегда можно его поменять так:


  1. заходим на сервер , где крутится oracle
  2. переходим под пользователя oracle
  3. sqlplus /nolog
  4. >connect / as sysdba
  5. alter user system identified by new_password

среда, 7 ноября 2012 г.

Немного о том, как я перепиливал django-lfs

Некоторое время назад, когда передо мной встала задача создания интернет-магазина на django я разрывался между несколькими вариантами дальнейших действий:
    1. django-satchmo
    2. django-lfs 
    3. django-shop (недоделанный)
    4. сделать все самому с нуля
Будь у меня побольше время на тот проект, то , скорее всего , я бы остановился на варианте "сделай сам". 
Но в итоге выбор пал на django-lfs, потому что он мне показался проще в установке и сразу из коробки , там что-то даже полноценно работало =)

Довольно скоро после начала попыток создания данного конкретного магазина на lfs стало ясно, что допиливать придется , и допиливать много. Вот краткий список того, что было доделано-переделано:
  1. добавлен механизм новостей (на основе переработанного под SEO модуля django-diario)
  2. добавлен ротатор баннеров
  3. добавлены товары-"новинки"
  4. упрощена работа с адресами
  5. добавлен механизм персональных скидок зарегенным пользователям магазина
  6. расширен модуль "способы доставки" (для возможности иметь адрес доставки опциональным  в зависимости от способа доставки)
  7. добавлен модуль "FAQ"
  8. добавлен модуль "Рекламные кампании"
и еще куча доработок и переделок по - мелочи - все это, естественно, с поддержкой в manage интерфейсе, что требовало дополнительного кропотливого "встраивания".

В перспективе еще много фич задумано...

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

Не уверен, что пришлось бы меньше потеть, возьми я ,  кпримеру satchmo. И наоборот, уверен, что с учетом трудозатрат на ознакомление и допиливание с нуля проект делался бы дольше, начни я делать все с нуля. Правда и "костылей" бы в нем не было, какие , признаться есть сейчас и ожидают очередни на рефакторинг =)

Бытует мнение , что всякий подобный продукт в любом случае придется кастомизировать под заказчика, и не всегда будет складываться так, что архитектура продукта позволит это сделать безболезненно. Это касается не только django-магазинов. Думаю, для всяких джумл с виртуемартами это справедливо в той же или даже большей степени. Что косвенно подтверждают многочисленные отзывы php-разработчиков.

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

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


понедельник, 5 ноября 2012 г.

Django - отдаем статику с nginx-ом

django в DEBUG=False режиме не отдает статику и по этому начинающие разработчики зачастую напарываются на многочисленные 404 после отключения отладочного режима.
Напомню, что для боевого режима статика должна собираться командой collectstatic в отдельную папку (python manage.py collectstatic) - именно на эту директорию и следует натравить nginx (если вы его конечно используете):

location /static/ {
alias /home/projects/test_project/static;
expires 10d;
}

пятница, 2 ноября 2012 г.

Установка DBD::Oracle на linux

Если при установке CPAN-модуля возникли проблемы подобные этой:
Can't load '/root/.cpan/build/DBD-Oracle-1.52-2Zd5mX/blib/arch/auto/DBD/Oracle/Oracle.so' for module DBD::Oracle: libclntsh.so.11.1: невозможно открыть разделяемый объектный файл: Нет такого файла или каталога at /usr/lib64/perl5/DynaLoader.pm line 200
на стадии make или make test, то скорее всего не установлены переменные окружения: ORACLE_HOME и LD_LIBRARY_PATH
$ sudo -s
# export ORACLE_HOME=путь к домашней директории ORACLE
# export LD_LIBRARY_PATH=$ORACLE_HOME/lib
# cpan DBD::Oracle

четверг, 25 октября 2012 г.

Ненавижу виртуальные хостинги

Виртуальные хостинги (shared hosting) в моем понимании - зло. Все эти jail, ограничения, квоты, "шаг влево , шаг вправо" доводят до седых волос, когда в очередной раз  надо развернуть проект, который вот только что "успешно работал на тестовом сервере"...

Как только зальешь на вирт.хостинг, сразу начинаются проблемы и проблемки. И как внимательно не читай отзывы и описания тарифных планов, как тщательно не выспрашивай все у саппорта - ты обязательно наткнешься как какое-нибудь г.... Например в таком виде: на хостинге криво установлен python-mysql - ну не хочет проект запускаться . Требуется явный апгрейд библиотеки, установка -dev версии , возможно самостоятельная ручная сборка... Но бдительный хостер забанил все и вся - пользуйся тем, что есть  как хочешь.

Пишешь саппорту слезную жалобу "ну установите мне то или то"... и хорошо ,если ответят через день... Бывает неделями не отвечают....

Ну и как быть?

В топку дорогие и дешевые shared hosting с их "удобными панелями".. берем vps , и делаем с ней все, что душе угодно - с умом , конечно :) И тогда решение любой (ну или совсем почти любой) технической проблемы оказывается в наших собственных руках!

вторник, 23 октября 2012 г.

Решение проблемы с PIL на хостинге Джино (Jino)

Сегодня потребовалось перенести один из django-проектов на свежеорганизоавнный хостинг "Джино".
Предварительное знакомство с опциями тарифного плана оставило хорошее впечатление - здесь вам и virtualenv и ssh .
Файлы проекта были перенесены успешно, wsgi-скрипт был настроен также за минуту согласно рекомендациям хостера. Чуть подольше ставил нужные либы в виртуальное окружение.
Сайт оказался работоспособным удивительно быстро.
Но первая и пока последняя трудность возникла почти сразу - тестовая загрузка картинки закончилась неудачно.
Немного дебага показало "INFO decoder jpeg not available".
Сразу стало все ясно - PIL в виртуальном окружении установился криво - а точнее без поддержки libjpeg (и не только ее ). Пляски с переменными окружения и указыванием путей к libjpeg не помогли.. И неудивительно , libjpeg-dev на сервере не оказалось.
Ситуацию спасла предустановленная PIL , которая оказалась на сервере - подкладываем ее в virtualenv и все работает )

суббота, 20 октября 2012 г.

sqlite - не поддерживается изменение колонок

Многие , кто только начинает использовать sqlite в работе, часто натыкаются на то, что не могут провести привычную для других СУБД операцию изменений параметров колонки.
Например, у поля в данный момент ограничение NOT NULL, которое вам мешает и вы хотите пометить ее как NULL.
Пробуете применить ALTER TABLE и ... облом.
Перепроверять синтаксис запроса - бесполезно :) sqlite (по-крайней мере текущая 3-я версия ) эту операцию попросту не поддерживает.
Выход один: пересоздать таблицу. Ну а чтобы не потерять данные, предварительно копируете их в резервную таблицу:

  create table tms as select * from table_to_backup; 

после пересоздания возвращаете данные на место

  insert into new_Table_version select * from table_to_backup;

django-lfs: ошибка при управлении остатками

Если при добавлении в корзину товара вы натолкнулись на следующее сообщение об ошибке


TypeError at /product-form-dispatcher

unsupported operand type(s) for /: 'float' and 'NoneType'
Request Method:POST
Request URL:http://test/product-form-dispatcher
Django Version:1.4.1
Exception Type:TypeError
Exception Value:
unsupported operand type(s) for /: 'float' and 'NoneType'
Exception Location:/home/projects/eshop/pydocs/deploy/../lfs/catalog/models.py in get_amount_by_packages, line 780
Python Executable:/usr/sbin/uwsgi
то скорее всего дело в том, что для данного товара вы не указали packing_unit . Этот параметр необязателен, и такое поведение - несомненный баг. Открываем catalog/models.py и правим багу в функции get_amount_of_packages:


780,781c780,781 
< packages = math.ceil(quantity / pu) 
< return packages * pu 
--- 
> packages = math.ceil(quantity / self.packing_unit) 
> return packages * self.packing_unit

вторник, 9 октября 2012 г.

jquery: выравниваем высоту элементов

Наверняка, многие сталкивались подобной ситуацией:

Есть футер, в нем несколько горизонтально располагающихся друг за другом блоков..
Например блоки быстрых ссылок.
У этих блоков свой фон, который отличается от основного фона.
Содержимое блоков (читай, высота) заранее неизвестны.

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

Кто-то пытается решить проблему подбором min-height побольше, что лишь снижает вероятность возникновения проблемы, но не решает ее.

Кто-то , возможно, знает как сверстать одинаковую высоту (заранее неизвестную) для произвольного содержимого блоков.

Кто-то использует старые добрые таблицы )

Я же использую незатейливый js-код и ставшую уже классикой - jquery

Например ,  у описанных блоков класс footer-block. Сделать их равной высоты можно примерно так:


        <script>
          var maxh=0;
          $('.footer-block').each(
            function(){
              var h = $(this).height();
              if( h  > maxh){
                maxh = h;
              }
            }
          );
          $('.footer-container').css('height',maxh);
        </script>


django:использование MultipleChoiceField в моделях

Сегодня натолкнулся на проблему... В django foms есть замечательное поле MultipleChoiceField. Позволяет, как не трудно догадаться, показывать пользователю контрол с возможностью множественного выбора.
Описывается , например, так (в классе формы):

EQUIPMENT_VARIANTS = ( 
 ('pm',u'Посудомоечная машина'),
 ('sm',u'Стиральная машина'),
 ('vr',u'Варка'),
 ('du',u'Духовка'),
 ('mi',u'Микроволновка'),
 ('ho',u'Холодильник'),
 ('vy',u'Вытяжка'), 


equipment = forms.MultipleChoiceField(choices=EQUIPMENT_VARIANTS, required=False)   

Все хорошо - форма рисуется, данные принимаются. Надо сохранить в БД.

Обнаруживается, что для Models поля-аналога нет.

Нормализовать БД, создавая новую словарную таблицу, не планирую и не хочу.

Хочу просто хранить список строк в одном поле.

Что ж, в соответствующей модели объявляем такое поле:

 equipment = models.CharField( max_length="1024", null=True, blank=True)

Привязываем форму к модели , используя forms.ModelForm.
Теперь данные сохраняются в БД. Но есть проблема. В БД они в таком виде: "[u'pm', u'sm']"

Когда все это затеивал знал, что для ChoiceField (вернее для CharField с аттрибутом choices) есть замечательная функция-хелпер get_FOO_display.

Замечательна она тем, что позволяет в шаблоне без лишних движений получить значение выбранной опции , а не мнемонику. Это нужно , например, при выводе превью формы или результата ее сабмита или вообще при выводе значения поля таблицы, которое описано с атрибутом choices.

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

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

#не забыть import re 

def get_equipment_list(self): 
    p = re.compile('\'(\w+)\'') 
    result = [] 
    for e in p.findall(self.equipment): 
       for v in EQUIPMENT_VARIANTS: 
          if v[0] == e: 
             result.append(v[1]) 
    return result 

Функция получает список мнемоник значения поля equipment и формирует список значений опций из EQUIPMENT_VARIANTS - этот массив опций пришлось сделать видимым не только в классе формы , но и в классе модели.

Теперь в django-шаблоне можно спокойно получить для объекта результат множественного выбора из одного из его полей (в данном случае equipment).

Ну а дальше отрисовать этот список как угодно.

Надеюсь, кому-то сэкономит время.

вторник, 24 июля 2012 г.

django: ошибка при отправке e-mail

Если у Вас не отправляются письма из вашего django-проекта и при этом в debug-режиме показывается сообщения типа

'utf8' codec can't decode byte 0xcf in position 32: invalid continuation byte

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

Переименуйте комп , перезагрузите его и пробуйте отправить сообщенеи еще раз )

Удачи.

пятница, 8 июня 2012 г.

django-lfs: опыт использования. часть 3 (ошибка при работе скидками)

Этот пост небольшой и посвящен одной единственной проблемке.

django 1.4
python 2.7

В интерфейсе управления (manage) создаем объект - скидку (discount).

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

Открываем js-консоль и видим там 500 Internal server error в ответ на запрос, который делается при нажатии на "добавить критерий".

Копируем проблемный url и открываем его в отдельной вкладке (убедитесь, что settings.DEBUG=True) , чтобы увидеть стек и понять в чем причина 500-ки.


На картинке не видно, но  в стеке нас отправляют исправлять lfs/manage/criteria/views.py:49

Смотрим,  а там действительно бага - указан некорректный формат для функции strftime.
Вместо %s надо указывать %S.

#       "id": "%s%s" % (now.strftime("%s"), now.microsecond),
         "id": "%s%s" % (now.strftime("%S"), now.microsecond),

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

вторник, 5 июня 2012 г.

django-lfs: опыт использования. часть 2 (темы)

Итак, нам удалось запустить тестовый сервер тестового проекта. Понятно, что стандартный внешний вид нас не устроит.
Что можно сделать, чтобы его изменить?
Надо воспользоваться так называемым механизмом "схем". Схема - это обычное django приложение , которое надо создать самому, создать в нем структуру шаблонов, аналогичную структуре шаблонов стандартной lfs-схемы (см. приложение django-lfstheme)

cd /path/to/lfs_project
python manage.py startapp custom_lfstheme
cp -r /path/to/lfstheme/application/templates ./custom_lfstheme

далее в settings.py надо добавить зависимость от этого приложения (INSTALLED_APPS), поместив его ДО 'lfstheme':

INSTALLED_APPS = (
#...
    "custom_lfstheme",
    "lfstheme",
#...
)

Вот в общем-то вся подготовка к кастомизации внешнего вида. Далее надо просто видоизменять (переписывать с нуля) скопированные шаблоны.

Ну и для того, чтобы пополнить список "несовместимостей" с django 1.4: Может вылезти и обязательно вылезет следующая ошибка (при использовании) admin интерфейса lfs:
You cannot add messages without installing 
django.contrib.messages.middleware.MessageMiddleware
Лечится просто - добавляем нужные миддлваре в settings.py проекта:

MIDDLEWARE_CLASSES = (
#...
    'django.contrib.messages.middleware.MessageMiddleware',
#...
)

среда, 30 мая 2012 г.

django: flatpages error "Cannot use None as a query value" (En)

If you see "Cannot use None as a query value" error message in Django admin tying to save new Flatpage object, then just fill all mandatory fields. Probable you forgot to specify 'site' field.
It's very strange not to see standart django error message but the stack trace.. And it's a bug in Flatpages field validation code.
Moreover, this bug has been fixed (Ticket #18324).
p.s. stable version (c)

понедельник, 28 мая 2012 г.

django-lfs: опыт использования. часть 1 ( установка )

Потребовалось сделать небольшой проект - интернет-магазин на django.
Остановил свой выбор на решении django-lfs.
Беглый осмотр исходников навел на мысль о том, что поддержки новой django 1.4 пока нет - взять хотя бы структуру проекта. Она явно не соответствует той структуре , которую создает django_admin.
Также вижу, что в settings.py не все так, как принято теперь в дефолтном джанго-приложении.
Есть соблазн использовать django 1.3, но спортивный интерес и желание разобраться со всеми проблемами совместимости, которые вылезут, подталкивает начинать именно с 1.4...
Итак, что у нас имеется?
  1. Linux Debian Lenny 32 bit
  2. Python 2.7
  3. virtualenv
  4. django-lfs-installer-0.7.4.tar.gz
  5. MySQL
Сразу готовимся с будущему копипасту )) Создаем фейковый проектик из-под django 1.4 для того, чтобы под рукой иметь эталон. Очень будет полезно для приведения lfs-ного settings к потребному для 1.4 виду. Сделали? Двигаемся дальше.
Далее создадим бд (предполагается что сервер mysql уже крутится на сервере). Логинимся на локальный mysql-сервер под рутом субд и выполняем sql:

--создание базы данных
create database shop_db;
--создание пользователя и выдача ему всех прав на эту бд
grant all privileges on shop_db.* to shop_admin@localhost identified by 'very_hard_to_brake_password' with grant option;

Начнем.
Фазу установки виртуального окружения , распаковки архива, пожалуй , опущу. Отмечу только, что перед синхронизацией БД (syncdb) не забываем прописать настройки доступа к СУБД в settings.py. Остальные первичные телодвижения, которые надо сделать неплохо описаны здесь.
Естественно ни у кого никогда сразу все не ставится. Первое, что мне потребовалось доставить - модуль gunicorn :

#не забываем ставить пакеты при активированном вирт.окружении
easy_install gunicorn

Второе, на что ругнулся скрипт синхронизации БД - отсутствие MySQLdb. ИЗИ_инсталлами он не ставится. Качаем с surceforge последний тарболл MySQL-python-1.2.3.tar.gz , распаковываем, и ставим

wget "длинный дайрект линк с сорцфорж"
tar xvfz MySQL-python-1.2.3.tar.gz
cd MySQL-python-1.2.3
python ./setup.py install

Итак, база синхронизована (syncdb), магазин проиницализирован (lfs_init), тестовый сервер запущен. Открываем. И, судя по всему, видим первый "баг совместимости". В данном случае совместимости дефолтных настроек от джанги старых версий с новой джангой.

Module "django.core.context_processors" does not define a "auth" callable request processor

Исправляется просто. Открываем settings.py, ищем список TEMPLATE_CONTEXT_PROCESSORS , комментируем (если страшно)или удаляем :

TEMPLATE_CONTEXT_PROCESSORS = (
#...some processors here
    'django.contrib.auth.context_processors.auth',
#    'django.core.context_processors.auth',
#...some processors here
)

Запускаем тестовый сервер. Обновляем страницу. И, "следующий сказал заведующий". Другая ошибка:

Error importing template source loader django.template.loaders.filesystem.load_template_source: "'module' object has no attribute 'load_template_source'

Помните, я выше советовал создать проект-пустышку ? открываем его settings.py, копируем целиком оттуда список TEMPLATE_LOADERS и заменяем им аналог в settings.py нашего lfs-ного магазина.

#список шаблонозагрузчиков должен выглядеть так
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
#     'django.template.loaders.eggs.Loader',
)

Перезапускаем тестовый сервер или даем ему самому перезапуститься. И эврика, ничего толком не делали и уже видим готовый интернет-магазин )) Шучу, конечно, нам его еще допиливать и допиливать до "требований заказчика".
На этой мажорной ноте "часть первую", пожалуй закончу. Если у кого, вылезло, что-то еще в ходе описанных стадий, о чем я не упомянул - милости прошу поделиться)

четверг, 24 мая 2012 г.

flatpages валится в продакшене

Поразительно мрачный глюк сегодня уделал меня в flatpages!
Приложение в debug =True работает на ура. При debug = False то работает то нет! Пока понял закономерность - поседел.
Валились страницы , где рендерились flatpages. Понял , что в них дело , но как пофиксить никак не мог дотукать. Почитал матчасть и освежил особенность , связанную с обработкой 404. Простая страница будет рендериться, если не найдено ни одного подходящего урла. Да, только оказалось, что еше ей нужен фейковы 404.html в корне темплейтов. Ужас. Поди - догадайся!
Надеюсь кому-то помогло. p.s. да... еще проверьте, что у вас не переопределены хендлеры для 404, а то тоже не получится отрендерить простую страницу...

среда, 16 мая 2012 г.

django: flatpages error "Cannot use None as a query value"

Если при попытке сохранения flatpage из админки django вылезает страница с ошибкой "Cannot use None as a query value", то скорее всего вы не заполнили то или иное обязательное поле. И скорее всего - это поле site. Оно так неявно расположено и еще более неявно является обязательным, что все наступают на эти грабли.
А почему не срабатывает стандартный для админки контроль обяхательных полей - без страшных страниц со стеками? А потому что баг и в транке он уже пофикшен Ticket #18324
Просто не ожидаешь такого от контрибовских приложений для стабильной версии.
Ну ничего, бывает )

суббота, 12 мая 2012 г.

batch file encoding convertaion

If you need to change encoding of large amount of files in *nix you could find + iconv For example, you are in some site directory and wanna convert all html-files from cp1251 to utf8:

~$ find ./ -name "*.html" -exec iconv -f cp1251 -t utf8 {} -o {}.utf8 \;
~$ find ./ -name "*.html" -exec mv {}.utf8 -o {} \;
find ./ -name "*.html" -exec mv {}.utf8  {} \;

Good luck.

PHP: is_a(): Deprecated. Please use the instanceof operator

Попался тут один сайтик написанный на древней версии php , который пылился в архиве лет 5 и теперь должен быть восстановлен. Имеется в распоряжении хостинг с поддержкой php5. Сайт запустился с пол-пинка, но я постоянно ждал , что какие-нибудь проблемы с совместимостью все-таки вылезут. Так и получилось. В одном из сценариев выполение завершилось ошибкой:
is_a(): Deprecated. Please use the instanceof operator
Полез в код и заменил проблемную строку :

#if(!is_a($_m5_controls, "Tpl"))
if(!$_m5_controls instanceof Tpl)

Вот такая вот небольшая шпаргалка )

пятница, 20 апреля 2012 г.

KIckstart, виджеты vkontakte - и пара седых волос

Сегодня попросили запилить виджет ВКонтакте для отображения ифрейма одной из ВК-групп.
Ранее это я делал сотни раз - ну примитивнейшая задачка, которая сводится лишь к небольшому копипасту. Подключил нужный openapi.js от ВКонтакте.
Поместил нужный код (div+script) в нужное место нужного сайдбара и обновил страницу. Увидел два идентичных блока с данными группы. "Ха, ерунда" , подумал я , будучи уверен, что скопировал лишний div или что-то вроде того...
 Увидился, когда код оказался в порядке. И началось... Ну сплошная мистика - откуда второй блок - неясно.
 Забегая вперед - на пальцах и сейчас не объясню , откуда...  Но в итоге нашел "виновника"...
На данном сайте используется CSS - фреймворк Kickstart. Довольно практичная и удобная штука, надо сказать. Ну так вот. Этот фреймфорк идет вместе со своим js-тулкитом, в котором заплен функционал для простеньких слайдшоу, некоторая хитрая работа с DOM и т.д. Файл подключен в <head>. В консоль ошибками не плюется. Сам фреймворк отлажен и пользуется популярностью у существенного количества людей. Но вот ВКонтактовский виджет зачем-то клонирует) В итоге пришлось <script> , рисующий вконтактовский ифрейм помещать непосредственно перед </body> , чтобы не дать никому шанса все испортить...
Виджетов теперь ровно столько, сколько надо - один :)

p.s. какой именно код kickstart.js все испохабил я пока не понял.. будет время (а это врядли ) - раскопаю :)

среда, 18 апреля 2012 г.

django: совместимость с 1.4 : settings.py - ImproperlyConfigured: Error importing template source loader

В продолжении предыдущего поста о совместимости с django 1.4

Может возникнуть следующая проблема с загрузкой шаблонов при рендеринге:

ImproperlyConfigured: Error importing template source loader django.template.loaders.app_directories.load_template_source: "'module' object has no attribute 'load_template_source'"


Решение: закомментировать TEMPLATE_LOADERS в settings.py и скопипастить аналогичные настройки из settings.py , сгенерированного django-admin от версии 1.4

django: совместимость с 1.4 : diario - ImportError: No module named feeds

Не так давно вышел официальный релиз django 1.4
До его выхода кто-то дальновидно заранее начинал работать с пре-релиз версией и портировать на нее свои существующие проекты.

Те, кто предпочел использовать в повседневновсти стабильную версию 1.3 теперь либо продолжают ее использовать, либо потихоньку (ведь никто не гонит) , переводят проекты на 1.4.

При этом у каждого НЕИЗБЕЖНО возникнут большие и малые трудности..

Дело в том, что весь зоопарк сторонних django-приложений еще не успел актуализироваться вслед за измененями в django, которые появились в версии 1.4

В этом и быть может последующих постах буду более менее существенные моменты отражать - быть может этот опыт пригодиться кому-то быстрее решить проблему миграции..

1. django-diario - неплохое приложение для организаци блога. В нем я наткнулся на ошибку импорта в 13-й строке файла diario/feeds/entries.py:

  from django.contrib.syndication.feeds import Feed
ImportError: No module named feeds

Действительно, в версии 1.4 нет файла feeds - вместо него используется views - видимо так правильнее ) Имена сущностей те же.. то есть, теперь надо

from django.contrib.syndication.views import Feed


Проверил - в текущей версии diario проблема не решена , поэтому пришлось пока "ручками" поправить.

2. еще одно замечание про diario
Из-за того же рефакторинга django.contrib.syndication изменился и способ работы с фидами, которые предоставляет diario.
Если раньше вы подключали url-ы для фидов примерно так:

from diario.feeds.entries import RssEntriesFeed, AtomEntriesFeed
entries_feeds = {
        'rss': RssEntriesFeed,
        'atom': AtomEntriesFeed,
    }
#...
urlpatterns = patterns(
    url(r'^pub/(?P(rss|atom))/$', 'django.contrib.syndication.views.feed', {'feed_dict': 
entries_feeds}),
)


то теперь это надо делать так:

from diario.feeds.entries import RssEntriesFeed, AtomEntriesFeed
#...
urlpatterns = patterns(
    url(r'^pub/(?Prss)/$', RssEntriesFeed()),
    url(r'^pub/(?Patom)/$', AtomEntriesFeed()),
)



Удачи!

среда, 28 марта 2012 г.

BOOTSTRAP CSS FRAMEWORK: проблема с carousel ( Cannot read property 'end' of undefined)

Использую в одном из проектов css-фреймворк bootstrap от twitter.
Вчера обнаружил очередное обновление, вместе с которым появился js-плагин для слайдшоу.

Подключил его отдельным скриптом bootstrap-crousel.js и при прокрутке первого же тестового элемента слайдшоу получил ошибку в js-консоли:

Cannot read property 'end' of undefined
со ссылкой на 109 строку bootstrap-carousel.js

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

В частности от bootsrap-transitions.js.

Пришлось подключить и его.

А вообще , проще всего подключить весь bootstrap.js во избежание подобных багов в будущем.

четверг, 22 марта 2012 г.

nginx: unknown directive "uwsgi_pass"

Постепенно начинаю отказываться от apache как основного веб-сервера для своих проектов.
Альтернатива? Буду банален - nginx.

Решил описывать все камушки, каменюги , грабли и грабельки , на которые буду наступать в ходе освоения этого броузера.

Итак, первая "проблема":
На Debian 6 установил nginx из репозитория .
Настроил на взаимодействие с django проектом через uwsgi.
Первый запуск веб-сервера завершился ошибкой: nginx: unknown directive "uwsgi_pass"

проблема оказалась в том, что дефолтный веб-сервер в репозитории squeeze-а не содержит по дефолту модуль для работы с uwsgi.

Это изменено начиная с nginx версии 0.8. В репозитории 0.7.67 - так что все объяснимо.

Качаю исходники последнего стабильного релиза nginx:


cd /usr/src/nginx
wget http://nginx.org/download/nginx-1.1.9.tar.gz
tar xfz nginx-1.1.9.tar.gz
cd nginx-1.1.9
#подсмотрел рекомендуемые опции конфигурирования в инете :)
./configure --pid-path=/var/run/nginx.pid \
--conf-path=/etc/nginx/nginx.conf \
--sbin-path=/usr/local/sbin \
--user=www-data \
--group=www-data \
--http-log-path=/var/log/nginx/access.log \
--error-log-path=/var/log/nginx/error.log \
--with-http_stub_status_module \
--with-ipv6 \
--with-http_ssl_module \
--with-http_realip_module \
--with-sha1-asm \
--with-sha1=/usr/lib \
--http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ \
--http-proxy-temp-path=/var/tmp/nginx/proxy/ \
--http-client-body-temp-path=/var/tmp/nginx/client/ \
--with-http_geoip_module \
--with-http_gzip_static_module \
--with-http_sub_module \
--with-http_addition_module \
--with-file-aio \
--without-mail_smtp_module

#модулей много заюзали, так что у вас configure может ругнуться на отсутствие необходимых 
#для сборки библиотек..
#ставим все, что нужно

apt-get install libpcre3 libpcre3-dev openssl libssl-dev  libgeoip-dev libgeoip1

#после этого конфигурируется без ошибок

make
make install
mkdir /var/tmp/nginx #ибо при конфигурировании мы попросили nginx писать времянки именно сюда)
chown www-data /var/tmp/nginx #вместо www-data подставьте нужного пользователя


Да, дефолтную версию nginx лучше удалить.

вторник, 20 марта 2012 г.

django: range - цикл в шаблоне

Встроенный в django шаблонный движок , как оказалось, не поддерживает циклы по диапазону значений.
Например , в шаблон передается некоторое число и необходимо организовать цикл от 0 до этого числа - 1
Средствами встроенных шаблонных тегов и фильтров сделать это , судя по всему , нельзя. Но есть простое решение : передавть в шаблон не число а значение, возвращаемое встроенной python-функцией range.

Например там, где было

...
context['num'] = Somemodel.objects.count(),
...

надо сделать

...
context['num'] = range(Somemodel.objects.count()),
...

после чего в шаблоне можно уже делать вот так:

...
{%for i in num%}
...
{%endfor%}
...

четверг, 23 февраля 2012 г.

django: Internal Server Error при debug = False

Сегодня напоролся на неприятный "баг" в django-flatpages.
Суть его в том, что при отключенном дебаге (settings.debug = False) возникает ошибка при попытке отобразить url с flatpage.

Включаешь дебаг - страницы отображаются . Отключаешь - видишь 500-тку. Мистика.

Оказалось, что для решения проблемы потребовалось разместить в templates/flatpages файл default.html , наследующий базовый шаблон. Ни один из моих url не использует этот дефолтный шаблон, но тем не менее он оказывается краеугольным камнем проекта.

Глупо да? Я тоже так думаю...

вторник, 3 января 2012 г.

Отладка django+mod_wsgi: помощь при головной боли начинающего

Одной из первых проблем , с которыми сталкиваются начинающие django-разработчики при развертывании своих творений - это их отладка в продакшене. Например, такой вариант: Apache+mod_wsgi+django.
Вот в кратце список того, с чем пришлось столкнуться мне:
Предусловия:
1. В наличии имелся виртуальный сервер с Debian 5 Lenny , Apache2, python2.5 (из коробки) + доустановленный python 2.7.
2. Среди виртуальных хостов уже крутился один django-проект, использующий python2.5 и развернутый при помощи mod_python.
3. Доустановка нужного python 2.7 не доставила проблем, как и разработка и отладка с помощью встроенного в django веб-сервера. Когда настала пора деплоить проект на имеющийся apache, выбор пал на mod_wsgi.
4. Создан виртуальный хост и питоновский обработчик wsgi запросов от апача - все по мануалу, ошибиться было трудно.

Далее о многочисленных граблях, на которые пришлось наступить:

1. Установил mos_wsgi из репозитория - ничего не заработало.
Это потому что в репозитории lenny есть только модули , собранные для версии 2.5
Пришлось собирать mod_wsgi из исходников с нужной (2.7) версией python
2. Далее сглупил, собрав mod_wsgi без флага --enable-shared.
3. Потратил время, пытаясь запустить apache2 с виртуальными хостами, использующими разную версию python. Один - использует mod_python и сам интерпретатор версии 2.5. Другой (который пытаюсь развернуть) - только что собранный mod_wsgi для python 2.7. В такой конфигурации и без использования virtualenv так и не удалось заставить работать оба сайта. Причина оказалась не очень явная - mod_python всегда загружался первым и тянул за собой либы для 2.5 версии. И последующие ухищрения с настройками mod_wsgi и окружения не помогали. Решения было два: начать использовать virtualenv или пересобрать mod_python для версии 2.7 и запустить старый сайт с 2.7 версией python. Я выбрал второй вариант.
4. Долго пытался собрать mod_python версии 2.7.*, пока не понял, что бьюсь башкой о баг. Скачал версию 3.x.x , успешно собрал и установил , теперь уже не забывая про сборку именно шаред-библиотеки. После этого связку apache2+mod_wsgi можно было считать работающей.
5. Долго пытался отлаживать (путем print в стандартные потоки) причины 500-ток, пока не набрел на совет, как обернуть wsgi-приложение так, чтобы спроксировать дебаг-информацию в mod_wsgi. Вот какое решение (django.wsgi) мне помогло увидеть долгожданную дебаг-информацию в стиле django в окне броузера:

import os
import sys
sys.stdout = sys.stderr
# Add the virtual Python environment site-packages directory to the path
import site
sys.path.insert(0,'/www/djangoprojects/')
sys.path.insert(0,'/www/djangoprojects/testproject/')

print sys.path
os.environ['DJANGO_SETTINGS_MODULE'] = 'yourapp.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

from django.conf import settings

# Debug middleware
if settings.DEBUG:
    print >> sys.stderr, "Using Paste error middleware"

from paste.exceptions.errormiddleware import ErrorMiddleware
application = ErrorMiddleware(application, debug=True, show_exceptions_in_wsgi_errors=True)
print "here"
if settings.SESSION_FILE_PATH:
    try:
        os.makedirs(settings.SESSION_FILE_PATH)
    except OSError:
        pass



Предварительно надо установить paste:

sudo easy_install paste


6. После того, как появился нормальный дебаг в броузере первпя проблему, которую пришлось решить это Unable to Open Database File. Было ясно , что процессам апача не хватает прав на работу с файлом бд (речь идет о sqlite). Пермишены были добавлены, но ошибка осталась. Некоторое время ушло на понимание того, что надо дать еще права на директорию, в которой находится файл БД - такова особенность работы orm с данной СУБД.
7. после этого были подправлены некоторые директивы import по файлам проекта
8. статику тоже не сразу увидел (css, js) - не были настроены алиасы в виртуальном хосте. Вот, что было нужно добавить:
Alias /mediaurl /path/to/files

Order allow,deny
Allow from all


Вуаля, apache2+mod_wsgi-3.3.1+python2.7+django-1.3+sqlite успешно заработало.

Да , был забавный момент. В настройке WSGIScriptAlias зачем-то сдуру указал 30 потоков использовать и забыл об этом успешно. Долго не мог понять , почему моя vrs-ка стала загибаться. Чуть было не снес все , что переустановил. Вовремя заметил эту самонадеянную настройку и ограничился всего лишь 4 мя потоками.