Archive for July, 2009

Django 1.1 зарелизился

Вышел релиз Django 1.1. Список новых фич и несовместимостей со старыми версиями можно и нужно прочитать тут.

Несколько фишек, которые мне приглянулись.

Опция admin класса list_editable позволяет задавать поля модели, которые можно будет редактировать прямо в списке объектов. Подробности.

Тэг {% for %} может содержать {% empty %} тэг, который выполнится в случае, когда список для итерации был пустой. Подробности с примерами тут.

Команда dumpdata позволяет теперь делать дамп конкретной модели в приложении.

Новая функция django.shortcuts.redirect упрощяет работу с редиректами. Она принимает в качестве аргумента URL или имя urlpattern или объект модели. Подробности.

Чистим сессии в django сайте

In human-computer interaction, session management is the process of keeping track of a user’s activity across sessions of interaction with the computer system … Session management is particularly useful in a web browser where a user can save all open pages and settings and restore them at a later date. To help recover from a system or application crash, pages and settings can also be restored on next run. From: Wikepedia

За работу с сессиями в Django отвечает приложение django.contrib.sessions. По умолчанию оно хранит сессии в базе данных. Django не производит автоматическую чистку этой таблицы. Это значит, что если к вам на сайт зашёл Вася, то для него создалась сессия, Вася посмотрел и закрыл вашу страничку. Если вы ничего не делали специально с базой данных, то через год информация о сессии Васи по прежнему будет храниться в базе данных. Для Коли и других пользователей, браузеры которых поддерживают cookies, ситуация аналогична :-)

Будем чистить! Django предоставляет команду manage.py cleanup — она удаляет те сессии, время жизни которых слишком большое. Нужно всего лишь вызывать по крону эту команду. Если у вас много сайтов на вашем сервере, то есть смысл написать shell скрипт. У меня каждый сайт лежит в каталоге /web/site_name/, поэтому я сделал себе такой скрипт:

#!/bin/sh
WEB_ROOT="/web"

