среда, 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 мя потоками.

среда, 28 декабря 2011 г.

Django: "Кто на сайте" ?


В одном из проектов понадобилось реализовать функцию "Кто на сайте". Средней продолжительности копания привели к модулю django-onlineiser.


$sudo easy install django-onlineuser


далее в settings.py:

#добавляем приложение в список используемых в проекте
INSTALLED_APPS += ('oblineuser')

#регистрируем его middleware в соответствующем списке
MIDDLEWARE_CLASSES += ('onlineuser.middleware.OnlineUserMiddleware')



в нужном html шаблоне подгружаем теги этого приложения и там же отображаем html-код этой приблуды

{% load onlineuser_tags %}
Кто здесь?
{%onlineinfos%}


У меня, чтобы заработало пришлось немного модифицировать код класса из middleware.py, обернув имеющийся там код в try except

class OnlineUserMiddleware:
    def process_request(self, request):
        try:
            user = request.user
            ip=request.META['REMOTE_ADDR']
            if user.is_authenticated():
                o, created = Online.objects.get_or_create(user=user)
                o.session_key = ip
            else:
                o, created = Online.objects.get_or_create(ident=ip)
                o.session_key = ip
            if not created:
                o.save()
        except:
            return


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

С первого взгляда все ок и автору приложения в любом случае спасибо.

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

Не совсем релевантно получается.

Я допилил это приложение с помощью сигналов о логине и логауте:


#этот код я добавил в middleware.py рассматриваемого приложения, хотя можно и любое другое удобное место 
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver
@receiver(user_logged_in)
def count_logins(sender,request,user,**kwargs):
    ip=request.META['REMOTE_ADDR']
    g=Online.objects.get(session_key=ip, user=None)
    if(g):
        g.delete() #удаляем гостя , после того , как он стал залогиненным юзером
    #создаем или обновляем запись в таблице приложения (этот код я скопипастил из имеющегося миддлваре-класса
    o, created = Online.objects.get_or_create(user=user) 
    o.session_key = ip
    if not created:
        o.save()    
 
@receiver(user_logged_out)
def count_logouts(sender,request,user,**kwargs):
    o=Online.objects.get(user=user) 
    o.delete() #сразу при логауте удаляем запись из таблицы



Если в ближайшее время не придумаю как более красиво это все дело улучшить, отпишу автору или посто форкну это приложение )

четверг, 1 декабря 2011 г.

Django: Добавляем свои собственные проверки объектов в админку



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

Начиная с версии 1.2 в Django позволяет добавлять в модель метод clean, в котором и предлагается реализовывать логику нужных вам проверок.

Например у вас есть таблица "Матч" , в полях которой есть две ссылки (ForeignKey) на таблицу "Команда". То есть играют две команды ) Сама с собой команда, понятно, играть не может. По крайней мере в тех командных видах спорта , о которых доводилось слышать )

Код проверки будет примерно прост (добавить метод в класс модели "Матч"):


    def clean(self):
        from django.core.exceptions import ValidationError
        if(self.team1.id == self.team2.id):
            raise ValidationError('Команда не может играть сама c собой.')            

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

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

Если кто уже в курсе - буду признателен за ответ. Хотя кому это я, читателей пока у меня судя по всему нет )))))

воскресенье, 27 ноября 2011 г.

Django: пишем свой менеджер модели


Недавно натолкнулся на часто востребованную операцию "получить следующее по дате" в моих Django-моделях. Встроенный менеджер objects имеет прекрасный метод latest. Симметричного ему , что-то типа next, я не нашел (может плохо искал).
И так как сейчас я как раз в самом разгаре прокачки django-скиллов (подобно новому персонажу в diablo , когда ну очень много нового и очень быстро происходят level-апы :), то я с радостью покопал тему расширения функционала менеджера и сделал нужный мне метод.



#models.py

#Менеджер (обратите внимание на базовый класс)
class EventManager(models.Manager):
    def next(self):
        import datetime
        try:
            return Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]
        except:
            return None
#Модель, для которой нужен метод next
class Event(models.Model):
    title = models.CharField()
    date  = models.DateTimeField()
    objects = MatchManager() #Явно указываем, кто нами рулит


После этого в соответствующем view я спокойно могу использовать:

Event.objects.next(), что гораздо удобнее и менее хрупко , чем конструирование фильтра.

Пара пояснений, любая модель, пронаследованная явно или опосредованно от models.Model имеет поле objects, значением которого является встроенный менеджер модели (объект класса models.Manager), который имеет кучу полезных методов. Мы создали класс-потомок и чуток его дополнили.
При желании можно параметризовать поле, по которому будет искать объекты метод next - подобно тому, как это делает latest. Но пока этого не требовалось, буду тратить время на что-то другое.
Что такое менеджер модели, во всяком случае, мне стало чуток понятнее =)