Bers blog http://www.yablochkin.net/feed/ 2012-10-27T09:40:58Z Werkzeug PyCon UA 2012 http://www.yablochkin.net/pycon-2012/ 2012-10-27T09:40:58Z 2012-10-27T09:40:58Z Bers <p>На прошедших выходных посетил прекрасное мероприятие - <a href="http://ua.pycon.org/">PyCon Ukraine 2012</a>. В Киеве конференция проходит уже третий год, но попал я на неё впервые.</p> <p>На сайте мероприятия любой желающий может посмотреть записи докладов, от себя хочу отметить несколько особо запомнившихся, советую посмотреть:<br /><br /><br /> - Павел Коломиец. Request pipelining in python with greenlets<br /> интересно, но маленько странные бенчмарки, как мне показалось. Django работает не так медленно, по крайней мере по моим замерам. <br /><br /> - Alexey Kachayev. Functional Programming with Python<br /><br /> - Mikhail Korobov. Как писать быстрый и эффективный код на Python<br /> название не особо отражает суть. Доклад от автора pymorphy, рассказывает как ему удалось в разы упаковать базу слов и при этом быстро по ней искать.<br /><br /> - Олексій Васильєв. Использование PostgreSQL в высоконагруженных проектах<br /> докладчик рубист, но рассказал много интересного про PostgreSQL даже для тех, кто давно пользуется этой СУБД.<br /><br /> - Эдуард Снесарев, Роман Соколов. Elebal, Conchelita, Concierge и другие герои веб-выборов<br /> сделали стриминг с избирательных участков на последних выборах в России. Развеселили названиями компонентов системы.<br /><br /> - Vladimir Pouzanov. REPL Done Right<br /> рассказал про фичи ipython, о которых я и не догадывался.<br /><br /><br /> </p> <p>Ещё были доклады от django core, python core разработчиков и авторов таких известных среди питонистов штук, как pypy, south, gunicorn, flask-admin и прочих. Часть докладов была на английском. Большое спасибо организаторам, постараюсь побывать и в следующем году. </p> <p> update: теперь всё видео доступно тут - <a href="https://www.youtube.com/playlist?list=PL7sYAFudE7X1wb54NOiMaRkE5RUbJZrG5">https://www.youtube.com/playlist?list=PL7sYAFudE7X1wb54NOiMaRkE5RUbJZrG5</a> </p> Деплой Django с Nginx, UWSGI, supervisor, fabric http://www.yablochkin.net/deploy-django-with-nginx-uwsgi-supervisor-fabric/ 2012-08-04T09:31:33Z 2012-08-04T09:31:33Z Bers <p>Долгое время по старинке использовал fastcgi c django, на давно пора было перейти на более совершенный uwsgi. Основные преимущества - более производительный, graceful reload.</p> <p>Устанавливаем uwsgi в окружение проекта:</p> <pre><code>pip install uwsgi</code></pre> <p>Создаём конфиг для него - wsgi.ini:</p> <pre><code>[uwsgi] home=/home/project/env chdir=/home/project master=True disable-logging=True vacuum=True pidfile=/tmp/project.pid max-requests=5000 socket=127.0.0.1:49001 processes=2 pythonpath=/home/sites/project env=DJANGO_SETTINGS_MODULE=settings module = django.core.handlers.wsgi:WSGIHandler() touch-reload=/tmp/project.txt</code></pre> <p><b>home</b> - путь к виртуальному окружению, если вы его используете. Об остальных настройках можно подробнее почитать <a href="http://projects.unbit.it/uwsgi/wiki/Doc" target="_blank">тут</a>.<br> <b>touch-reload</b> - механизм плавного рестарта(graceful reload) проекта - все запросы будут обработаны при перезапуске, никто из клиентов не увидит 502. Для перезапуска нужно изменить файл по указанному пути, выполнив, например, touch /tmp/project.txt. Нормально перезапустить через supervisor к сожалению пока не получается, есть даже <a href="http://projects.unbit.it/uwsgi/ticket/123" target="_blank">тикет на эту тему</a>. Поэтому будем использовать эту фичу. Кстати о supervisor, через который я запускаю всякие процессы, вы можете почитать в <a href="http://www.yablochkin.net/supervisord/">соседнем посте</a>.</p> <p>Добавляем процесс в supervisord.conf:</p> <pre><code>[program:project] command=/home/project/env/bin/uwsgi /home/project/wsgi.ini stdout_logfile=/home/project/logs/wsgi.log stderr_logfile=/home/project/logs/wsgi_err.log autostart=true autorestart=true redirect_stderr=true stopwaitsecs = 60 stopsignal=INT</code></pre> <p>Выполняем команду:</p> <pre><code>supervisorctl update</code></pre> <p>Конфиг перечитывается и uwsgi запускается. Теперь можно настраивать nginx, пример конфига:</p> <pre><code>server { listen 80; server_name site.com; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log info; location / { uwsgi_pass 127.0.0.1:49001; include uwsgi_params; } location /media/ { alias /home/project/media/; expires 30d; } location /static/ { alias /home/project/static/; expires 30d; } }</code></pre> <p>Перезапускаем nginx и всё готово. Теперь настроим fabric для удобного обновления нашего проекта. Установим его в систему:</p> <pre><code>pip install fabric</code></pre> <p>Создаём файл fabfile.py в проекте. Я использую git и выглядит он у меня так:</p> <pre><code># -*- coding: utf-8 -*- from __future__ import with_statement from fabric.api import * from fabric.contrib.console import confirm env.hosts = ['root@host'] def deploy(): local('git push') code_dir = '/home/project' with cd(code_dir): run('git pull') #run('./manage.py compress') run('touch /tmp/project.txt') def celery(): run('supervisorctl restart project_celery') def status(): run('supervisorctl status') def uptime(): run('uptime')</code></pre> <p>Теперь находясь в проекте выполняем команду:</p> <pre><code>fab deploy</code></pre> <p>и все обновления из локального репозитория проекта перетекают на сервер и приложение перезапускается. По аналогии используем команды status и uptime(через него смотрю за la). Это намного удобнее чем вручную заходить на сервер, делать pull и тп.</p> <p>./manage.py compress - это django-compressor, про него, наверное, расскажу в следующем посте.</p> Разделитель в DecimalField - и точка, и запятая http://www.yablochkin.net/decimalfield-dot-and-comma/ 2012-05-29T13:25:13Z 2012-05-29T13:25:13Z Bers <p>Так как англоязычный мир использует в качестве разделителя десятичных дробей точку, у русскоязычных пользователей возникают некоторые проблемы. Начиная с Django 1.2 появилась возможность указать разделитель самостоятельно, но использоваться он будет только при выводе дробных, в формах же всё равно будет ожидаться только точка, даже если в локали ru-ru принята запятая.</p> <p>Какие неудобства это вызывает? Например, если юзер копирует дробное значение со страницы на сайте или из таблицы в экселе и вставляет его в форму. В этом случае он будет удивлён ошибке валидации формы - число с запятой за число вообще не считается. Конечно, можно просто забить, и использовать везде точку, но не обязательно.</p> <p>Выходов из ситуации несколько - либо заменять самостоятельно во входящих данных запятые на точки в классе формы, использовать модифицированный DecimalField, etc. Либо написать небольшой monkey patch, который будет делать то же самое, но во всех формах сразу, включая интерфейс администратора. Для этого добавим в project/__init__.py следующий код:</p> <pre><code>from django.forms import fields from django.utils.encoding import smart_str from django.utils import formats from django.core.exceptions import ValidationError from django.core import validators from decimal import Decimal, DecimalException def to_python(self, value): if value in validators.EMPTY_VALUES: return None if self.localize: value = formats.sanitize_separators(value) value = smart_str(value).strip() value = value.replace(',', '.') try: value = Decimal(value) except DecimalException: raise ValidationError(self.error_messages['invalid']) return value fields.DecimalField.to_python = to_python</code></pre> Автозагрузка своих шаблонных тегов в Django http://www.yablochkin.net/django-custom-template-tag-autoload/ 2012-04-03T11:26:27Z 2012-04-03T11:26:27Z Bers <p>Если вам надоело подключать в каждом шаблоне свой очень нужный шаблонный тег, строчкой вроде этой {% load mytags %}, то можно немного упростить свою жизнь таким вот образом:</p> <pre><code>from django.template.loader import add_to_builtins add_to_builtins('project.app.templatetags.mytags') </code></pre> <p>Теперь теги из mytags будут доступны во всех шаблонах. Но не стоит забывать - <a href="http://www.python.org/dev/peps/pep-0020/">Explicit is better than implicit</a>.</p> Ссылка из raw_id на объект в админке Django http://www.yablochkin.net/django-admin-raw_id-link/ 2012-01-09T08:49:50Z 2012-01-09T08:49:50Z Bers <p>Для работы с внешними ключами и большим количеством значений удобно использовать raw_id_fields - для выбора записи всплывает popup со стандартным listview, в котором легко выбрать нужную запись, так же можно использовать фильтры. Всё в этом варианте хорошо, но не хватает ссылки со страницы редактирования дочерней записи на родительскую, на выбранный объект. Можно написать какой-то хитрый виджет для внешних ключей, а можно и добавить ссылку прямо рядом с полем raw_id. В самом фреймворке такой возможности почему-то не заложено, поэтому добавим её сами. В admin.py вашего приложения добавляем такой monkey patch:</p> <pre><code>from django.contrib.admin.widgets import ForeignKeyRawIdWidget from django.core.urlresolvers import reverse from django.utils.text import truncate_words from django.utils.html import escape def url_to_edit_object(obj): url = reverse('admin:%s_%s_change' % (obj._meta.app_label, obj._meta.module_name), args=[obj.id]) return url def label_for_value(self, value): key = self.rel.get_related_field().name try: obj = self.rel.to._default_manager.using(self.db).get(**{key: value}) return &#39;&amp;nbsp;&lt;strong&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/strong&gt;&#39; % ( url_to_edit_object(obj), escape(truncate_words(obj, 14)) ) except (ValueError, self.rel.to.DoesNotExist): return '' ForeignKeyRawIdWidget.label_for_value = lambda x, y: label_for_value(x, y) </code></pre> <p>Выглядит как-то так, как и обычный raw_id_field, но со ссылкой:<br /> <img src="http://pix.am/rJye.png" /> </p> Мой шаблон django-проекта http://www.yablochkin.net/django-simpleskeleton/ 2011-11-28T16:03:27Z 2011-11-28T16:03:27Z Bers <p>Опубликовал ещё одну штуку от себя на github. На этот раз это эдакий skeleton, на основе которого я разрабатываю приложения - <a href="https://github.com/Bers/django-simpleskeleton">django-simpleskeleton</a>. Состоит из трёх папок:<br /><br /> - <b>build</b> - скрипт для сборки окружения и список зависимостей проекта<br /> - <b>etc</b> - примеры конфигов, на данный момент для supervisor и nginx<br /> - <b>project</b> - почти пустой джангопроект, с мелкими настройками и кусками кода, готовыми для копипаста<br /> </p> <p>Такая заготовка экономит время мне и, возможно, сэкономит вам. Шаблон рассчитан на использование с virtualenv, даже если вы не пользовались раньше этим прекрасным инструментом - всё делается достаточно просто. Нужен он для установки библиотек не прямо в систему, а в некое изолированное окружение. Такой подход позволяет избежать некоторых проблем, к примеру, когда на одном компьютере должно находиться несколько проектов, использующих разные версии одной и той же библиотеки. В файле build/requirements.txt лежит список приложений/библиотек для установки через pip. По умолчанию в нём уже находятся некоторые часто используемые мной вещи. Собирается окружение так:</p> <pre><code>./build/buildenv.sh</code></pre> <p> После успешного выполнения команды, должна создаться папка ./env/ с самим окружением. Важно, чтобы команда запуска сборки выглядело именно как в примере выше, т.к. используются относительные пути. Это, пожалуй, не очень красивое решение, но зато удобное. Для работы с django из окружения переходим в джангопроект - ./project/ и используем manage.py как-то так: </p> <pre><code>./manage.py runserver</code></pre> <p>В manage.py указан относительный путь к интерпретатору окружения из ./env/. </p> Как работает этот бложек http://www.yablochkin.net/how-it-works/ 2011-10-06T10:21:59Z 2011-10-06T10:21:59Z Bers <p>Залил исходники этого простенького сайта на <a href="http://github.com/Bers/homepage">github</a>. Большой ценности они не представляют, но, возможно, помогут интересующимся в GAE, python. В этом посте постараюсь осветить некоторые детали.</p> <p>GAE предоставляет кое-какие инструменты для разработки, например webapp Framework, который показался мне не очень удобным, но никто не запрещает использовать сторонние библиотеки на python. Я решил воспользоваться случаем и попробовать Flask, слышал о нём раньше, но близко не сталкивался, он оказался хорош.</p> <p>Вся домашняя страничка представляет собой очень минималистичный блог и пару статических страниц. Бложек хардкорный, и функции будут дописываться по мере необходимости, сейчас нет даже разделения на страницы, пока нечего разделять. Всё что есть, это - добавление/редактирование постов без wysiwyg, фид, слаг, подсветка исходников через highlight.js, комментарии - disqus, которые подумываю убрать. После создания нового поста он отмечается как "скрыт" - эдакий вариант обязательного предпросмотра. В общем продукт только для себя. Авторизацию сделали за нас через гугл аккаунты, администратором считается владелец приложения. Ссылки на авторизацию нет, если что - /login/.</p> <p>Из странного отмечу то, что приложение можно повесить только на субдомен, именно поэтому сейчас в адресе этой странички вы видите www. Максимум, что можно сделать, так это редирект с yablochkin.net, на нужный субдомен, для этого есть специальный инструмент.</p> <p>Так как прямого доступа к облаку нет, и поставить дополнительные библиотеки на сервер гугл нельзя, приходится носить всё вместе с проектом. Flask и прочее сейчас лежат в /project/lib/, лучше, конечно, оформить их в git как ссылки на репозитории - submodules, а не тянуть весь код в проект.</p> <p>Теперь кратко о запуске. Предполагается, что в /google_appengine/ - лежит сам Google App Engine SDK, его надо будет скачать. ./run.sh - запуск сервера для разработки. ./deploy.sh - заливка проекта в облако. При заливке система спросит email и пароль владельца приложения.</p> <p>Как установить? <a href="https://appengine.google.com/">Регистрируйтесь</a> в gae, создайте приложение через вебморду, при создании указывается уникальное имя приложения, вместе с которым даётся бесплатный домен третьего уровня вида *.appspot.com. Измените имя приложения в исходниках проекта /project/app.yaml - первая строчка - application: eyablochkin. Теперь можно запускать deploy.sh. Готово, сайт уже в интернете.</p> Контролируем Django и прочих через Supervisord http://www.yablochkin.net/supervisord/ 2011-09-08T11:02:17Z 2011-09-08T11:02:17Z Bers <p>Для удобного запуска и контроля над приложениями на продакшене я использую <a href="http://supervisord.org/">supervisord</a>. Перейду сразу к делу и покажу как это работает. После простой настройки сам процес выглядит примерно так:</p> <pre><code class="bash">$ supervisorctl status projectname_celery RUNNING pid 6704, uptime 1 day, 22:29:57 projectname_fcgi RUNNING pid 29016, uptime 20:59:24 $ supervisorctl restart projectname_fcgi $ supervisorctl stop projectname_fcgi $ supervisorctl start projectname_fcgi $ supervisorctl tail projectname_celery [2011-09-08 07:16:26,636: INFO/MainProcess] Got task from broker: project.teasers.tasks... ... </code></pre> <p>Supervisord позволяет демонизировать что угодно, я использую его для запуска django, celery, tornado. Эта замечательная утилита проста в настройке, к тому же написана на питоне, поэтому установить её можно через setuptools. В репозитории CentOS была только старая ветка 2.*, поэтому поставим последнюю версию 3.*, между 2 и 3 есть обратные несовместимости. </p> <pre><code class="bash">$ pip install supervisor</code></pre> <p>Добавляем <b>supervisord</b> в автозапуск, например в /etc/rc.local или каким-либо другим способом, возможно скрипт в /etc/init.d/.</p> <p>Конфиг по умолчанию лежит в /etc/supervisord.conf. Мой выглдит так: </p> <pre><code class="ini">[unix_http_server] file=/tmp/supervisor.sock ; (the path to the socket file) [supervisord] logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) logfile_backups=10 ; (num of main logfile rotation backups;default 10) loglevel=info ; (log level;default info; others: debug,warn,trace) pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) nodaemon=false ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket [program:projectname_fcgi] command=/home/projectname/project/manage.py runfcgi method=prefork maxchildren=6 maxspare=8 minspare=4 maxrequests=1000 host=127.0.0.1 port=8000 daemonize=false stdout_logfile=/home/projectname/logs/fcgi.log stderr_logfile=/home/projectname/logs/fcgi_err.log autostart=true autorestart=true redirect_stderr=true [program:projectname_celery] command=/home/projectname/project/manage.py celeryd --loglevel=INFO -B stdout_logfile=/home/projectname/logs/celery.log stderr_logfile=/home/projectname/logs/celery_err.log redirect_stderr=true autostart=true autorestart=true startsecs=5 ; Need to wait for currently executing tasks to finish at shutdown. ; Increase this if you have very long running tasks. stopwaitsecs = 60</code></pre> <p>Для manage.py runfcgi обязательно нужно добавить параметр daemonize=false.</p> <p>По аналогии добавляем новые блоки [program:*] для запуска других своих приложений. После правки конфига не забываем перезапустить supervisord. Примечателен также параметр autorestart - в случае остановки процесса по каким-то причинам, supervisord перезапустит его. </p> <p>Если понадобилось изменить конфиг супервизора, например, добавить процесс, перезапускать его не обязательно, можно перечитать конфиг командой supervisorctl update.</p> Регистрация в Django с авторизацией по email http://www.yablochkin.net/django-simplereg/ 2011-08-30T13:02:08Z 2011-08-30T13:02:08Z Bers <p>В последнее время в разрабатываемых приложениях использую только авторизацию по email. Это упрощает регистрационную форму, да и вообще, как мне кажется, удобнее для пользователя - ему не нужно придумывать уникальный логин, у него уже есть уникальный email. К тому же я не люблю активацию акаунта через почту, в большинстве случаев без неё можно прекрасно обходиться.</p> <p>Для того, чтобы не копировать одно и то же в разных проектах, оформил всё, что нужно в отдельное приложение и залил на github - <a href="http://github.com/Bers/django-simplereg">django-simplereg</a>. </p> <p>Простая установка в три шага. Первый - устанавливаем само приложение. <pre><code>pip install -e git://github.com/Bers/django-simplereg.git#egg=simplereg </code></pre></p> <p>Добавьте simplereg в INSTALLED_APPS. Для авторизации по email надо добавить backend в settings.py, если оставить 'django.contrib.auth.backends.ModelBackend', то авторизация по юзернейму тоже будет работать. <pre><code>AUTHENTICATION_BACKENDS = ( 'simplereg.backend.EmailAuthBackEnd', 'django.contrib.auth.backends.ModelBackend', ) </code></pre></p> <p>Правим urls.py. Выглядеть должен как-то так: <pre><code>from django.conf.urls.defaults import * from simplereg.forms import LoginForm urlpatterns = patterns('', url(r'^registration/$', 'simplereg.views.registration', { 'template_name': 'registration.html', 'autologin': True, 'callback': None }, name='registration'), url(r'^login/$', 'django.contrib.auth.views.login', { 'authentication_form': LoginForm }, name='login'), ... ) </code></pre></p> <p>После регистрации пользователь будет сразу авторизован, если вам не нравится такой подход измените значение autologin на False. URL после регистрации/авторизации указывается в шаблоне полем next. Примеры шаблонов можно посмотреть в исходниках.</p> Как подружить Sentry и Celery http://www.yablochkin.net/sentry-and-celery/ 2011-08-27T11:01:18Z 2011-08-27T11:01:18Z Bers <p>django-sentry - приложение, которое отлавливает и сохраняет ошибки в базу данных.</p> <p>На get.2leep.com я использую celery для выполнения некоторых задач в фоне. По умолчанию, если какой-нибудь таск из celery возвращает ошибку, она вываливается в консоль, это не очень удобно. Есть возможность сохранять результаты всех тасков в базу, там ошибки тоже будут видны, но будет проще, если всё будет находиться в одном месте, тем более, что у sentry приятный интерфейс для их просмотра.</p> <p>Для решения этой проблемы можно использовать такой вот код, который нужно добавить в tasks.py, чтобы celery сохранял все ошибки в sentry</p> <p> <code><pre> from sentry.client.handlers import SentryHandler from celery.signals import task_failure import logging logger = logging.getLogger('task') logger.addHandler(SentryHandler()) def process_failure_signal(exception, traceback, sender, task_id, signal, args, kwargs, einfo, **kw): exc_info = (type(exception), exception, traceback) logger.error( 'Celery job exception: %s(%s)' % (exception.__class__.__name__, exception), exc_info=exc_info, extra={ 'data': { 'task_id': task_id, 'sender': sender, 'args': args, 'kwargs': kwargs, } }) task_failure.connect(process_failure_signal) </pre></code> </p> Первый http://www.yablochkin.net/first/ 2011-08-26T12:44:18Z 2011-08-26T12:44:18Z Bers <p>Этот небольшой бложек - результат опытов с google app engine.</p> <p>GAE, для тех кто не в курсе, это такой облачный хостинг веб-приложения от Google. Приложения можно писать на java, python и go. Сам я, из перечисленных, хорошо знаком только с питоном, поэтому на нём и был написан блог. Из удобного можно отметить простой деплой, отсутствие необходимости самостоятельно что-то админить и хорошие бесплатные квоты. Но и в рамки сильно загоняют - как на обычном сервере поставить, что хочется не получится. Используется своя база данных BigTable, не реляционная, без схемы, поэтому использовать, например, django без костылей не получится. В наличии есть джанго 0.96 адаптированная для gae, сам не пробовал. Можно загружать любые либы на чистом питоне. Так что для чего-то небольшого вполне можно использовать.</p> <p>Вместо привычной для меня джанги, здесь использовал <a href="http://flask.pocoo.org/">flask</a> - минималистичный фреймворк объединяющий в себе Werkzeug и jinja2. На гитхабе нашёл <a href="https://github.com/blossom/flask-gae-skeleton">удобный костяк</a> flask проекта для gae, который и заюзал. Кстати, jinja2 удобная штука, особенно на фоне стандартных джанговских шаблонов, к тому же, как говорят, намного быстрее.</p> <p>На этом пока всё.</p>