for dir in $(find $WEB_ROOT/* -maxdepth 0 -type d); do
    cd $dir
    if [ -f $dir/manage.py ]; then
        echo "Cleaning up the $dir"
        ./manage.py cleanup
    fi  
done

Если вы никогда не чистили таблицу сессий, то команда может работать достаточно долго в первый раз. Если вам нужна скорость, просто выполните SQL DELETE запрос с условием на возраст сессии или обыкновенный TRUNCATE.

Разгоняем django сайт с помощью staticgenerator

Я неспешно переезжаю на VDS от gandi.net. Был дедик с объёмом памяти один гиг, а переезаю на 256 метров. Пока не знаю, удастся ли втиснуться. Конечно же, возникают вопросы, а можно ли убыстрить мои сайты, заставить их жрать меньше памяти. Сегодня ночью я сражался с web-brains, а точнее с byteflow, на котором они работают. Выяснилось, что более одного запроса в секунду apache/mod_wsgi демон не обрабатывает, хоть ты тресни. Я пока не понял, во что именно упирается byteflow, но это не важно в рамках данного сообщения. Решил я закешировать веб-мозги, да так шобы прям в усмерть закешировать. Как раз вчера один знакомый упоминал staticgenerator и я понял — самое время призвать на помощь его мощности статическогенерические.

Идея, лежащая в основе staticgenerator оень простая и дерзкая. Адрес веб-страницы похож на файловый путь. Когда мы запрашиваем страницу /2009/08/07/hello-world, то staticgenerator создаёт в заданном каталоге такую же структуру т.е. каталог 2009, в нём каталог 08, в нём каталог 07, в нём каталог hello-world, а в нём файл index.html, куда и записывается содержимое сгенерированной страницы. Всё это делает специальная middleware. Далее начинается самое интересное, мы настраиваем веб-сервер так, чтобы он смотрел в заданном каталоге закэшированный файл и если там он есть, то файл выдаётся пользователю и джанго-демон даже не дёргается. Вот такие пироги :)

Далее я опишу технические детали того, как я прикрутил staticgenerator конкретно к byteflow.

Для начала неплохо установить staticgenerator через easy_install

Далее идём в settings_local.py проекта и пишем там такое в конце:

# Здесь мы просим staticgenerator складывать кэш файл в каталог нужный
WEB_ROOT = '/web/cache/staticgenerator/web-brains.com'

# Это регулярные выражения. Будут кэшироваться только те страницы, которые
# удовлетворяют выражениям. Я поковырялся в urls файлах byteflow и выбрал те,
# которые относятся к непосредственному отображению данных
STATIC_GENERATOR_URLS = (
    r'^/$',
    r'^/feeds',
    r'^/\d',
    r'^/tag',
    r'^/tags',
    r'^/featured',
    r'^/author',
    r'^/sitemap\.xml',
    r'^/robots\.txt',
)

# Так можно в byteflow добавить новую middleware из setttings.py
ADDITIONAL_MIDDLEWARE= ('staticgenerator.middleware.StaticGeneratorMiddleware',)

Ещё я в settings.py добавил cache_manager в ADDITIONAL_APPS. А что такое cache_manager? А это такое приложение, которое сигналы ловит при создании нового поста или нового комментария и грохает весь кэш. Можно, конечно, хорошо подумать и написать очищение только изменившихся страниц, а можно просто грохнуть быстренько всё :) Я запихал код в cache_manager/__init__.py — таким образом я добился его выполнения при старте сайта т.е. установки нужных обработчиков сигналов.

from subprocess import Popen from staticgenerator import quick_delete

from django.db.models.signals import post_save
from django.conf import settings

from blog.models import Post
from discussion.models import CommentNode

def clear_cache(**kwargs):
    Popen('rm -rf %s/*' % settings.WEB_ROOT, shell=True)

post_save.connect(clear_cache, sender=Post)
post_save.connect(clear_cache, sender=CommentNode)

Код элементартный. Если создали Post или CommentNode — грохаем кэш!

Так, так. Вот мы сайт настроили. Осталось веб-сервер настроить. Веб-сервер у нас, конечно, nginx. У меня такой конфиг для веб-мозгов:

server { server_name .web-brains.com; access_log /var/log/www/web-brains.com-access.log; error_log /var/log/www/web-brains.com-error.log warn;

    include fastcgi_params;

    location /admin-media {
        alias /home/web/lib/django_src/django/contrib/admin/media;
    }
    location /static/ {
        root /web/web_brains;
    }

    # Нас интересует ниженарисованный кусок конфига
    # Делаем корнем для статики тот каталог, куда
    # staticgenerator кэш пишет.
    root /web/cache/staticgenerator/web-brains.com;
    location / {
        # Всё что не GET, отдаём бэкенду. У меня это apache/mod_wsgi
        if ($request_method != GET) {
            proxy_pass http://127.0.0.1:8080;
            break;
        }
        # Вот тут чуток меняем путь поиска т.к. staticgenerator
        #  пишет кэш в index.html файлы
        if (-f $request_filename/index.html) {
            rewrite (.*) $1/index.html break;
        }
        # Если файл таки не нашёлся, идём в бэкенд
        # иначе ничего не делаем и благополучно выкидываем
        # закэшированный файл пользователю
        if (!-f $request_filename) {
            proxy_pass http://127.0.0.1:8080;
            break;
        }
    }
}

Гм, ну вот и всё. Ща сохраню статью и заодно проверю, что кэш грохнулся. Я запускал ab тесты прямо на VPS. Раньше морда веб-мозгов раздавалась со скростью 1page/second, а щас 668pages/second. Клёво :)

Деплоймент python проекта: pip & virtualenv

При работе над 3task мы разработали удобный способ установки зависимостей для проекта. Америку я не открою, просто расскажу, что мы сделали. Надеюсь, кому-нибудь пригодится.

Зависимости обрабатываются с помощью двух инструментов: pip и virtualenv.

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

Pip — это инструмент для установки python-приложений. Грууубо говоря, это аналог easy_install, отличающийся рядом вкусных плюшек. Одна из убойных его фишек — возможность ставить софт прямо из репозитория. Я пробовал ставить из git, svn, hg — всё работает. Возможно, easy_install умеет что-то подобное, я не в курсе, честно говоря. Другие отличительные черты pip: поддержка virtualenv и возможность ставить софт по заранее составленному списку.

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

Файлик build/buildenv.sh руководит всем процессом:

#!/bin/sh
echo Creating environment
virtualenv .env

echo Install PIP inside virtual environment
./.env/bin/easy_install pip

echo Installing dependencies
./.env/bin/pip install -E .env -r ./build/pipreq.txt

То есть сначала создаём виртуальное окружение с помощью инструмента virtualenv. Далее устанавливаем pip через easy_install. Можно, конечно и средствами пакетного менеджера вашей ОС, но easy_install стоит почти везде, так что грех этим не воспользоваться. Третий шаг — установка необходимых библиотек в виртуальное окружение по списку.

Приведу пример файла зависимостей:

-e svn+http://code.djangoproject.com/svn/django/trunk@10748#egg=django
-e svn+http://django-filebrowser.googlecode.com/svn/trunk@358#egg=django-filebrowser
-e svn+http://sorl-thumbnail.googlecode.com/svn/trunk@449#egg=solr
-e svn+http://django-tagging.googlecode.com/svn/trunk@156#egg=django-tagging

-e hg+http://bitbucket.org/lorien/django-account#egg=django-account
-e hg+http://hg.barbuza.info/supercaptcha/#egg=supercaptcha
-e hg+http://bitbucket.org/lorien/pybb#egg=pybb

django-renderform
simplejson
markdown
beautifulsoup
http://django-tinymce.googlecode.com/files/django-tinymce-1.5.tar.gz

Как видите, источники можно указывать самые разные: адреса hg/git/svn репозиториев, адреса архивов, просто имена (поиск идёт через pypi.python.org). Единственное условие: пакет по указанному адресу должен обязательно содержать файл setup.py.

Если специально не запрещать, то пакет ищется сначала в виртуальном окружении, в случае неудачи — в глобальном окружении. Это мне помогло в случае PIL. Почему-то не хотел он компиляться при установке через pip — я поставил его через apt. Драйвера баз данных типа mysqdb мне тоже кажется удобным ставить отдельно — в глобальную область видимости.

Можно почитать по теме:

Интегрируем django admin interface и wysiwyg редактор xinha

Недавно понадобилось мне воткнуть WYSIWYG-редактор в джанго-админку для редактирования содержимого новостей и статей. Решено было использовать xinha. Порыскав минут 10-20 в сети я так и не нашёл статью, которую читал год назад, — в ней было описано как интегрировать по-красивому сложные виджеты в админку. Пришлось придумывать самому.

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

Итак, для начала нужно скачать редактор xinha с сайта, распаковать архив и засунуть его содержимое, например, в /static/xinha

Далее, пускай у нас есть модель News, в которой нам нужно редактировать поле content. В news/admin.py создаём админ-класс и говорим, что хотим использовать особую форму.

class NewsAdmin(admin.ModelAdmin):
    form = NewsAdminForm

Далее, создаём особую форму.

class NewsAdminForm(forms.ModelForm):
    class Meta:
        model = News

    def __init__(self, *args, **kwargs):
        super(NewsAdminForm, self).__init__(*args, **kwargs)
        self.fields['content'].widget = XinhaWidget()

В этой форме мы меняем только виджет для поля content, всё остальное за нас делает ModelForm. Другими словами форма будет похожа на стандартную за исключением поля content.

Теперь нам нужно описать класс виджета.

from django.utils.safestring import mark_safe
from django import forms

XINHA_CODE = """
<script type="text/javascript">
var xinha_editors = ['id_content'];
</script>
"""

class XinhaWidget(forms.Textarea):
    def render(self, *args, **kwargs):
        out = super(XinhaWidget, self).render(*args, **kwargs)
        return mark_safe(XINHA_CODE + out)

    class Media:
        js = ('js/xinha_prepare.js', 'xinha/XinhaCore.js', 'js/xinha_configure.js')

Здесь мы опять же берём за основу готовую вещь — виджет Textarea и дописываем после HTML-кода маленький javascript, который изменяет глобальную переменную xinha_editors — она будет использована после полной загрузки страницы большим и жирным javascript-кодом, который подгружает и настраивает редактор xinha.

Обратите внимание на class Media. Он говорит джанге, что на странице, где используется наш виджет, нужно подключить эти скрипты (в head секции страницы).

В Media мы объявляем три скрипта. Что значит второй, думаю, всем понятно. Первый скрипт делает небольшую подготовку к загрузке xinha — объявляет язык и базовый URL:

_editor_url = ‘/static/xinha/’ _editor_lang = ‘ru’

Далее грузится второй скрипт — потроха Xinha. И в третьем скрипте мы используем подгруженные библиотеки — создаём реальные объекты редактора Xinha и настраиваем их.

У меня третий скрипт (xinha_configure.js) получился таким. Он достаточно большой (жирнючие комментарии) — я редактировал болванку из документации Xinha — так что я вынес код на dumpz.org.

Вот и всё. Теперь грузим админку и радуемся интегрированной xinha :-)

UPD: Нашёл пост, который давно читал, вот он: A WYSIWYM editor widget for Django’s admin interface