<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Маниакальный Веблог &#187; Django</title>
	<atom:link href="http://softwaremaniacs.org/blog/category/django/feed/" rel="self" type="application/rss+xml" />
	<link>http://softwaremaniacs.org/blog</link>
	<description>Иван Сагалаев о программировании и веб-разработке</description>
	<lastBuildDate>Mon, 22 Jun 2009 12:19:29 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Частные проблемы использования django-evolution</title>
		<link>http://softwaremaniacs.org/blog/2009/06/17/evolution-pain-points/</link>
		<comments>http://softwaremaniacs.org/blog/2009/06/17/evolution-pain-points/#comments</comments>
		<pubDate>Tue, 16 Jun 2009 22:17:06 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=693</guid>
		<description><![CDATA[Сегодняшний день для меня ознаменовался эпичной битвой с django-evolution. С различными неудобствами этого приложения мы в разработке сначала Куда Все Идут, а потом Афиши сталкиваемся уже довольно давно. Сегодняшний же случай наконец заставил меня перебороть лень и изложить эти проблемы для публики.

Должен, тем не менее, обязательно сказать, что не считаю django-evolution плохим средством, потому что [...]]]></description>
			<content:encoded><![CDATA[<p>Сегодняшний день для меня ознаменовался эпичной битвой с <a href="http://code.google.com/p/django-evolution/">django-evolution</a>. С различными неудобствами этого приложения мы в разработке сначала <a href="http://kuda.yandex.ru/">Куда Все Идут</a>, а потом <a href="http://afisha.yandex.ru/">Афиши</a> сталкиваемся уже довольно давно. Сегодняшний же случай наконец заставил меня перебороть лень и изложить эти проблемы для публики.</p>

<p>Должен, тем не менее, обязательно сказать, что не считаю django-evolution плохим средством, потому что знаю, что во многих сценариях использования он вполне справляется со своими обязанностями. Однако архитектурные особенности нашего проекта и наши рабочие привычки, видимо, совмещаются с ним плохо. Поэтому принимайте текст как есть, без далеко идущих выводов, просто к сведению.</p>

<p><span id="more-693"></span></p>

<h2>Очерёдность с syncdb</h2>

<p>Одно из выбранных решений django-evolution &#8212; невмешательство в дела команды syncdb. То есть, django-evolution никогда не пытается выполнять SQL код для создания таблиц.</p>

<p>Первое неудобство в этом для нас &#8212; более сложный процесс составления миграционного SQL&#8217;а. Дело в том, что наши админы не шибко любят запускать изменения в базах всякими неконтролируемыми тулзами, а просят присылать им SQL-скрипты. Django-evolution умеет генерить SQL своих эволюций, а SQL для создания таблиц приходится вырезать руками из базы отдельно.</p>

<p>Но гораздо хуже другое. Представьте, что в процессе миграции базы с достаточно старой версии есть и новые модели, и эволюции, применяемые к этим моделям. Поскольку создавать таблицы надо отдельной командой, надо помнить, что этот SQL должен обязательно идти до эволюций. Несколько муторно. Теперь представьте, что среди эволюций есть переименование одной таблицы (соответствующее переименованию модели). С точки зрения syncdb таблицы для нового имени модели нет, и она её создаст. От этого эволюция переименования накроется, потому что таблица уже существует.</p>

<p>Приехали &#8212; есть эволюции, которые надо запускать до syncdb, а есть те, которые после. Соответственно, процесс превращается в совсем ручной cherry-picking.</p>

<p>Были ещё какие-то частные случаи, когда важен порядок применения syncdb и evolve, и в итоге, чтобы избавиться от этих проблем, мы стали вносить создания таблиц в эволюции вручную.</p>

<h2>Написание симуляций</h2>

<p>Кто не знает, в django-evolution есть интересная фича: она умеет не применять эволюции непосредственно к базе, а прогнать их сначала вхолостую (&#8221;simulate&#8221; в терминах приложения). Делается это не реально в БД, а спомощью выполнения специально написанных функций, которые меняют <dfn>сигнатуру проекта</dfn> &#8212; представление текущего вида моделей в памяти. Это примерно такой словарик:</p>

<pre><code>{
    'myapp': {
        'MyModel1': {'fields': [ ... ], 'meta': [ ... ]},
        'MyModel2': {'fields': [ ... ], 'meta': [ ... ]},
    }
}
</code></pre>

<p>Идея состоит в том, что django-evolution берёт сохранённую сигнатуру из БД, которая соответствует предыдущему её состоянию, и протаскивает её через симуляционные функции всех неприменённых эволюций. Они меняют сигнатуру в памяти, и если в конце она соответствует тому, какая у вас модель в Питоне сейчас описана, django-evolution рапортует, что наличествующих эволюций достаточно для того, чтобы проапгрейдить базу.</p>

<p>Это хорошо работает в теории, если всю эту симуляцию делает за вас миграционное приложение. Однако на практике выходит так, что кроме обычных эволюций типа добавить поле/удалить поле у нас очень немало кастомных эволюций. Создания таблиц, переименования таблиц, разделения таблиц на несколько и т.п. Получается, что на каждый такой кастомный чих надо писать два кода: реальный SQL и симуляцию.</p>

<p>Хуже того, многие симуляции нужны только для того, чтобы evolve перестал жаловаться на несовпадение при симуляции, потому что в этом случае <em>он отказывается что-либо делать вообще</em>. Ни применить эволюции, ни SQL для них вывести. Нет у него ключика <code>--force</code>&#8230;</p>

<p>В качестве грустного примера могу привести эволюцию &#8220;DeleteFieldWithoutSQL&#8221;, которая нужна для того, чтобы симуляция показала, что поле из модели удаляется, но при этом чтобы оно не удалялось физически, потому что позднее мы перетаскиваем данные из него в другую таблицу, и только потом удаляем.</p>

<p>В итоге по факту оказывается, что ещё не было ни одного релиза у нас, когда бы мы не тратили немало времени на то, чтобы дописывать код только для того, чтобы evolve хотя бы просто запускался поверх довольно старой версии БД. То есть по очереди во время разработки всё вроде применилось, а вот скопом &#8212; нет.</p>

<h2>Hint&#8217;ованные и сохранённые эволюции</h2>

<p>Эволюции можно применять двояко. Можно попросить автоматически определить текущую разницу между кодом и базой (<code>--hint</code>) и тут же её применить. А можно сохранить эволюцию в файл и зарегистрировать её в последовательности применения, чтобы она была применена при апгрейде другой БД.</p>

<p>Теперь представьте себе вполне обычный workflow:</p>

<ul>
<li>разработчик делает изменения в модели</li>
<li>через <code>evolve --hint -x</code> отражает его в БД</li>
<li>тестирует, отлаживает</li>
</ul>

<p>Теперь, чтобы это скоммитить, надо обязательно записать проведённые эволюции в виде файла, чтобы автоматически апгрейдить другие БД. А откуда вы теперь эти эволюции возьмёте? Всё ведь уже применено.</p>

<p>Казалось бы, можно ввести в процесс разработки правило, что никогда нельзя применять hint&#8217;ованные эволюции, а делать их только в виде файлов. Но во-первых, слишком много таких &#8220;гигиенических&#8221; правил сильно нагружают голову, а во-вторых это просто не работает. Потому что не родился ещё тот разработчик, который бы с первого раза писал правильный код, в том числе и при изменении схемы БД. Всегда нужно что-то переделать по результатам отладки.</p>

<p>В итоге это тоже вечная война: или копайся, в кишках таблиц django-evolution, вручную вычищая информацию о применённых эволюциях, или убирай вручную изменения в базе и делай новую эволюцию.</p>

<p>Наличие двух видов эволюций мне сейчас кажется, пожалуй, главным архитектурным изъяном django-evolution. Хотя может быть, мы просто не поняли, как этим пользоваться&#8230;</p>

<h2>Ключик &#8220;всё хорошо&#8221;</h2>

<p>Обратная ситуация тоже частенько случается: у вас есть некая база, в которой что-то переделано вручную, что-то откуда-то сдамплено/скопировано. Однако программист уверен, что она соответствует тому, что есть в моделях. А команда evolve &#8212; нет. Потому что в базе не записано, что были применены сохранённые эволюции, она пытается их симулировать и находит ошибки от повторного применения.</p>

<p>К сожалению, у команды evolve нет никакого ключика, чтобы сказать базе: &#8220;всё применено руками, просто запиши новое состояние и считай, что все эволюции применены&#8221;. Да, считается, что при использовании django-evolution нельзя лезть в БД руками. Но я поверю в практичность такого подхода, когда оно само будет работать идеального. А пока реальность такова, что в базу приходится лазить руками.</p>

<h2>Не фиксируются отдельные эволюции</h2>

<p>Даже для многих админов БД является откровением, что PostgreSQL умеет делать DDL в транзакциях. Ни одна другая СУБД из набора Джанги этого не умеет, даже Oracle. И надо ж случиться такому совпадению, что <del>ядрёные</del><ins>корные</ins> разработчики Джанги предпочитают именно Postgres. Рассел (автор django-evolution), кажется, тоже его предпочитает. Никак иначе я не могу объяснить то, что применяемые эволюции фиксируются как выполненные только по успешному завершению <em>всей</em> последовательности эволюций.</p>

<p>Представьте себе теперь типичную ситуацию выезда в продакшн на MySQL-сервере какой-нибудь напряжённой итерации разработки. Применямых эволюций там &#8212; десяток. И на разработческой базе они прекрасно применялись. А вот на живы данных &#8212; упали. Где-то в середине. С Postgres&#8217;ом бы не было проблем, транзакция бы не скоммитилась. Но у нас вот часть изменений применилась, а часть нет. Какая именно часть применилась, django-evolution в БД не записал. И теперь, если начать миграцию заново, оно будет пытаться применять уже применённые вещи, и напарываться на всякие &#8220;already exists&#8221; для создаваемых полей и &#8220;not exists&#8221; для удаляемых.</p>

<p>Если бы это действительно происходило в продакшне, то да, вы в ж&#8230; живописно чудовищной ситуации. Поэтому мы обычно перед выкладкой закатываем свежую продакшн-базу на тестовый сервер и имитируем там весь процесс выкладки. И поэтому же наши админы предпочитают работать с простым набором SQL-команд, а не с излишне автоматическими тулзами.</p>

<h2>Эволюции лежат в приложениях</h2>

<p>Считается, что эволюции относятся только к моделям одного приложения. Однако бывают эволюции, когда надо перетащить модельку из одного приложения в другое, например после рефакторинга, в результате которого второе приложение и возникло. Непонятно, куда класть такую эволюцию.</p>

<p>Еще одна (совсем специфичная для нас) проблема возникает от того, что у нас два разных проекта живут на одной базе и используют одно общее приложение и свои собственные приложения, про которые другой проект не знает. Когда один проект применяет эволюции для себя, django-evolution записывает в базу сигнатуру <em>этого</em> проекта. В последствие, когда другому проекту надо применить эволюции своих приложений, он напарывается на сигнатуру, в которой нет его приложений вообще, зато есть какие-то левые другие.</p>

<p>Поэтому нам хочется иметь эволюции, относящиеся к какой-то конкретной базе, и лежащие где-то отдельно от структуры приложений.</p>

<h2>Что вместо?</h2>

<p>В связи с вышеизложенным мы, накушавшись, хотим попробовать что-то другое.</p>

<p>Видимо, это будут <a href="http://code.google.com/p/dmigrations/">dmigrations</a>. Миграции там хранятся в отдельной директории, применяются только из сохранённых файлов, и там есть ручной контроль &#8212; команда <code>mark_as_applied</code>. Очень похоже, что их авторы исходили из очень практических use-case&#8217;ов и наступали на похожие с нашими грабли.</p>

<p><a href="http://south.aeracode.org/">South</a> решили не пробовать. <a href="http://webnewage.org/">Саша</a> утверждает, что он &#8220;ещё хуже&#8221;, чем django-evolution :-). А остальным разработчикам, включая меня, кажется, должно быть всё равно, потому что не работали ни с South, ни с dmigrations.</p>

<p>Посмотрим, что получится.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/06/17/evolution-pain-points/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Зачем нужны сигналы</title>
		<link>http://softwaremaniacs.org/blog/2009/05/31/what-signals-for/</link>
		<comments>http://softwaremaniacs.org/blog/2009/05/31/what-signals-for/#comments</comments>
		<pubDate>Sat, 30 May 2009 22:38:28 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Проектирование]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=678</guid>
		<description><![CDATA[На днях у меня в форуме возникли почти подряд два топика про сигналы. Оба напомнили мне давно закравшееся в голове подозрение о том, что для многих сигналы &#8212; это магия из серии &#8220;если что-то не выходит, наверняка для этого нужны сигналы&#8221; :-).

Хочу раскрыть тему, потому как рамки форумного ответа особо разгуляться не позволяют.



Сигналы синхронны

Часто совет [...]]]></description>
			<content:encoded><![CDATA[<p>На днях у меня в форуме возникли почти подряд <a href="http://softwaremaniacs.org/forum/django/12074/">два</a> <a href="http://softwaremaniacs.org/forum/django/12053/">топика</a> про сигналы. Оба напомнили мне давно закравшееся в голове подозрение о том, что для многих сигналы &#8212; это магия из серии &#8220;если что-то не выходит, наверняка для этого нужны сигналы&#8221; :-).</p>

<p>Хочу раскрыть тему, потому как рамки форумного ответа особо разгуляться не позволяют.</p>

<p><span id="more-678"></span></p>

<h2>Сигналы синхронны</h2>

<p>Часто совет (неверный) использовать сигналы встречается в ситуации, когда нужно в веб-запросе сделать какую-то потенциально долгую операцию, и хочется, чтобы она выполнилась как-нибудь в фоне (асинхронно, то есть), чтобы пользователь получил уже какой-нибудь ответ до того, как эта операция завершить.</p>

<p>Но сигналы тут совсем ни причем. Не знаю, откуда берется идея, что они выполняются в каком-то другом времени. Возможно из-за того, что механизм их работы где-то там в глубине скрыт, и просто хочется верить, что сделано так как раз для асинхронности (а для чего ещё?). А может виной всему опыт использования <a href="http://en.wikipedia.org/wiki/Signal_(computing)">POSIX&#8217;овых сигналов</a>, которые присылаются ОС в некие произвольные моменты времени и с точки зрения программы действительно происходят асинхронно с её процессом исполнения.</p>

<p>На самом же деле механизм вызова сигнала по сути своей прост, как доска. Точнее, как цикл &#8220;for&#8221;. Представьте, что у вас где-то в системе есть глобальный словарик такого рода:</p>

<pre><code>signals = {
    signal1: [func1, func2],
    signal2: [func3],
}
</code></pre>

<p>И вызов например <code>signal1.send(*args)</code> &#8212; это:</p>

<pre><code>for func in signals[signal1]:
    func(*args)
</code></pre>

<p>Вот и всё. И пока весь цикл не пройдёт и все обработчики не отработают, вызов &#8220;send&#8221; будет честно их ждать.</p>

<h2>Слабая связанность компонентов</h2>

<p>Разобравшись с тем, чего сигналы не делают, осталось понять, зачем нужен такой странный способ вызывать функции.</p>

<p>Представим для примера некий гипотетический Форум, в котором есть авторизация по OpenID. Когда в форум приходит новый пользователь, код авторизации заводит новую запись форумного Профиля для этого пользователя. Делает он это самым простым и очевидным способом: импортирует модель Profile и создает её новый объект.</p>

<p>Теперь представим, что нам хочется оторвать авторизацию по OpenID в отдельное приложение &#8212; некий гипотетический Правильный OpenID-консумер. Просто перенести весь авторизационный код в отдельную директорию недостаточно из-за сильной связности: Авторизация всё ещё импортирует к себе модель Форума Profile, а значит её нельзя будет использовать как отдельное приложение без Форума.</p>

<p>Это как раз и решается сигналами. Вместо того, чтобы звать Форум напрямую, Авторизация в момент осознания, что с ней случился новый пользователь, отсылает в Бесконечный Эфир сигнал. Это специальный сигнал, описанный специально для такой оказии где-то в коде Авторизации и основательно документирован. Со своей стороны Форум импортирует определение сигнала из Авторизации и начинает его слушать, повесив на него в качестве реакции свою функцию, создающую новому пользователю профиль.</p>

<p>Таким образом мы получаем связь с однонаправленной зависимостью:</p>

<ul>
<li><p>Форум <em>сильно</em> связан с (зависит от) Авторизацией, потому что должен знать про то, что она отсылает этот конкретный сигнал. Тто есть должен его явно импортировать.</p></li>
<li><p>Авторизация <em>слабо</em> связана с (не зависит от) Форума, потому что предоставляет свой сигнал для любого потребителя и благодаря тому, что те навешивают обработчики сигналам по собственной инициативе, она не зависит от их наличия, количества или сущности.</p></li>
</ul>

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

<p>P.S. Создание системы со слабой связностью в <em>обоих направлениях</em> остаётся в качестве упражнения читателю.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/05/31/what-signals-for/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Django 1.1 beta</title>
		<link>http://softwaremaniacs.org/blog/2009/03/27/django-11-beta/</link>
		<comments>http://softwaremaniacs.org/blog/2009/03/27/django-11-beta/#comments</comments>
		<pubDate>Thu, 26 Mar 2009 22:36:38 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=648</guid>
		<description><![CDATA[Как многие, без сомнения, знают, недавно вышла Django 1.1 beta. Там в release notes все самое вкусное перечислено, а я немножко вокруг покомментирую.



Одна из когда-то давно слезно просимых фич &#8212; загрузка объектов с неполным списком полей (aka &#8220;defer/only&#8220;). Раньше это делалось через .values(), но результатом запроса был тупой набор значений. Новая фича возвращает полноценные объекты [...]]]></description>
			<content:encoded><![CDATA[<p>Как многие, без сомнения, знают, недавно вышла <a href="http://docs.djangoproject.com/en/dev/releases/1.1-beta-1/">Django 1.1 beta</a>. Там в release notes все самое вкусное перечислено, а я немножко вокруг покомментирую.</p>

<p><span id="more-648"></span></p>

<p>Одна из когда-то давно слезно просимых фич &#8212; загрузка объектов с неполным списком полей (aka &#8220;<a href="http://docs.djangoproject.com/en/dev/ref/models/querysets/#queryset-defer">defer/only</a>&#8220;). Раньше это делалось через <code>.values()</code>, но результатом запроса был тупой набор значений. Новая фича возвращает полноценные объекты с недозаполненными полями. Поля заполняются отдельными запросами при первом обращении.</p>

<p>Теперь, по <a href="https://twitter.com/malcolmt/statuses/1379164264">меткому выражению Малколма</a> все кинутся этим пользоваться, потому что оно кажется очень крутым. Грустная ирония состоит в том, что если вы думаете, что вам нужны defer/only, это с высокой вероятностью либо преждевременная оптимизация, либо указание на неудачную модель базы. То есть, либо вы Реально Большие BLOB&#8217;ы храните в базе, вместо файлов, либо у вас в модели куча полей, которые можно и нужно разнести по разным моделям, провязанным один-к-одному. Впрочем, ладно, разберемся :-)</p>

<p>Еще одна фичка, про которую я даже не знал, пока в релиз нотах не прочитал &#8212; <a href="http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/">массовые действия над объектами в админке</a>. Тоже давно просили все, чтобы массово что-нибудь удалять или проставлять флажки всякие. Что меня реально порадовало &#8212; это то, что фича сразу вышла хорошо проработанной в деталях:</p>

<ul>
<li>можно выдать юзеру свое сообщение типа &#8220;удалено столько-то объектов&#8221;</li>
<li>можно вместо возвращения управления в админку передать объекты в свою вьюху, если над ними нужны какие-то многоходовые издевательства</li>
<li>действие можно повесить как на одну модель, так и глобально для всех</li>
</ul>

<p>А, да&#8230; Включенный сейчас по умолчанию &#8220;удалить все объекты&#8221; обещали сделать отключаемым. Не всем это надо :-)</p>

<p>Также совршенно нежданно-негаданно Малколм нашел время добить и скоммитить мой <a href="http://docs.djangoproject.com/en/dev/topics/conditional-view-processing/#topics-conditional-processing">декоратор condition</a>, который я <a href="http://softwaremaniacs.org/blog/2007/08/18/conditional-get-bites/">когда-то писал для Cicero</a>. Малколм его обобщил до работы не только с GET&#8217;ом, но и с остальными методами (PUT, POST, DELETE), которые могут обрабатывать ETag&#8217;и. Теперь Джанго реагирует на нужные заголовки с правильными HTTP-кодами. Ура! Писать REST-сервисы на Джанго теперь еще приятнее!</p>

<p>Ну и на закуску немножко приятных багфиксов:</p>

<ul>
<li><p><a href="http://code.djangoproject.com/ticket/7295">7295</a>: в шаблонных тегах теперь можно использовать кавычки любого типа</p></li>
<li><p><a href="http://code.djangoproject.com/ticket/5756">5756</a> и <a href="http://code.djangoproject.com/ticket/6296">6296</a>: в параметрах шаблонных тегов теперь можно использовать фильтры (всеми любимый <code>{% ifequal list|length 5 %}</code> теперь должен работать)</p></li>
<li><p><a href="http://code.djangoproject.com/ticket/2698">2698</a>: наконец обработаны все краевые случаи с обращением к родительским объектам при использовании фильтрующих менеджеров по умолчанию (читай: теперь это можно делать без опасения наступить на туманные грабли)</p></li>
<li><p><a href="http://code.djangoproject.com/ticket/8164">8164</a>: в ModelForms теперь можно удобно задавать порядок и полей модели и дополнительных полей формы</p></li>
<li><p><a href="http://code.djangoproject.com/ticket/10194">10194</a>: появилась умная функция <code>django.shortcuts.redirect()</code>, которая <a href="http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#redirect">принимает всякие значения</a> и делать The Right Thing™. Например вместо:</p>

<pre><code>from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
...
return HttpResponseRedirect(reverse(viewname, args=[param]))
</code></pre>

<p>теперь достаточно:</p>

<pre><code>from django.shortcuts import redirect
...
return redirect(viewname, param)
</code></pre></li>
</ul>

<p>А у вас есть любимые фички в этом релизе?</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/03/27/django-11-beta/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>{% media %}</title>
		<link>http://softwaremaniacs.org/blog/2009/03/22/media-tag/</link>
		<comments>http://softwaremaniacs.org/blog/2009/03/22/media-tag/#comments</comments>
		<pubDate>Sun, 22 Mar 2009 02:55:49 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=638</guid>
		<description><![CDATA[Незабвенный герой Анатолия Папанова говаривал, что ежели человек идиот, то это надолго. Мне сейчас кажется, что я как раз в такой ситуации :-). Это я о том, как в джанговских шаблонах делать ссылки на JS, CSS и прочую media.



Я всегда пропагандировал простой способ: прокидывать в шаблоны переменную MEDIA_URL, используя стандартный контекст-процессор media. А в шаблоне [...]]]></description>
			<content:encoded><![CDATA[<p>Незабвенный <a href="http://ru.wikiquote.org/wiki/%D0%91%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%D0%B0%D1%8F_%D1%80%D1%83%D0%BA%D0%B0">герой Анатолия Папанова говаривал</a>, что ежели человек идиот, то это надолго. Мне сейчас кажется, что я как раз в такой ситуации :-). Это я о том, как в джанговских шаблонах делать ссылки на JS, CSS и прочую media.</p>

<p><span id="more-638"></span></p>

<p>Я всегда пропагандировал простой способ: прокидывать в шаблоны переменную <code>MEDIA_URL</code>, используя <a href="http://docs.djangoproject.com/en/dev/ref/templates/api/?from=olddocs#django-core-context-processors-media">стандартный контекст-процессор media</a>. А в шаблоне просто составлять слова рядом:</p>

<pre><code>&lt;link rel="stylesheet" href="{{ MEDIA_URL }}css/style.css"&gt;
</code></pre>

<p>Это типа работает, но у такого способа есть пара минусов:</p>

<ul>
<li>Нужно всегда использовать RequestContext для шаблонов. Что, впрочем, в принципе хорошая идея, поэтому минус этот условный.</li>
<li>Вот что реально плохо, это что эту переменную приходится вручную прокидывать в любой inclusion-тег, в котором понадобилась media. Это особенно неприятно, потому что периодически мешает параллельности работы верстальщика и программиста.</li>
</ul>

<p>И тут вдруг у меня сошлись в голове какие-то правильные мысли с разных сторон, и я написал себе удобный тег для формирования ссылок на media-файлы в шаблонах с парой полезных дополнений.</p>

<ul>
<li><p>В простейшем случае он просто присоединяет имя файла к <code>MEDIA_URL</code>:</p>

<pre><code>{% media "images/edit.png" %}
</code></pre>

<p>Однако если параметр начинатся со слеша или с протокола (&#8221;http://&#8221;), то он уже не будет присоединяться к <code>MEDIA_URL</code>.</p></li>
<li><p>Можно добавить флажок, чтобы ссылка всегда была абсолютной:</p>

<pre><code>&lt;!-- /media/edit.png становится http://example.com/media/edit.png --&gt;
{% media "images/edit.png" "absolute" %}
</code></pre></li>
<li><p>Можно добавить к файлу timestamp по времени обновления, что очень полезно для css- и js-файлов, чтобы при изменении их на сервере, браузер автоматически получали новую версию без необходимости заставлять юзеров вручную рефрешить страницу. Больше того, для css- и js-файлов это включено автоматически. Поведение это явно можно включать и выключать флажками &#8220;timestamp&#8221; и &#8220;no-timestamp&#8221;</p>

<pre><code>{% media "css/style.css" %}                &lt;!-- ...style.css?123456789 --&gt;
{% media "css/style.css" "no-timestamp" %} &lt;!-- ...style.css --&gt;
{% media "images/edit.png" "timestamp" %}  &lt;!-- ...edit.png?123456789 --&gt;
</code></pre></li>
</ul>

<p>Флажки &#8220;timestamp&#8221;, &#8220;no-timestamp&#8221; и &#8220;absolute&#8221; можно использовать одноврменно (через запятую).</p>

<p>Выкладывать отдельным проектом я его не хочу, потому что уж очень маленький. Вместо этого я хочу собрать мнения по поводу полезности самого подхода и может быть предложить включить его в Django. А пока вот его небольшой код:</p>

<pre><code>def _absolute_url(url):
    if url.startswith('http://') or url.startswith('https://'):
        return url
    domain = Site.objects.get_current().domain
    return 'http://%s%s' % (domain, url)

@register.simple_tag
def media(filename, flags=''):
    flags = set(f.strip() for f in flags.split(','))
    url = urlparse.urljoin(settings.MEDIA_URL, filename)
    if 'absolute' in flags:
        url = _absolute_url(url)
    if (filename.endswith('.css') or filename.endswith('.js')) and 'no-timestamp' not in flags or \
       'timestamp' in flags:
        fullname = os.path.join(settings.MEDIA_ROOT, filename)
        if os.path.exists(fullname):
            url += '?%d' % os.path.getmtime(fullname)
    return url
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/03/22/media-tag/feed/</wfw:commentRss>
		<slash:comments>28</slash:comments>
		</item>
		<item>
		<title>Еще один</title>
		<link>http://softwaremaniacs.org/blog/2009/03/16/another-one/</link>
		<comments>http://softwaremaniacs.org/blog/2009/03/16/another-one/#comments</comments>
		<pubDate>Mon, 16 Mar 2009 13:31:42 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=634</guid>
		<description><![CDATA[isagalaev@isagalaev:~$ curl -is http://afisha.yandex.ru/ &#124; grep Server:
Server: lighttpd / Pony Powered!


Вот! Подробности позже. И, наверное, даже не у меня&#8230;
]]></description>
			<content:encoded><![CDATA[<pre><code>isagalaev@isagalaev:~$ curl -is http://afisha.yandex.ru/ | grep Server:
Server: lighttpd / Pony Powered!
</code></pre>

<p>Вот! Подробности позже. И, наверное, даже не у меня&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/03/16/another-one/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
		<item>
		<title>Джанговские агрегации</title>
		<link>http://softwaremaniacs.org/blog/2009/01/15/django-aggregation/</link>
		<comments>http://softwaremaniacs.org/blog/2009/01/15/django-aggregation/#comments</comments>
		<pubDate>Thu, 15 Jan 2009 14:55:20 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=568</guid>
		<description><![CDATA[Сделали: http://docs.djangoproject.com/en/dev/topics/db/aggregation/!

Особо приглянувшиеся мне вещи:


Store.objects.annotate(Count('books__authors'))

Store &#8212; это магазин, у него many-to-many на Book, у которого есть many-to-many на Author. Тут делается join по всем нужным таблицам и group by на них.
Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

Я очень рад, что having не стали делать отдельным понятием. Это просто фильтр. А уж having там или where &#8212; пусть оно само разбирается.


А также [...]]]></description>
			<content:encoded><![CDATA[<p>Сделали: <a href="http://docs.djangoproject.com/en/dev/topics/db/aggregation/">http://docs.djangoproject.com/en/dev/topics/db/aggregation/</a>!</p>

<p>Особо приглянувшиеся мне вещи:</p>

<ul>
<li><p><code>Store.objects.annotate(Count('books__authors'))</code></p>

<p>Store &#8212; это магазин, у него many-to-many на Book, у которого есть many-to-many на Author. Тут делается join по всем нужным таблицам и group by на них.</p></li>
<li><p><code>Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)</code></p>

<p>Я очень рад, что having не стали делать отдельным понятием. Это просто фильтр. А уж having там или where &#8212; пусть оно само разбирается.</p></li>
</ul>

<p>А также вместе с этим в транк приехали и <a href="/blog/2008/11/21/expressions-in-django-queries/">выражения со ссылкой на поля</a> (по крайней мере, должны были, я не проверял :-) ).</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/01/15/django-aggregation/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Изменённые данные в форме</title>
		<link>http://softwaremaniacs.org/blog/2009/01/14/changed-data-in-forms/</link>
		<comments>http://softwaremaniacs.org/blog/2009/01/14/changed-data-in-forms/#comments</comments>
		<pubDate>Wed, 14 Jan 2009 13:17:58 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=560</guid>
		<description><![CDATA[Когда-то я придумал для своего багтракинга фичу, которая мне казалась уникальной для багтракингов. Фича связана с тем, как обрабатываются изменения, которые человек сохраняет в тикет, в то время, как тикет уже успел поменяться кем-то другим за время с момента открытия того в окне браузера.

И вот на днях, в процессе переписывания TaCo на Джанго (да, я [...]]]></description>
			<content:encoded><![CDATA[<p>Когда-то я придумал для своего багтракинга фичу, которая мне казалась уникальной для багтракингов. Фича связана с тем, как обрабатываются изменения, которые человек сохраняет в тикет, в то время, как тикет уже успел поменяться кем-то другим за время с момента открытия того в окне браузера.</p>

<p>И вот на днях, в процессе переписывания TaCo на Джанго (да, я <a href="http://softwaremaniacs.org/forum/taco/3832/">таки занялся</a>) я обнаружил в джанговских формах фичу, про которую как-то нигде &#8220;официально&#8221; не рассказывали.</p>

<p><span id="more-560"></span></p>

<h2>Задачка</h2>

<p>Пусть у нас есть Маша и Саша, которые работают в багтракинге над одним проектом. Маша открывает баг про падение программы, и, словив озарение, начинает его тут же фиксить, потому что работы там на 10 минут. Через часок она понимает, что починить все так просто не получилось и решает отложить работу. Перед тем, как пойти налить кофейку, она переключается обратно в окно браузера с багом и переводить его в статус &#8220;In progress&#8221;.</p>

<p>Беда в том, что в это же время Саша, который взял на себя обязанности пастуха багтракинга, тоже нашел этот баг и, справедливо подумав,  что падение программы &#8212; проблема серьезная, адекватно поменял поле severity на &#8220;Major&#8221;.</p>

<p>Так вот, если сабмит формы тикета обрабатывать стандартным образом, то Машин перевод тикета в &#8220;In Progress&#8221; помимо этого вернет ему severity в &#8220;Normal&#8221;, потому что именно в таком виде она открыла эту форму час назад.</p>

<h2>Решение</h2>

<p>TaCo решал эту проблему. Вместо того, чтобы сохранять в тикет данные из формы целиком, он определял, какие именно поля в этой форме пользователь поменял, и применял только эти изменения. То есть, в нашем случае от Маши пришло бы только указание &#8220;поменять статус&#8221;, и severity остался бы нетронутым.</p>

<p>Реализовано это самым очевидным способом: в форме вместе с видимыми юзеру полями рисуются их скрытые дубликаты с именами типа &#8220;OldSeverity&#8221;, &#8220;OldOwner&#8221; и т.д. При сабмите из формы вынимаются поля и их старые значения, по которым выясняется, что поменялось.</p>

<p class="note"><small>Уголок буквоеда. Да, это все равно не устраняет race condition целиком. Но это и не цель. Мне нужно решить задачу правильного поведения в большинстве практических случаев. В остальных случаях люди просто вручную обновят тикет еще раз как надо.</small></p>

<h2>Django</h2>

<p>Я был очень рад и немало удивлен, когда увидел, что в Джанге такой функционал просто встроен в стандартные формы. Оказывается, совсем не идиотский подход я тогда &#8220;изобрел&#8221;, а другие так тоже делают.</p>

<p>Включается это для отдельных полей указанием атрибута <code>show_hidden_initial</code>:</p>

<pre><code>class TaskForm(forms.Form):
    severity = forms.ChoiceField(..., show_hidden_initial=True)
    owner = forms.ChoiceField(..., show_hidden_initial=True)
    # ...
</code></pre>

<p>После валидации у формы будет доступен <code>form.changed_data</code> &#8212; список с названиями измененных полей, который уже можно использовать по своему усмотрению. Например:</p>

<pre><code>if form.is_valid():
    for fieldname in form.changed_data:
        setattr(obj, fieldname, form.cleaned_data[fieldname])
    obj.save()
</code></pre>

<p>Кажется, это хорошая иллюстрация полезности фреймворков. Такая фича &#8212; нудная мелочь, которую никто не будет реализовывать прямо сразу, а скорее всего оставит &#8220;на потом&#8221;. А во фреймворке она просто есть, потому что кому-то в мире она уже понадобилась и он ее оформил патчем.</p>

<p>P.S. Я заодно подпилил <a href="http://softwaremaniacs.org/soft/tagsfield/">TagsField</a>, чтобы он поддерживал этот параметр. Правда не опубликовал еще.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/01/14/changed-data-in-forms/feed/</wfw:commentRss>
		<slash:comments>24</slash:comments>
		</item>
		<item>
		<title>Как работает SM.Org</title>
		<link>http://softwaremaniacs.org/blog/2009/01/04/how-smorg-works/</link>
		<comments>http://softwaremaniacs.org/blog/2009/01/04/how-smorg-works/#comments</comments>
		<pubDate>Sat, 03 Jan 2009 23:06:59 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Мои программы]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=517</guid>
		<description><![CDATA[Недавно расквитался в первом приближении с давно висящей и давящую на голову задачкой: опубликовал исходный код всех Джанго-приложений, которые поддерживают разные части SoftwareManiacs.Org. И меня посетила мысль поделиться тем, как оно вообще у меня тут все живет.



Сайт SoftwareManiacs.Org работает на VPS-сервере (у компании TekTonic) и представляет собой сборную солянку из технологий:


блог &#8212; вполне себе стандартный [...]]]></description>
			<content:encoded><![CDATA[<p>Недавно расквитался в первом приближении с давно висящей и давящую на голову задачкой: опубликовал <a href="http://softwaremaniacs.org/soft/">исходный код всех Джанго-приложений</a>, которые поддерживают разные части SoftwareManiacs.Org. И меня посетила мысль поделиться тем, как оно вообще у меня тут все живет.</p>

<p><span id="more-517"></span></p>

<p>Сайт SoftwareManiacs.Org работает на VPS-сервере (у компании <a href="http://tektonic.net/">TekTonic</a>) и представляет собой сборную солянку из технологий:</p>

<ul>
<li><a href="http://softwaremaniacs.org/blog/">блог</a> &#8212; вполне себе стандартный <a href="http://wordpress.org/">WordPress</a> с парой плагинов и собственной темой, которую я меняю раз в год в начале мая</li>
<li><a href="http://softwaremaniacs.org/forum/">форум</a>, <a href="http://softwaremaniacs.org/soft/">каталог программ</a>, <a href="http://softwaremaniacs.org/sanestat/">статистика</a> и <a href="http://softwaremaniacs.org/about/">about</a> &#8212; мои собственные программы, написанные на <a href="http://www.djangoproject.com/">Django</a></li>
<li>еще <a href="http://softwaremaniacs.org/wiki/">wiki</a> (<a href="http://www.dokuwiki.org/dokuwiki">DokuWiki</a>) и <a href="http://softwaremaniacs.org/bugs/">багтракинг</a> (самописный), которые используются исключительно для личных целей</li>
</ul>

<p>Меня периодически спрашивали, как это все интегрировано. Очень просто &#8212; на уровне конфига веб-сервера (<a href="http://www.lighttpd.net/">lighttpd</a>), который раздает задачки трем бэкендам:</p>

<ul>
<li>FastCGI-сервер со всем Джанго-кодом</li>
<li>PHP через FastCGI для всего PHP-кода (wiki и блог)</li>
<li>FastCGI-сервер для багтракинга</li>
</ul>

<p>На самом деле, меня этот зоопарк не радует. Во-первых, лишнюю память кушает, а во-вторых, заставляет хранить одинаковые части шаблонов для блога и джанго-части в двух местах. В связи с этим, я наверное буду это все лениво упрощать: передвину багтракинг и блог под Джанго в каком-нибудь будущем. Впрочем, все это не особенно тяготит и сейчас.</p>

<h2>Открытие исходников</h2>

<p>Идея выложить свои приложения у меня созрела давно, и причина этому очень простая: вдруг кому что полезно будет. Тем не менее, не все они, что называется, &#8220;коммерческого&#8221; качества. К таким я с чистой совестью могу отнести только <a href="http://softwaremaniacs.org/soft/cicero/">Cicero</a> и <a href="http://softwaremaniacs.org/soft/vooid/">Vooid</a>, которые можно брать и устанавливать. Два других &#8212; <a href="http://softwaremaniacs.org/soft/soft/">Soft</a> и <a href="http://softwaremaniacs.org/soft/sanestat/">SaneStat</a> &#8212; это скорее код, в который можно просто полюбопытствовать.</p>

<p>Все исходники выложены просто в виде ссылок на Bazaar-бранчи, без всяких красивых интерфейсов и без всякой сопроводительной инфраструктуры типа публичного багтракинга и wiki. Сделано так намеренно. Дело в том, что равносильно желанию поделиться, у меня существует осознание, что я не смогу находить время, чтобы поддерживать все эти вещи как нормальные открытые <em>проекты</em>. Иначе бы просто выложил все на <a href="http://code.google.com/">Google Code</a>, где уже все это есть. А пока, я буду рад всяким советам, сообщениям о багах и патчам, но надеюсь, что их количество не сведет меня с ума :-)</p>

<h2><a href="http://softwaremaniacs.org/soft/soft/">Soft</a></h2>

<p>Выкладывание программок собственного сочинения было первым назначением моего сайта. В те времена (2000 год где-то) у него еще не было собственного домена (хотя адрес <a href="http://www.mtu-net.ru/maniac/">http://www.mtu-net.ru/maniac/</a> до сих пор правильно редиректит!), и весь сайт, как наверное у многих, представлял собой набор статичных HTML-файлов. Довольно быстро это стало очень неудобно поддерживать по очевидным причинам. Но я мучился до 2005 года :-). И только тогда, когда я <a href="/blog/2005/12/08/django/">&#8220;заболел&#8221; Джангой</a>, я сподобился сделать софтинку, которая бы позволяла выкладывать программы и обновления к ним автоматизированным способом.</p>

<p class="note"><small>Надо сказать, что вообще вся история развития этого сайта &#8212; это попытка избавить себя от лишней работ, которую я придумываю себе на голову <a href="/blog/2006/08/14/highlight-js/">всякими бессонными ночами</a> :-). Это видно даже по оформлению, в котором с каждым годом становится все меньше элементов.</small></p>

<p>В итоге &#8220;soft&#8221; &#8212; это достаточно узконаправленная программа, в которой, тем не менее, есть кое-что интересное для внешнего наблюдателя.</p>

<ul>
<li><p>Реализовано достаточно элегантное решение мультиязычного контента. В итоге, сейчас у меня есть программы на двух языках (<a href="http://softwaremaniacs.org/soft/highlight/">highlight.js</a>, <a href="http://softwaremaniacs.org/soft/tagsfield/">TagsField</a>, <a href="http://softwaremaniacs.org/soft/mysql_replicated/">mysql_replicated</a>), у которых переведено все вплоть до новостей. Все это достаточно удобно рулится из админки.</p></li>
<li><p>Есть зачатки кастомизации шаблонов для отдельных приложений, благодаря чему у того же хайлайтера есть кастомная <a href="http://softwaremaniacs.org/soft/highlight/download/">страница загрузки</a>, которая, кстати, сама по себе является отдельным Джанго-приложением, отвечающим за формирование списка языков и запаковывание zip&#8217;ов для скачивания.</p></li>
</ul>

<p>Следующее, что мне очень хочется туда привернуть &#8212; это выкладывания новых версий программ. Сейчас мне приходится руками ходить на сервер, чекаутить последние версии из репозиториев, упаковывать архивы и раскладывать все в нужные места. Хочется это все делать в рамках джанговской админки, просто создавая новый объект &#8220;Релиз&#8221;, который будет делать все сам через шеллный скрипт, привязанный к каждой конкретной программе.</p>

<h2><a href="http://softwaremaniacs.org/soft/sanestat/">SaneStat</a></h2>

<p>Эта штука началась с простого питоньего скрипта, который парсил апачевский лог в поисках рефереров, которые на меня ссылаются. Этот же скрипт простыми print&#8217;ами выводил и HTML, когда вызывался по CGI. Это, опять-таки, было медленно и неудобно, поэтому было переведено под Джанго. Как и у любого парсера логов, у него есть две части &#8212; собственно парсер, который оформлен в виде команды для manage.py, вызывающейся из крона, и набора вьюшек, которые передают в шаблоны результаты этого парсинга.</p>

<p>Сборщик статистики интересен тем, что в нем собран за несколько лет большой набор регулярок, определяющий &#8220;неинтересные&#8221; рефереры, спам-рефереры, а также семейства браузеров. Все это, кстати, выложено в виде <a href="http://softwaremaniacs.org/media/soft/sanestat/sanestat_bundle.json">отдельного архива</a> в формате джанговской fixture.</p>

<p>Но вообще, я этой софтиной вечно недоволен :-(. С самого рождения она недостаточно удобна, а тратить время на <a href="http://softwaremaniacs.org/bugs/?product=SaneStat&amp;action=list&amp;Resolution=None&amp;SortField1=Severity&amp;SortField2=Kind">улучшение</a> хочется меньше, чем с остальными программами. Как-то никак я с ней дзена не достигну&#8230; Иногда хочется вообще от нее отказаться и повесить какой-нибудь стандартный js-счетчик.</p>

<h2>Все остальное</h2>

<p>Все остальное в джанго-части сайта реализовано стандартными джанговскими приложениями: <a href="http://docs.djangoproject.com/en/dev/ref/contrib/admin/">admin</a>, <a href="http://docs.djangoproject.com/en/dev/ref/contrib/flatpages/">flatpages</a>, <a href="http://docs.djangoproject.com/en/dev/ref/contrib/redirects/">redirects</a>. Я слышал мнение, что джановский контриб бесполезен для чего-то реального, но могу это авторитетно опровергнуть. Его вполне можно использовать, надо только знать ограничения и не хотеть слишком многого.</p>

<p>Админка используется у меня по прямому назначению: редактирование программ, добавление рефереров в базу статистики, редактирование about&#8217;а и т.д. Практически не кастомизирована. Самый сложный экран &#8212; это пожалуй редактирование программы с кучей inline&#8217;утых линков и переводов:</p>

<p class="center picture"><a href="/blog/wp-content/soft-admin.png"><img src="/blog/wp-content/thumbsoft-admin.jpg" alt=""></a></p>

<p>На flatpages реализуется все, что либо статично по сути, либо еще не стало повторяться настолько часто, чтобы хотелось написать под это код. Яркий пример первого случая &#8212; <a href="http://softwaremaniacs.org/about/">about</a>. Просто flatpage с кастомным шаблоном. Пример второго случая &#8212; <a href="http://softwaremaniacs.org/soft/highlight/describing-language/">описание создания языков для highlight.js</a>. Пока такой документ единственный в своем роде. Если вдруг другие программы начнут обзаводиться похожими вещами, допишу для этого немножко когда в приложение soft. Так и живем :-).</p>

<h2>Расположение проекта</h2>

<p>На сервере все это у меня лежит, как и положено по best practices джанго-проектов. Проект &#8212; отдельная папка с настройками, конфигами, шаблонами и локализацией. Приложения &#8212; в совершенно другой отвлеченной папке. Что как раз и позволяет выложить приложения в мир, потому что они не привязаны жестко к сайту. В итоге <code>INSTALLED_APPS</code> в настройках выглядит довольно длинно:</p>

<pre><code>INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'django.contrib.admindocs',
    'django.contrib.flatpages',
    'django.contrib.redirects',
    'common',
    'soft',
    'hljs_download',
    'vooid',
    'sanestat',
    'cicero',
    'cicero_punbb_import',
)
</code></pre>

<p>И все это нужно :-).</p>

<hr />

<p>Вот такой обзорчик вышел. Сильно подробней я писать не стал, потому что непонятно, что из этого вообще интересно. Поэтому, если что-то хочется узнать подробней &#8212; спрашивайте в комментариях, с удовольствием поделюсь.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2009/01/04/how-smorg-works/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Выражения в джанговских запросах</title>
		<link>http://softwaremaniacs.org/blog/2008/11/21/expressions-in-django-queries/</link>
		<comments>http://softwaremaniacs.org/blog/2008/11/21/expressions-in-django-queries/#comments</comments>
		<pubDate>Fri, 21 Nov 2008 15:23:28 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=491</guid>
		<description><![CDATA[Сегодня был опубликован план на следующий релиз Джанго 1.1. В нем много всякого хорошего, но мое внимание привлекла красота реализации одной из must-have фич, и я решил поделиться с вами неожиданно нахлынувшей на меня радостью :-). Заодно кто-то, как я, узнает новую для себя возможность Питона.



Речь идет о тикете 7210 &#8212; &#8220;Added expression support for [...]]]></description>
			<content:encoded><![CDATA[<p>Сегодня был опубликован <a href="http://code.djangoproject.com/wiki/Version1.1Roadmap">план на следующий релиз Джанго 1.1</a>. В нем <em>много</em> всякого хорошего, но мое внимание привлекла красота реализации одной из must-have фич, и я решил поделиться с вами неожиданно нахлынувшей на меня радостью :-). Заодно кто-то, как я, узнает новую для себя возможность Питона.</p>

<p><span id="more-491"></span></p>

<p>Речь идет о тикете <a href="http://code.djangoproject.com/ticket/7210">7210</a> &#8212; &#8220;Added expression support for QuerySet.update&#8221;. Это про то, что сейчас джанговский ORM не умеет создавать SQL для условий в запросах, в которых есть а) выражения:</p>

<pre><code>update page set views_count = views_count + 1 where id = 123;
</code></pre>

<p>б) поля в качестве значений:</p>

<pre><code>select * from employee where current_salary &gt; start_salary;
</code></pre>

<p>Теперь это можно будет делать.</p>

<h2>Загвоздка</h2>

<p>Но самое интересное &#8212; это синтаксис, которым такие штуки записываются. В нем, собственно, и была главная загвоздка, почему этой фичи не было в ORM еще давно. Действительно, если написать все как есть, то получится:</p>

<pre><code># вместо увеличения на единицу текущего значения Питон увеличит локальну переменную 
# views_count, которой еще и нет наверняка
Page.objects.filter(pk=123).update(views_count=views_count + 1)

# вместо поля start_salary будет использована строка "start_salary", которая свалится
# на приведении к числу
Employee.objects.filter(current_salary__gt='start_salary')
</code></pre>

<h2>F-объект</h2>

<p>Проблему решили введением специального объекта, который означает &#8220;поле в этом запросе&#8221;. И назвали его, что важно, кратко &#8212; <code>F</code>. В итоге оба примера начинают выглядеть вот так:</p>

<pre><code>Page.objects.filter(pk=123).update(views_count=F('views_count') + 1)
Employee.objects.filter(current_salary__gt=F('start_salary'))
</code></pre>

<p>Кода придет время формировать SQL, объект запроса будет знать, что F-объект содержит в себе название поля. С вытекающим отсюда правильным экранированием и т.д.</p>

<h2>Минуточку&#8230;</h2>

<p>Раз F &#8212; объект для отложенного хранения имени поля, то как работает конструкция <code>F('views_count') + 1</code>? Оказывается, F переопределяет для себя все имеющие смысл математические операции методами <code>__add__</code>, <code>__sub__</code>, <code>__mul__</code> и <a href="http://docs.python.org/reference/datamodel.html#emulating-numeric-types">прочими</a>, которые Питон использует при рассчете выражений. Результатом каждой из них является такой же объект F, который хранит у себя свои операнды. Соответственно, эти вызовы можно комбинировать (хотя практического юзкейса я еще не придумал):</p>

<pre><code>op = F('field') + 1   →  op = F('field').__add__(1)
op2 = op * 3          →  op2 = op.__mul__(3)
</code></pre>

<p>А вот на закуску стоит сказать, что эти вещи работают и при переставленных операндах. То есть:</p>

<pre><code>1 + F('field')
</code></pre>

<p>&#8230; не свалится, хотя у объекта <code>int</code> и не определено, как к нему <code>__add__</code> джанговский F-объект. Вот эта магия достигается за счет обратных методов операций: <code>__radd__</code>, <code>__rmul__</code> и т.д. Они вызываются у <em>правого</em> объекта в операции, если вызов прямого метода на <em>левом</em> объекте выдал ошибку NotImplemented. Соответственно:</p>

<pre><code>1 + F('field')        →  F('field').__radd__(1)
</code></pre>

<p>Я даже, пожалуй, скажу, что такой простой двухступенчатый алгоритм нравится мне больше, чем дико сложная штука в C++, где выражение <code>x + y</code> будет пытаться найти <em>все</em> возможные преобразования типов операндов, позволяющих выполнить операцию.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/11/21/expressions-in-django-queries/feed/</wfw:commentRss>
		<slash:comments>34</slash:comments>
		</item>
		<item>
		<title>Схема файлового бэкенда Джанго</title>
		<link>http://softwaremaniacs.org/blog/2008/11/17/django-file-backend-chart/</link>
		<comments>http://softwaremaniacs.org/blog/2008/11/17/django-file-backend-chart/#comments</comments>
		<pubDate>Mon, 17 Nov 2008 01:01:56 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=474</guid>
		<description><![CDATA[По долгу службы ковырялся тут внутри у Джанго вокруг работы с файлами. Всякие хранилища, файловые поля, дескрипторы, врапперы&#8230; Признаться, штука вышла далеко не простая (&#8221;а я еще во-о-от такой помню&#8230;&#8221;). Я начал набрасывать себе схему того, как объекты друг с другом взаимодействуют, а потом решил ее причесать и выложить, чтобы остальным проще разбираться было.



Итак, первая [...]]]></description>
			<content:encoded><![CDATA[<p>По долгу службы ковырялся тут внутри у Джанго вокруг <a href="http://docs.djangoproject.com/en/dev/topics/files/#topics-files">работы с файлами</a>. Всякие хранилища, файловые поля, дескрипторы, врапперы&#8230; Признаться, штука вышла далеко не простая (&#8221;а я еще во-о-от такой помню&#8230;&#8221;). Я начал набрасывать себе схему того, как объекты друг с другом взаимодействуют, а потом решил ее причесать и выложить, чтобы остальным проще разбираться было.</p>

<p><span id="more-474"></span></p>

<p>Итак, первая версия схемы файлового инструментария Джанго:</p>

<p class="picture center"><a href="/blog/wp-content/django-file-backend.png"><img src="/blog/wp-content/django-file-backendthumb.png" alt=""></a></p>

<p>К схеме прилагаются отмазка и пояснения. Отмазка простая: сейчас полчетвертого ночи, поэтому я реально мог там что-то напутать. Буду благодарен за исправления!</p>

<p>Пояснения следуют&#8230;</p>

<p>Схема намерено не перечисляет всех существующих наследников базовых классов и их подробных интерфейсов, чтобы не создавать каши. Перечислены только основные действующие лица:</p>

<dl>
<dt><code>Storage</code></dt>
<dd>Базовый класс для понятия &#8220;хранилище файлов&#8221;. В Джанго есть самая очевидная реализация &#8212; хранилище файлов в локальной файловой системе (<code>FileSystemStorage</code>). Но вообще эта штука предназначена для того, чтобы пользователи писали свои интересные наследники: удаленные хранилища, распределенные хранилища, шифрованные хранилища и т.п.</dd>

<dt><code>File</code></dt>
<dd>Базовый класс, представляющий все джанговские файлы. По сути &#8212; легкая обертка над стандартным питоньим <code>file</code>ом с добавлением пары удобных методов (итератор <code>chunks()</code>, свойства <code>.path</code>, <code>.url</code>). Именно этот объект возвращает метод <code>open()</code> из хранилища.</dd>

<dt><code>FileUploadHandler</code></dt>
<dd>
<p>Обработчик потока байтов, идущих по HTTP при upload&#8217;е файлов. Джанго умеет разбирать поток по ходу дела на файлы и передавать их на обработку таким вот хендлерам. По умолчанию в Джанго реализован хендлер, который умеет принимать маленькие файлы в память, а большие &#8212; сваливать на диск. Но этот интерфейс тоже предназначен для расширения юзерами. Возможная функциональность: потоковая распаковка архивов, проксирование файлов на удаленную машину без сохранения локально, проверка на вирусы, а также подсчет принятых байтов для реализации ajax&#8217;овых progress bar&#8217;ов.</p>

<p>Дефолтный <code>FileUploadHandler</code> в зависимости от размера файла рождает одного из двух наследников <code>UploadedFile</code> (см. далее): <code>InMemoryUploadedFile</code> или <code>TemporaryUploadedFile</code> (во временной директории на диске).</p>
</dd>

<dt><code>UploadedFile</code></dt>
<dd>Наследник <code>File</code>. По сути &#8212; реимплементация его, умеющая безопасно обрезать заданное пользователем имя и помнящая кодировку загрузки. Вряд ли когда-то кому-то понадобится от него наследоваться или что-то с ним делать напрямую.</dd>

<dt><code>FileField</code></dt>
<dd>Поле модели, которое представляет собой файл, сохраненный в модели. На самом деле, как и все остальные поля моделей, он виден в объектах моделей не сам по себе, а заменяет себя в классе на атрибут с данным (так, например, <code>CharField</code> заменяет себя в классе модели на простой строковый атрибут). Атрибут этот &#8212; дескриптор <code>FileDescriptor</code>, который при запросе его у модели через <code>obj.file_field</code> лезет в хранилище и достает оттуда файл в виде класса <code>FieldFile</code>.</dd>

<dt><code>FieldFile</code> (у вас не рябит еще в глазах от &#8220;F&#8221;, &#8220;i&#8221;, &#8220;e&#8221; и &#8220;l&#8221;?)</dt>
<dd>Наследник <code>File</code>, знающий, что он представлен в модели как имя файла. По этому имени он умеет доставать и сохранять контент из/в хранилище, на которое хранит у себя ссылку. Именно от этого класса предполагается наследоваться для создания своих интересных файловых полей. Самый очевидный пример такого интересного поля &#8212; всевозможные варианты ThumbnailImageField (<a href="http://webnewage.org/">привет, Саша</a>!). Кстати, если будете делать что-то в таком духе, пожалуйста, сохраняйте данные не напрямую в файловую систему, а пользуйтесь интерфейсом хранилища, чтобы тамбнейлы сохранялись туда же, где и сами картинки.</dd>
</dl>

<p>Вот, как-то так. <acronym title="Hope this helps">HTH</acronym>!</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/11/17/django-file-backend-chart/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Родительский контекст в inclusion-тегах</title>
		<link>http://softwaremaniacs.org/blog/2008/11/09/parent-context-in-inclusion-tag/</link>
		<comments>http://softwaremaniacs.org/blog/2008/11/09/parent-context-in-inclusion-tag/#comments</comments>
		<pubDate>Sun, 09 Nov 2008 09:06:22 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=467</guid>
		<description><![CDATA[Чтобы все не решили, что я перестал писать технические посты про Django, вот маленький пост средней полезности.

Если вы какое-то время работаете с Django и используете inclusion-теги, то наверняка сталкивались с неудобством, что кроме переменных контекста, возвращаемого из тега, в шаблон не попадает ничего. В частности, там нет всяких полезных {{ user }}, {{ MEDIA_URL }} [...]]]></description>
			<content:encoded><![CDATA[<p>Чтобы все не решили, что я перестал писать технические посты про Django, вот маленький пост средней полезности.</p>

<p>Если вы какое-то время работаете с Django и используете <a href="http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#inclusion-tags">inclusion-теги</a>, то наверняка сталкивались с неудобством, что кроме переменных контекста, возвращаемого из тега, в шаблон не попадает ничего. В частности, там нет всяких полезных <code>{{ user }}</code>, <code>{{ MEDIA_URL }}</code> и прочих &#8220;глобальных&#8221; вещей из родительских контекстов.</p>

<p><span id="more-467"></span></p>

<p>Я одно время был даже пропонентом такого подхода, потому что он &#8220;чище&#8221;: в контекст не сливается куча глобальных переменных, которые теоретически засоряют namespace внутреннего независимого шаблона. Но практика показывает, что все наоборот. Случаев, когда вам нужны глобальные данные в контексте, полно, а вот чтобы они мешались, я как-то не припоминаю совсем. Да и кроме того, поскольку шаблонный язык очень ограничен, прострелить себе ногу с помощью глобальных данных там довольно сложно. Средств мало :-).</p>

<p>Поэтому сейчас я думаю, что на самом деле inclusion-тег должен просто передавать в свой шаблон весь родительский контекст тоже. А пока этого не произошло, я хочу описать способ, которым я это делаю.</p>

<p>Проще всего сделать, конечно так:</p>

<pre><code>@register.inclusion_tag('template.html', takes_context=True)
def my_tag(context):
    return {
         'user': context['user'],
         'MEDIA_URL': context['MEDIA_URL'],
    }
</code></pre>

<p>Но это скучно и нуждается все время в поддержке по дописыванию нужных переменных. Чтобы это автоматизировать, можно было бы воспользоваться методом типа <code>items()</code>, выдающему все содержимое, но такого метода у контекста нет. Потому что контекст &#8212; это на самом деле не <code>dict</code>, а стек из <code>dict</code>ов. Поэтому нужно было бы бежать по всем стеку циклом, и у каждого <code>dict</code>а брать items(). Но это уже слишком многословно.</p>

<p>Поэтому вместо того, чтобы бороться со стековой структурой контекста, я использую ее себе на благо. Ведь вместо того, чтобы копировать по сути весь контекст, можно естественным образом добавить свои переменные в еще один уровень стека и передать этот же самый контекст в свой шаблон! Тогда шаблон увидит и свои переменные, и все остальные, что были в контексте до этого.</p>

<p>Жаль только, что декоратор <code>inclusion_tag</code> написан так, что сделать этого не позволяет. Поэтому я пишу тег руками:</p>

<pre><code>class MyTagNode(template.Node):
    def render(self, context):
        context.update({
            # собственные переменные
        })
        result = template.loader.get_template('template.html').render(context)
        context.pop()
        return result

@register.tag
def my_tag(parser, token):
    return MyTagNode()
</code></pre>

<p>Метод <code>context.update</code> берет переданный <code>dict</code> и &#8220;пушит&#8221; его наверх стека. Соответственно <code>context.pop</code> его потом оттуда удаляет. Вуаля!</p>

<p>Хотя, повторюсь, хотелось бы, чтобы это было дефолтным поведением inclusion-тега. Потому что он все таки удобней, чем написание тега целиком.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/11/09/parent-context-in-inclusion-tag/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Vooid</title>
		<link>http://softwaremaniacs.org/blog/2008/10/04/vooid/</link>
		<comments>http://softwaremaniacs.org/blog/2008/10/04/vooid/#comments</comments>
		<pubDate>Sat, 04 Oct 2008 09:51:15 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[OpenID]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=410</guid>
		<description><![CDATA[На прошедшем минибаре для веб-программистов я кратко рассказывал про то, как хорошо, что проект в Джанго строится из отдельных приложений. Не знаю, насколько это было полезно всем остальным, но лично меня это окончательно сподвигло на то, что надо делиться! SoftwareManiacs.Org состоит из нескольких приложений, и пару из них вполне можно выложить в мир &#8212; вдруг [...]]]></description>
			<content:encoded><![CDATA[<p>На прошедшем <a href="http://icamp.ru/event.php?event_id=118">минибаре для веб-программистов</a> я кратко рассказывал про то, как хорошо, что проект в Джанго строится из отдельных приложений. Не знаю, насколько это было полезно всем остальным, но лично меня это окончательно сподвигло на то, что надо делиться! SoftwareManiacs.Org состоит из нескольких приложений, и пару из них вполне можно выложить в мир &#8212; вдруг пригодятся.</p>

<p>Первой идет штука под странным названием &#8220;Vooid&#8221;.</p>

<p><span id="more-410"></span></p>

<p><a href="http://softwaremaniacs.org/soft/vooid/">Vooid</a> (Very Own OpenID server) &#8212; это OpenID-сервер для персонального сайта, написанного на Джанго. Обслуживает единственного пользователя &#8212; администратора, позволяя ему использовать любой URL своего сайта для авторизации по OpenID.</p>

<p>Штука получилась очень простой в установке &#8212; пара сеттингов да подключение в urlconf. По желанию &#8212; включение шаблонного тега с <code>&lt;link&gt;</code>ами в базовый шаблон. Также можно выбрать любой URL своего сайта в качестве OpenID и настроить SREG.</p>

<p>Так что, для тех, кто не хочет светить OpenID-провайдеру свою историю входов, по-моему, довольно удобная штука.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/10/04/vooid/feed/</wfw:commentRss>
		<slash:comments>24</slash:comments>
		</item>
		<item>
		<title>mysql_replicated</title>
		<link>http://softwaremaniacs.org/blog/2008/09/11/mysql_replicated/</link>
		<comments>http://softwaremaniacs.org/blog/2008/09/11/mysql_replicated/#comments</comments>
		<pubDate>Wed, 10 Sep 2008 21:22:04 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=389</guid>
		<description><![CDATA[Недавно Саша Кошелев проапдейтил наш репликационный Джанго-бэкенд для MySQL под новый джанговский API, а я наконец переименовал его в &#8220;mysql_replicated&#8221; (потому что &#8220;mysql_cluster&#8221; был похож на совершенно другой продукт). Кому интересно, можно брать и пользоваться.

P.S. Кстати, несмотря на предречения, что эта штука обязательно сломается, уже полгода никаких проблем с ней не было.
]]></description>
			<content:encoded><![CDATA[<p>Недавно <a href="http://webnewage.org/">Саша Кошелев</a> проапдейтил наш репликационный Джанго-бэкенд для MySQL под новый джанговский API, а я наконец переименовал его в &#8220;<a href="http://softwaremaniacs.org/soft/mysql_replicated/">mysql_replicated</a>&#8221; (потому что &#8220;mysql_cluster&#8221; был похож на совершенно другой продукт). Кому интересно, можно брать и пользоваться.</p>

<p>P.S. Кстати, несмотря на <a href="http://softwaremaniacs.org/blog/2008/03/18/mysql_cluster/#comment-28861">предречения</a>, что эта штука обязательно сломается, уже полгода никаких проблем с ней не было.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/09/11/mysql_replicated/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Транк — вот что главное</title>
		<link>http://softwaremaniacs.org/blog/2008/09/04/only-trunk-matters/</link>
		<comments>http://softwaremaniacs.org/blog/2008/09/04/only-trunk-matters/#comments</comments>
		<pubDate>Thu, 04 Sep 2008 05:33:34 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=380</guid>
		<description><![CDATA[Вышла долгожданная версия Джанго 1.0. Мои самые искренние поздравления всем, кто участвовал и всем, кто сочувствовал. Совершенно серьезно я считаю, что на данный момент это лучший питоновый веб-фреймворк.

Тем не менее, я хочу по случаю посмотреть на этот релиз с нетрадиционной стороны и, возможно, несколько войти в диссонанс с эйфорией, которая сейчас начнется :-).



Разговаривая с людьми [...]]]></description>
			<content:encoded><![CDATA[<p>Вышла долгожданная <a href="http://www.djangoproject.com/weblog/2008/sep/03/1/">версия Джанго 1.0</a>. Мои самые искренние поздравления всем, <a href="http://code.djangoproject.com/browser/django/trunk/AUTHORS">кто участвовал</a> и всем, кто сочувствовал. Совершенно серьезно я считаю, что на данный момент это лучший питоновый веб-фреймворк.</p>

<p>Тем не менее, я хочу по случаю посмотреть на этот релиз с нетрадиционной стороны и, возможно, несколько войти в диссонанс с эйфорией, которая сейчас начнется :-).</p>

<p><span id="more-380"></span></p>

<p>Разговаривая с людьми о Джанго, я обнаружил, что многие ждут релиз 1.0 как некое одиозное событие, которое&#8230; что? Оно, очевидно, что-то такое <em>знаменует</em> и <em>означает</em>. Но вот что именно в релизе 1.0 такого отличительного, что его все ждут.</p>

<h2>Стабильность</h2>

<p>Слышу, что ждут стабильности. Но это слово означает две разные вещи. По поводу одной &#8212; стабильности работы &#8212; в джанговском FAQ&#8217;е есть запись о том, что <a href="http://docs.djangoproject.com/en/dev/faq/general/#is-django-stable">Джанго и так стабилен</a>, и был стабильным еще до того, как даже исходники открылись. На нем работают реальные сайты и не падают. И это все правда.</p>

<p>Поэтому речь, конечно, не об этой стабильности. Речь о стабильности API, который в версии 1.0 перестанет изменяться.</p>

<p>Но секундочку&#8230; Возьмите любую произвольную ревизию кода, сохраните ее на диск и, уверяю вас, <em>ее</em> API тоже не будет меняться. Никогда. Вы можете написать на ней сайт, можете &#8212; два. Можете работать с ней 10 лет. API будет стабильным.</p>

<h2>Развитие</h2>

<p>Однако те, кто так пробовал делать, знают, что такая жизнь имеет свой существенный недостаток. Фреймворк развивается, в нем появляются новые фичи, рефакторятся старые неудачные подходы, и с ним становится проще жить. Наступает момент, когда вы на самом деле уже не хотите той стабильности, а хотите вот эту новую штуку, с которой кода в три раза меньше получается.</p>

<p>Позвольте снова спросить, чем в этом смысле отличается релиз 1.0 от произвольно зафиксированной ревизии? Что, Джанго после 1.0 перестанет развиваться? Да нет, корные разработчики уже заявляли, что довольно быстро после 1.0 появятся 1.1, 1.2. Думаю и 2.0 будет довольно быстро&#8230; Может быть эти самые 1.1 и 1.2 будут сохранять обратную совместимость с 1.0? Да, наверняка. Но штука в том, что поддержка обратной совместимости &#8212; священная корова в разработке Джанго, и она ломается крайне редко и крайне неохотно. И только по делу. Номера версий тут не причина, а следствие. Фактически, версией 2.0 Джанго скорее всего станет первая же, в которой придется избавиться от какого-нибудь старого API, проявившего себя не слишком удачно.</p>

<h2>Сторонние компоненты</h2>

<p>Третий аспект &#8220;особенности&#8221; версии 1.0 &#8212; предполагается, что ее API будут использовать в качестве ориентира разработчики сторонних компонентов. И это снова правда. Но я, убейте меня, не верю, что разработчики сторонних компонентов не будут использовать и новые API. А значит их уже нельзя будет использовать с 1.0 напрямую. Да еще и эти компоненты не будут обязаны соблюдать полную обратную совместимость с 1.0, тут уж все зависит от специфики кода и желания разработчика. Если разработчик вменяемый, то он, опять таки, тоже старается сохранять обратную совместимость независимо от того, в какой версии фреймворка его стукнуло желание написать что-нибудь полезное.</p>

<h2>Транк</h2>

<p>Чтобы не быть голословным, расскажу, что у нас в Яндексе внутренние релизы Джанго &#8212; событие вполне заурядное. Как только кому-то из разработчиков нужна какая-то фича из транка, ближайшая удобная ревизия заворачивается в пакет, объявляется следующим релизом, и дальше разработчик-инициатор начинает кодить свой код, а остальные &#8212; апдейтить свои проекты под те несовместимости, которые к тому моменту накопились.</p>

<p>Думаю, мы продолжим делать это и после 1.0.</p>

<p>И вот это в Джанго, на мой взгляд, самое главное. Имея такой транк, который живет и практически не ломается, лично мне никаких релизов не надо :-)</p>

<h2>И тем не менее&#8230;</h2>

<p>Тем не менее, версия 1.0 &#8212; это хорошо. Я могу сходу придумать две причины, почему это так:</p>

<ul>
<li><p>Это большой <em>символ</em>, и очень хороший <em>стимул</em>: я давно не помню такого шквала активности в разработке Джанго, который начался с тех пор, как был объявлен план на 1.0. Были наконец-то доделаны и влиты в транк важнейшие рефакторинги, ждавшие своей очереди: новая админка, обработка аплоудов, подключаемые файловые хранилища и т.д. Фреймворк стал гораздо лучше за последние пару месяцев.</p></li>
<li><p>Это знаковое событие с маркетинговой точки зрения. Вокруг Джанго есть много людей, которые не интересуются процессом подробно, и которые привыкли к тому, что версия 1.0 &#8212; это &#8220;готово, можно пользоваться&#8221;. Теперь они, наконец-то, заткнутся о том, что Джанго &#8220;все еще бета&#8221; :-).</p></li>
</ul>

<p>Поздравляем нас всех!</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/09/04/only-trunk-matters/feed/</wfw:commentRss>
		<slash:comments>44</slash:comments>
		</item>
		<item>
		<title>Празднование релиза Django 1.0</title>
		<link>http://softwaremaniacs.org/blog/2008/08/28/django-release-party/</link>
		<comments>http://softwaremaniacs.org/blog/2008/08/28/django-release-party/#comments</comments>
		<pubDate>Thu, 28 Aug 2008 12:34:37 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=362</guid>
		<description><![CDATA[ Куда все идутЯ приглашаю:07.09.2008 15:00, Б2, Большая Садовая, д. 8 (м. Маяковская выход в сторону Театра Сатиры), метро МаяковскаяПразднование релиза Django 1.0Есть идея отпраздновать вместе с остальным миром долгожданный релиз Django 1.0!

Приглашаются все, кому это интересно. Познакомимся (кто не знаком), пообщаемся, расскажем друг о друге, сделаем фотки и пожелаем доброго пути любимому фреймворку. Программировать [...]]]></description>
			<content:encoded><![CDATA[<div class="vevent" style="font: 13px/1.3em arial, sans-serif; color: #000; background: #fff; border: 1px solid #eee; _width: 100%; overflow: hidden; margin: 10px 0;"><div style="float: right; font: 0.85em Verdana, sans-serif; padding: 3px 8px;"><a target="_blank"  href="http://kuda.yandex.ru/" style="text-decoration: none;"><font color="#de493f" size="2"> <img width="16" height="16" border="0" align="absmiddle" vspace="5" style="margin: 0;" src="http://img.yandex.net/i/afisha.gif">Куда все идут</font></a></div><div style="padding: 10px;"><div><span style="background: #ffd6ad; padding: 0.2em; float: left; margin: 0 0 5px;"><font size="2"><b><font color="#ff0000">Я</font></b> приглашаю:</font></span></div><p style='clear: left; margin: 0 0 10px;'><font size="2"><abbr style="border: none;" class="dtstart" title="2008-09-07T15:00">07.09.2008 15:00</abbr>, <span class="location">Б2, Большая Садовая, д. 8 (м. Маяковская выход в сторону Театра Сатиры)</span>, метро Маяковская</font></p><div style="float: left;"><a style="border: none; text-decoration: none;" href = "http://kuda.yandex.ru/events/96069/"><img border="0" vspace="4" alt="Празднование релиза Django 1.0" src="http://kuda.yandex.ru/media/images/user_file_96069.png" style="border: none; margin: 0 10px 10px 0;"/></a></div><div style="margin: 0; overflow: hidden; zoom: 1;"><div style="margin: 0 0 20px;"><a style="color: #14a115;" class="url summary" href = "http://kuda.yandex.ru/events/96069/"><font size="4" color="#15a115">Празднование релиза Django 1.0</font></a></div><div><a style="border: none; text-decoartion: none;" href="http://kuda.yandex.ru/events/96069/join/3250435/"><img style="margin: 0;" src="http://kuda.yandex.ru/media/img/button_add.gif" alt="Присоединиться" width="156" height="32" vspace="8" border="0"/></a></div></div><div style="font-style: italic; clear: both;"><p>Есть идея отпраздновать вместе с остальным миром долгожданный релиз Django 1.0!</p>

<p>Приглашаются все, кому это интересно. Познакомимся (кто не знаком), пообщаемся, расскажем друг о друге, сделаем фотки и пожелаем доброго пути любимому фреймворку. Программировать не будем :-)</p>

<p>В качестве места сбора предлагается клуб &#8220;PodМосковье&#8221;. Говорят, вполне демократичное место, и народу много можно вместить. Если у кого-то есть другие предложения &#8212; пишите.</p>

<p>И отдельная просьба &#8212; обязательно отмечайтесь по кнопке &#8220;Присоединиться&#8221;, все, кто собирается пойти, чтобы знать, на сколько человек просить уважаемый клуб место зарезервировать.</p>

<p>P.S. Мероприятие совершенно не связано с Яндексом, это просто встреча по интересам.</p></div></div></div>

<p>Geek&#8217;s note: этот пост, созданный средствами нашего мегасайта &#8220;<a href="http://kuda.yandex.ru/">Куда Все Идут</a>&#8221; содержит микроформат hEvent, и если у вас браузер умеет его распознавать, его можно добавить в ваш любимый веб-календарь.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/08/28/django-release-party/feed/</wfw:commentRss>
		<slash:comments>22</slash:comments>
		</item>
		<item>
		<title>Cicero: месяц спустя</title>
		<link>http://softwaremaniacs.org/blog/2008/07/15/cicero-one-month-later/</link>
		<comments>http://softwaremaniacs.org/blog/2008/07/15/cicero-one-month-later/#comments</comments>
		<pubDate>Tue, 15 Jul 2008 06:42:09 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Cicero]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=342</guid>
		<description><![CDATA[Прошел месяц с переезда моих форумов на Cicero и мне захотелось поделиться тем, как оно все живет и работает. Благо, живет оно, если вкратце, хорошо, и делиться этим приятно :-)



Месяц прошел в неспешном рефакторинге (наконец перевел формы на ModelForm, которых не сущетсвовало, когда начинал их писать), добавлении мелких удобств (автоподсветка URL&#8217;ов, чтение SRE из OpenID) [...]]]></description>
			<content:encoded><![CDATA[<p>Прошел месяц с <a href="/blog/2008/06/15/forum-import-release/">переезда моих форумов на Cicero</a> и мне захотелось поделиться тем, как оно все живет и работает. Благо, живет оно, если вкратце, хорошо, и делиться этим приятно :-)</p>

<p><span id="more-342"></span></p>

<p>Месяц прошел в неспешном рефакторинге (наконец перевел формы на <a href="http://www.djangoproject.com/documentation/modelforms/">ModelForm</a>, которых не сущетсвовало, когда начинал их писать), добавлении мелких удобств (автоподсветка URL&#8217;ов, чтение SRE из OpenID) и правке мелких багов (например, пропускались топики из одних пробелов). В общем, ничего существенного не изменилось.</p>

<p>А вот по ощущениям от использования могу не стесняясь сказать, что меня просто <em>прет</em> от того, как оно работает :-). Радикальный минимализм сначала казался странным, но чем дальше, тем больше привыкаешь к этому свежему ощущению. Не знаю, правда, как оно для остальных участников&#8230;</p>

<h2>Регистрация и спам</h2>

<p>Похоже, я таки <a href="/blog/2005/06/20/forum-registration/">был прав</a>: форум без регистрации живет лучше. По ощущениям, у меня сейчас примерно половина вопросов и обсуждений начинается с незарегистрированных новичков. И это создает контент, полезный для всего форума. То есть, задавание человеком вопроса &#8212; это не только услуга человеку, но и услуга форуму.</p>

<p>А регистрации начинаются уже потом. Вот <a href="http://softwaremaniacs.org/forum/django/2578/">типичный случай</a>.</p>

<p>На прошлом движке (<a href="http://punbb.informer.com/">PunBB</a>) спам приходил сотнями в день. На Cicero спама нет. <em>Совсем</em>. Причем, не только в самом форуме, но и в карантинной очереди. Я четко помню там ровно четыре поста за месяц: одно тестовое, а три других &#8212; false positives, про которые <a href="http://akismet.com/">Акисмет</a> поосторожничал и посчитал спамом, хотя они таковым не являлись. Их я, конечно, пропихнул в форум, научив собачку, что на них лаять больше не надо.</p>

<p>По большей части отсутствие спама я связываю с нестандартной формой постинга. <a href="http://webnewage.org/">Саша Кошелев</a>, правда, предрек мне, что &#8220;со временем научатся&#8221;, но я думаю, что это маловероятно: кому нужно специально затачивать роботов под форум, существующий пока в единственном экземпляре, на котором общаются гики?</p>

<h2>Фичи Cicero</h2>

<p>Если говорить о специфических фичах, то уже пару раз я <a href="http://softwaremaniacs.org/forum/web/2493/#10258">воспользовался отщеплением топиков</a> &#8212; это когда поднимается вопрос, явно выходящий за пределы текущей темы. Результат пока нравится.</p>

<p>А еще я, кажется, стал узнавать &#8220;в лицо&#8221; некоторых мутантов :-).</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/07/15/cicero-one-month-later/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Спринт финишировал</title>
		<link>http://softwaremaniacs.org/blog/2008/07/13/sprint-finished/</link>
		<comments>http://softwaremaniacs.org/blog/2008/07/13/sprint-finished/#comments</comments>
		<pubDate>Sun, 13 Jul 2008 12:59:32 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Яндекс]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=341</guid>
		<description><![CDATA[Ну что же, мы поспринтовали! С меня &#8212; короткий отчет.



Из собиравшихся 30 человек пришли, если не ошибаюсь, 18. Учитывая всякие тенические накладки, начали спринт мы где-то через час после назначенного времени. WiFi был, электричество тоже :-).

И тут я должен сказать, что думал, что этого будет достаточно. Но ошибся. Довольно скоро стало понятно, что схема, когда [...]]]></description>
			<content:encoded><![CDATA[<p>Ну что же, мы <a href="/blog/2008/07/03/django-sprin/">поспринтовали</a>! С меня &#8212; короткий отчет.</p>

<p><span id="more-341"></span></p>

<p>Из собиравшихся 30 человек пришли, если не ошибаюсь, 18. Учитывая всякие тенические накладки, начали спринт мы где-то через час после назначенного времени. WiFi был, электричество тоже :-).</p>

<p>И тут я должен сказать, что думал, что этого будет достаточно. Но ошибся. Довольно скоро стало понятно, что схема, когда каждый выбирает себе тикеты, не очень работает. Особенно, когда среди пришедших есть те, кто только хочет попробовать себя в работе над откытым проектом. Поэтому ставлю себе галочку, что внутренний процесс все-таки надо подробно организовывать.</p>

<p>Еще одна штука, которая явилась для меня откровением, что 5 часов на такое событие мало; у нас в офисе в тот день полы менялись, поэтому около шести вечера нас выгнали. Мало, потому что первые часса три ушли у всех на то, чтобы &#8220;войти в поток&#8221; и начать эффетктивно работать. Под конец, я заметил, работа пошла веселей. Да еще и на IRC-канале появился один из джанговский triager&#8217;ов и прямо в онлайне несколько наших тикетов подвинул в &#8220;Ready for checkin&#8221;, а также дал несколько советов.</p>

<p>В итоге, тикетов мы порешали <a href="http://code.djangoproject.com/query?keywords=~yandex-sprint&amp;order=priority">не очень много</a>. Но было несколько интересных обсуждений. А особенно мне запомнились две вещи:</p>

<ul>
<li><p><a href="http://barbuza.info/">Виктор</a> бессстрашно взялся за древний <a href="http://code.djangoproject.com/ticket/13">ticket 13</a>, и добился там неплохого прогресса (Викто, патчик доделаешь?) Тикет этот во время спринта успел обрасти собственным издевательским фольклором: &#8220;не знаешь что делать &#8212; а вон, возьми тринадцатый&#8221; :-)</p></li>
<li><p>Трое джангистов &#8212; <a href="http://gsql.org/">Тарас Халтурин</a>, <a href="http://oraclub.ru/">Николай Лещев</a> и <a href="http://plesser.livejournal.com/">Олег Плессер</a> сваяли целый <a href="http://code.djangoproject.com/ticket/7732">БД-бэкенд для Оракла с поддержкой пула коннектов</a>. Базовое &#8220;тупое&#8221; приложение с ним ускорилось в три раза.</p></li>
</ul>

<p>Ну а в завершение мероприятия мы накормили программистов пиццей. Кажется, все были довольны :-). Я считаю, будем собираться еще!</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/07/13/sprint-finished/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Спринт!</title>
		<link>http://softwaremaniacs.org/blog/2008/07/03/django-sprin/</link>
		<comments>http://softwaremaniacs.org/blog/2008/07/03/django-sprin/#comments</comments>
		<pubDate>Thu, 03 Jul 2008 10:42:13 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Яндекс]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=338</guid>
		<description><![CDATA[Итак, вроде бы мы все утрясли, и спринту быть! Чтобы не повторяться о подробностях и порядке &#8220;регистрации&#8221;, сошлюсь на свой пост в корп-блог.

Да, вот забыл: кто уже написал в прошлом посте, что придет, пришлите пожалуйста еще разок свои имя и фамилию мне на &#105;&#115;&#97;&#103;&#97;&#108;&#97;&#101;&#118;&#64;&#121;&#x61;&#x6e;&#x64;&#x65;&#x78;&#x2d;&#x74;&#x65;&#x61;&#x6d;&#x2e;&#x72;&#x75;. Надо надо заявку в охрану сделать.
]]></description>
			<content:encoded><![CDATA[<p>Итак, вроде бы мы все утрясли, и спринту быть! Чтобы не повторяться о подробностях и порядке &#8220;регистрации&#8221;, сошлюсь на свой <a href="http://company.yandex.ru/blog/message.xml?msg=102921">пост в корп-блог</a>.</p>

<p><strong>Да, вот забыл</strong>: кто уже написал в прошлом посте, что придет, пришлите пожалуйста еще разок свои имя и фамилию мне на <a href="mai&#108;&#116;&#111;&#58;&#105;&#115;&#97;&#103;&#97;&#108;&#97;&#101;&#118;&#64;&#121;&#x61;&#x6e;&#x64;&#x65;&#x78;&#x2d;&#x74;&#x65;&#x61;&#x6d;&#x2e;&#x72;&#x75;">&#105;&#115;&#97;&#103;&#97;&#108;&#97;&#101;&#118;&#64;&#121;&#x61;&#x6e;&#x64;&#x65;&#x78;&#x2d;&#x74;&#x65;&#x61;&#x6d;&#x2e;&#x72;&#x75;</a>. Надо надо заявку в охрану сделать.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/07/03/django-sprin/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Может, спринт?</title>
		<link>http://softwaremaniacs.org/blog/2008/06/30/fancy-a-sprint/</link>
		<comments>http://softwaremaniacs.org/blog/2008/06/30/fancy-a-sprint/#comments</comments>
		<pubDate>Mon, 30 Jun 2008 13:33:53 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Яндекс]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=337</guid>
		<description><![CDATA[У меня есть идея провести 10-12 июля (четверг-суббота) Джанго-спринт в стенах Яндекса в Москве. То есть, собраться заинтересованным людям командой и поработать над Джангой вживую вместе с остальным миром. Описание грядущего спринта уже какое-то время лежит на wiki. Вкратце &#8212; будем доделывать newforms-admin.

В Яндексе у меня есть предварительная договоренность, что технически мы это организовать сможем. [...]]]></description>
			<content:encoded><![CDATA[<p>У меня есть идея провести 10-12 июля (четверг-суббота) Джанго-спринт в стенах Яндекса в Москве. То есть, собраться заинтересованным людям командой и поработать над Джангой вживую вместе с остальным миром. <a href="http://code.djangoproject.com/wiki/SprintEuroPython2008">Описание грядущего спринта</a> уже какое-то время лежит на wiki. Вкратце &#8212; будем доделывать newforms-admin.</p>

<p>В Яндексе у меня есть предварительная договоренность, что технически мы это организовать сможем. Пожалуйста, напишите тут в комментариях, кому это было бы интересно, и когда было бы удобно прийти.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwaremaniacs.org/blog/2008/06/30/fancy-a-sprint/feed/</wfw:commentRss>
		<slash:comments>21</slash:comments>
		</item>
		<item>
		<title>Импорт и релиз форума</title>
		<link>http://softwaremaniacs.org/blog/2008/06/15/forum-import-release/</link>
		<comments>http://softwaremaniacs.org/blog/2008/06/15/forum-import-release/#comments</comments>
		<pubDate>Sat, 14 Jun 2008 21:03:02 +0000</pubDate>
		<dc:creator>Иван Сагалаев</dc:creator>
				<category><![CDATA[Cicero]]></category>

		<guid isPermaLink="false">http://softwaremaniacs.org/blog/?p=334</guid>
		<description><![CDATA[Вчера рано ночью случилось радостное событие: я переключил свои форумы на движок Cicero! Удачно использовав первую часть отпуска, я наконец доделал две последние абсолютно необходимые вещи: импорт сод