<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/atom10full.xsl" type="text/xsl" media="screen"?><?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/itemcontent.css" type="text/css" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru"><title>Интернет нового века | Последние записи | </title><link href="http://webnewage.org/" rel="alternate" /><id>http://webnewage.org/</id><updated>2008-11-17T22:23:18Z</updated><subtitle>Последние записи блога "Интернет нового века"</subtitle><link rel="self" href="http://feeds.feedburner.com/webnewage" type="application/atom+xml" /><entry><title>Тебе ровно год!</title><link href="http://webnewage.org/post/2008/11/17/tebe-rovno-god/" rel="alternate" /><updated>2008-11-17T22:23:18Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/11/17/tebe-rovno-god/</id><summary type="html">
&lt;p&gt;Точнее с момента &lt;a href="http://webnewage.org/post/2007/11/17/nachalo/"&gt;первого&lt;/a&gt; поста прошел ровно год. Да, много чего я успел рассказать на твоих страницах, мой блог, но ещё больше не успел.&lt;/p&gt;

&lt;p&gt;Ты изменил мою жизнь. Да, это чистая правда. Ты заставил меня посмотреть на себя под другим углом. Ты помог мне получить бесценный опыт письма и общения. Ты помог мне найти себя в системе координат жизни. И теперь я точно знаю зачем ты есть и для чего. Мы вместе будем продолжать идти вперед и у нас всё получится. &lt;/p&gt;

&lt;p&gt;Не буду много сейчас говорить, т.к. совсем скоро я тебе сделаю подарок и это будет гораздо ценнее чем тысяча слов.&lt;/p&gt;

&lt;p&gt;Спасибо тебе. Поздравляю тебя. Долгой жизни тебе.&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/lichnoe/"&gt;личное&lt;/a&gt;

	&lt;a href="/tag/o-bloge/"&gt;о блоге&lt;/a&gt;

</summary></entry><entry><title>Сливаемся</title><link href="http://webnewage.org/post/2008/10/13/slivaemsya/" rel="alternate" /><updated>2008-10-13T02:29:22Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/10/13/slivaemsya/</id><summary type="html">
&lt;p&gt;Беру небольшую передышку в своих изысканиях о композитных полях для большего прилива вдохновения. Подамся-ка я в другую область на время.&lt;/p&gt;

&lt;p&gt;Представим себе ситуацию(а лучше вспомним один из прошлых проектов, который вы делали:)), что есть пара приложений от сторонних разработчиков. И это не какие-то совсем простые приложения, а что-то более комплексное. А отличительной чертой их коплексности будет наличие в каждом своей модели профиля. Ведь было такое? Увы, было. Ну и пусть будут, только вот ещё незадача - их поля являются пересекающимися множествами. Ну т.е. и в том и в другом есть, допустим, поле с ссылкой на сайт владельца. И что же делать? Конечно, для облегчения работы интерфейс редактирования информации профиля один для выбранного &lt;em&gt;основного&lt;/em&gt; профиля. А объектов профиля закрепленных за пользователем два (а может быть и больше, если проект используем много разных приложений). Надо как-то синхронизировать данные...&lt;/p&gt;

&lt;p&gt;Вообще ситуация того, что в каждом мало-мальски большом приложении свой профиль - это как бы уже давно проблема. И тут ничег уже не поможет. Универсальный профиль помещенный в contrib, конечно, может как-то решить её, но не всегда. Да и потом, с профилем разберемся, так что-то другое появится. Надо находить универсальные пути решения.&lt;/p&gt;

&lt;p&gt;Кстати, обсуждения данной темы были как на &lt;a href="http://softwaremaniacs.org/blog/2008/08/28/django-release-party/"&gt;неформальной встрече&lt;/a&gt; джангистов по поводу 1.0 релиза, так и на не менее неформальном &lt;a href="http://softwaremaniacs.org/blog/2008/09/25/barcamp/"&gt;минибаре&lt;/a&gt; для программистов. В результате всех обсуждений, стало понятно, что решений данной проблемы два - это либо синхронизация пересекающихся полей в моделях, либо какой-то базовый прототип/интерфейс для модели, на который можно рассчитывать и &lt;em&gt;ссылаться&lt;/em&gt; в коде.&lt;/p&gt;

&lt;p&gt;Сейчас я покажу свой вариант первого решения - синхронизации. В лучших своих традициях - начну с примера.&lt;/p&gt;

&lt;p&gt;Итак, есть две модели профиля пользователя в разных приложениях. Вот такие:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;user_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;nickname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;www&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;birth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OtherProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;nickname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;website&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Поля у них отражают одну и туже суть, но некоторые по разному названы. Для краткости, я не показывал отличных полей, но они скорей всего будут и будут отражать специфику уже конкретного приложения.&lt;/p&gt;

&lt;p&gt;Нам надо чтобы объекты этих моделей, прикрепленные к одному и тому же пользователю, синхронизировались при изменении любых из них. Т.е. нам надо слить &lt;em&gt;изменения&lt;/em&gt; одной в другую. Я почему-то решил назвать это, уже преследующим меня, словом - &lt;em&gt;merge&lt;/em&gt;. И получился у меня &lt;a href="http://svn.turbion.org/turbion/trunk/turbion/utils/merging.py"&gt;merging framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Применим его для трекинга изменений в наших гипотетических профилях. Для указания мета информации трекинга, надо объявить объекты &lt;em&gt;прослойки&lt;/em&gt;. Например так:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;merging&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyProfileLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelLayer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyProfile&lt;/span&gt;
   &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nickname&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="n"&gt;aliases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="s"&gt;&amp;quot;site&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;www&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s"&gt;&amp;quot;day_of_birth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;birth&amp;quot;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;user_ptr&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OtherProfileLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelLayer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OtherProfile&lt;/span&gt;
   &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nickname&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="n"&gt;aliases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="s"&gt;&amp;quot;site&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;website&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s"&gt;&amp;quot;day_of_birth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;dob&amp;quot;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;
   &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Тут я указал модель(&lt;code&gt;model&lt;/code&gt;) и поля в атрибуте &lt;code&gt;fields&lt;/code&gt;, в частности нужно сливать никнеймы. Для полей с разными названиями я указываю алиасы через &lt;code&gt;aliases&lt;/code&gt;. Причем, обратите внимания, что алиасы имеют одинаковое название для обеих прослоек. На основе именно этих имен будет происходить синхронизация настоящих полей. Для обеих прослоек я указываю ещё и атрибут - &lt;code&gt;key&lt;/code&gt;. На базе него будет происходить выборка связных профилей.&lt;/p&gt;

&lt;p&gt;Для второй прослойки я ещё указал &lt;code&gt;create=True&lt;/code&gt;, чтобы, если главный профиль(&lt;code&gt;MyProfile&lt;/code&gt;) существует, а &lt;code&gt;OtherProfile&lt;/code&gt; нет, создавался бы автоматически объект последнего.&lt;/p&gt;

&lt;p&gt;Ура, описательную часть закончили, теперь надо запустить механизм отслеживания. Вот так:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;merging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;MyProfileLayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OtherProfileLayer&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Конечно, прослоек может быть больше двух. Этот код, скорей всего, нужно размещать уже на уровне проекта, тем самым, связывая приложения в нем.&lt;/p&gt;

&lt;p&gt;Итак, теперь все изменения в указанных полях будут перетекать в связные объекты:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_ptr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nickname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;                                    &lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://webnewage.org/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;birth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1987-06-25&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OtherProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;birth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1987&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;user_ptr_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;www&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u&amp;#39;http://webnewage.org/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;dob&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1987&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;&amp;#39;website&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u&amp;#39;http://webnewage.org/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;http://yandex.ru/&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OtherProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;website&lt;/span&gt;
&lt;span class="s"&gt;u&amp;#39;http://yandex.ru/&amp;#39;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nickname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;foobar&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nickname&lt;/span&gt;
&lt;span class="s"&gt;u&amp;#39;foobar&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Отныне интеграция нескольких больших приложений может стать немного проще. Базовым требованиям такое решение удовлетворяет, но я уже вижу, где могут возникнуть проблемы и где можно сделать несколько улучшений.&lt;/p&gt;

&lt;p&gt;Как обычно основные use cases покрыты &lt;a href="http://svn.turbion.org/turbion/trunk/turbion/utils/tests/merging.py"&gt;тестами&lt;/a&gt;, так что можете смело пользоваться.&lt;/p&gt;

&lt;p&gt;На очереди следующий вызов - фреймворк прототипов. Вперед  и с песней!;)&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/orm/"&gt;orm&lt;/a&gt;

	&lt;a href="/tag/python/"&gt;python&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

</summary></entry><entry><title>Композиция: ForeignAttributeField</title><link href="http://webnewage.org/post/2008/10/7/kompozitsiya-foreignattributefield/" rel="alternate" /><updated>2008-10-07T01:20:33Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/10/7/kompozitsiya-foreignattributefield/</id><summary type="html">
&lt;p&gt;Как вы догадались, я продолжаю тему &lt;a href="http://webnewage.org/post/2008/9/26/krasivaya-kompozitsiya/"&gt;денормализации и моей реализации композитных полей&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;С момента прошлого поста я успел значительно улучшить базовый &lt;a href="http://svn.turbion.org/turbion/trunk/turbion/utils/composition.py"&gt;CompositionField&lt;/a&gt; и решить несколько концептуальных проблем.&lt;/p&gt;

&lt;p&gt;Итак что же новое появилось:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Сделал низкоуровневый &lt;code&gt;CF&lt;/code&gt; полноценным классом, который теперь удобно сабклассить и добавлять новый функционал&lt;/li&gt;
&lt;li&gt;Появилось место для интроспекции. Присоединение к хост модели стало более интеллектуальным из-за возможного отложенного присоединения.&lt;/li&gt;
&lt;li&gt;Чуть-чуть изменился интерфейс - параметр &lt;code&gt;commit&lt;/code&gt; теперь может быть задан  для конкретного триггера. Это сугубо практическое изменение, которое помогло решить одну проблему с бесконечной рекурсией.&lt;/li&gt;
&lt;li&gt;По совету &lt;a href="http://softwaremaniacs.org/about/"&gt;Вани Сагалаева&lt;/a&gt; добавил генерацию &lt;code&gt;freeze_FOO&lt;/code&gt; метода, который включает/выключает обработку сигналов. Полезно когда надо обработать какие-то данные скопом, а потом так же скопом пересчитать денормализованные поля.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Я грозился написать высокоуровневые обертки над &lt;code&gt;CF&lt;/code&gt; чтобы облегчить использование и упростить конечный интерфейс. Сегодня я расскажу о первом сабклассе &lt;code&gt;CF&lt;/code&gt;: &lt;code&gt;ForeignAttributeField&lt;/code&gt; - поле которое отслеживает изменения некого внешнего поля в связанном объекте. Причем, поле может находится на любом уровне вложенности связи, что иногда очень удобно. Как я уже писал в прошлый раз - поля объектов, на которые непосредственно ссылается модель, так денормализовывать смыла мало. Проще использовать &lt;code&gt;select_related&lt;/code&gt;. Но если заветный атрибут лежит глубже и для доступа к нему надо приджоинить, допустим, пять таблиц, то тут уже другой расклад и это может оказаться вполне хорошим юзкейсом. Кстати, это поле очень похоже на оригинальный &lt;code&gt;DenormField&lt;/code&gt;, предложенный Эндрю Годвином. Я конечно не буду тут показывать пять уровней связей, а покажу лишь две, но формально их может быть сколь угодно много. Так же и композитных полей в модели может быть не ограниченное количество.&lt;/p&gt;

&lt;p&gt;Пример с фильмом и режиссером из предыдущего поста перепишу по новому и добавлю ещё одно поле:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;director&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;director_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignAttributeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;director.name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;director_country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignAttributeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;director.country.name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь посмотрим как это работает:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;USA&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Steven Spielberg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director_name&lt;/span&gt;
&lt;span class="s"&gt;&amp;#39;Steven Spielberg&amp;#39;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director_country&lt;/span&gt;
&lt;span class="s"&gt;&amp;#39;USA&amp;#39;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Steven Allan Spielberg&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director_name&lt;/span&gt;
&lt;span class="s"&gt;u&amp;#39;Steven Allan Spielberg&amp;#39;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;United States&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director_country&lt;/span&gt;
&lt;span class="s"&gt;u&amp;#39;United States&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видите всё довольно прозрачно и естественно. За это я и боролся. Надо только отметить, что в данном примере я явно каждый раз после изменения связных моделей тяну из базы объект &lt;code&gt;movie&lt;/code&gt;, чтобы в нем были уже обновленные данные.&lt;/p&gt;

&lt;p&gt;Кстати о борьбе. Скажу я вам, это потрясающий фан - придумывать и реализовывать подобные вещи. Сколько же нового понимаешь о структуре фреймворка. Словно как какой-то путешественик, пробираешься сквозь джунгли, прорубаешь себе дорогу и находишь выходы в непростых ситуациях. Очень интересно, занимательно и позновательно.&lt;/p&gt;

&lt;p&gt;Поразило также насколько универсальным и обобщенным оказался &lt;code&gt;CF&lt;/code&gt; сам по себе. Поскольку его функциональную часть не пришлось менять практически совсем за исключением выноса параметра &lt;code&gt;commit&lt;/code&gt; в объявление триггера. Это связано с тем, что далеко не после всех вызовов обработчиков сигналов можно звать &lt;code&gt;save&lt;/code&gt; объекта. Например его нельзя вызывать после &lt;code&gt;pre_save&lt;/code&gt;/&lt;code&gt;post_save&lt;/code&gt; сигналов того же объекта что и был обновлен, а то получается - бесконечная рекурсия.&lt;/p&gt;

&lt;p&gt;Ещё - вся функциональность этого высокоуровнего поля заключается в правильном составлении тригеров и обработчиков событий (на базе интроспекции моделей), которые передаются в "отложенный" конструктор &lt;code&gt;CF&lt;/code&gt;. Ничего более. Супер!&lt;/p&gt;

&lt;p&gt;Файл с исходниками доступен как и раньше &lt;a href="http://svn.turbion.org/turbion/trunk/turbion/utils/composition.py"&gt;тут&lt;/a&gt;. Если вы его посмотрите, то заметите заготовки для ещё двух типов полей: &lt;code&gt;ChildsAggregationField&lt;/code&gt;, &lt;code&gt;AttributesAggregationField&lt;/code&gt; - но они пока на уровне идеи. Как дойдут руки, то обязательно их реализую. Некие намеки на их использования можно посмотреть в тестах.&lt;/p&gt;

&lt;p&gt;Ах да, все примеры использования которые я приводил и буду - либо уже являются частью тест набора для &lt;code&gt;CF&lt;/code&gt; либо будут туда вписаны. Сами тесты доступны &lt;a href="http://svn.turbion.org/turbion/trunk/turbion/utils/tests/composition/"&gt;здесь&lt;/a&gt;. Они тоже пока ещё не устаканились, не покрывают максимум кода и возможно подвергнуться какой-то реорганизации. Но как источник примеров годятся уже сейчас.&lt;/p&gt;

&lt;p&gt;А вам как? Нравится?&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/orm/"&gt;orm&lt;/a&gt;

	&lt;a href="/tag/python/"&gt;python&lt;/a&gt;

	&lt;a href="/tag/signals/"&gt;signals&lt;/a&gt;

	&lt;a href="/tag/snippets/"&gt;snippets&lt;/a&gt;

	&lt;a href="/tag/denormalizatsiya/"&gt;денормализация&lt;/a&gt;

	&lt;a href="/tag/optimizatsiya/"&gt;оптимизация&lt;/a&gt;

</summary></entry><entry><title>Красивая композиция</title><link href="http://webnewage.org/post/2008/9/26/krasivaya-kompozitsiya/" rel="alternate" /><updated>2008-09-26T02:18:18Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/9/26/krasivaya-kompozitsiya/</id><summary type="html">
&lt;p&gt;Нет, я не про музыку решил написать. А про композицию данный, агрегацию если хотите.&lt;/p&gt;

&lt;p&gt;В джанге на данный момент агрегации в ORM нет. Но как известно скоро должна появиться, а значит жизнь в очередной раз станет проще.&lt;/p&gt;

&lt;h2&gt;Зачем?&lt;/h2&gt;

&lt;p&gt;Но на много ли? Да, писать запросы с агрегацией станет проще.  Но в большинстве случаешь выполнять запрос для сервера легче не станет. Вот ту на помощь приходит техника денормализации.&lt;/p&gt;

&lt;p&gt;Денормализация - это добавления избыточности данных для увеличения производительности чтения данный. Обычно чтение данных требуется гораздо чаще чем их запись, а значит на запись в принципе можно потратить чуть больше времени, за-то потом много раз сэкономить на выборке. Агрегацию я привел в качестве, наверно, самого популярного источника данных для денормализации, но ею конечно не ограничивается применение денормализации. Суть в том, что лучше что-то тяжелое посчитать при записи и сохранить, чем каждый раз при чтении пересчитывать заново.&lt;/p&gt;

&lt;p&gt;Наверно многие из вас в своих проектах писали код наподобие такого, тем более, если писали &lt;em&gt;"свой блог движок"&lt;/em&gt;:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;comment_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Либо добивались подобного функционала с использованием сигналов. Ещё наверно дописывали метод &lt;code&gt;update_что-то там&lt;/code&gt; чтобы в случае чего, можно было скопом посчитать денормализованное поле:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="c"&gt;#...&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_comment_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для поддержки консистентности денормализованного поля, надо предусмотреть возможные сценарии его изменения и плюс сделать какой удобный интерфейс для пересчета всего.&lt;/p&gt;

&lt;p&gt;Пример с &lt;code&gt;count()&lt;/code&gt; мне кажется самым распространенным. Денормализовать можно всё что угодно. Можно подсчитывать какие-то интервалы, записывать результаты сложных выборок. В общем как-бы кешировать трудно вычисляемые данные. Но это каждый раз приходится писать самому. А сценарии-то очень похожие обычно встречаются.&lt;/p&gt;

&lt;p&gt;Меня всегда мучал вопрос - можно ли это как-то автоматизировать? Оказывается можно и даже вполне красиво.&lt;/p&gt;

&lt;h2&gt;Долгий путь&lt;/h2&gt;

&lt;p&gt;Этим вопросом озадачивался конечно не только я. На прошедшем ДжангоКоне тоже встал этот вопрос и он был помечен как &lt;a href="http://code.djangoproject.com/ticket/8946"&gt;надо подумать&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Также, совсем недавно Эндрю Годвин из джанговского сообщества &lt;a href="http://www.aeracode.org/2008/9/14/denormalisation-follies/"&gt;написал&lt;/a&gt; в своем блоге про некий &lt;code&gt;DenormField&lt;/code&gt;, который магическим образом кешировал в себе некий атрибут &lt;code&gt;ForeignKey&lt;/code&gt; объекта. Т.е. да, в некотором роде это денормализация, но уж больно вырожденная. Ведь это по сути замена &lt;code&gt;select_related&lt;/code&gt;. А если учесть что джоин по первичному ключу достаточно дешёвый, то смысла делать такую денормализации мало. Но тем не менее это первый шаг в сторону решения задачи. Вскорости автор данной реализации написал о ней в &lt;a href="http://groups.google.com/group/django-developers/browse_thread/thread/9a672d5bbbe67562"&gt;девелоперсах&lt;/a&gt; и получил поддержку со стороны участников. Но всем показалось, что решение достаточно локальное и узкоспециализированное. Да тут и казаться нечему - так и есть. Надо улучшать.&lt;/p&gt;

&lt;p&gt;Вот, вдохновившись этими событиями, одной бессонной ночью я набросал некий концепт &lt;em&gt;универсального денормализованного поля модели&lt;/em&gt;. Прикинув вначале интерфейс, подумав надо тем ка это можно реализовать, посоветовавшись с коллегами, понял, что из этого может получиться что-то толковое.&lt;/p&gt;

&lt;p&gt;Собрался и реализовал. И действительно получился универсальный инструмент, который может быть сравнительно легко кастомизирован под конкретные нужды.&lt;/p&gt;

&lt;h2&gt;Как?&lt;/h2&gt;

&lt;p&gt;Основным критерием реализации я выбрал - декларативность. Т.е. описание нужной функциональности должно быть максимально декларативно, но при этом позволять гибко управлять логикой, которая будет сосредоточена в одном месте, а не размазана по нескольким моделям.&lt;/p&gt;

&lt;p&gt;За базовую систему контроля за состояниями решил выбрать сигналы. Тем более у меня, как я уже понял, некая страсть &lt;a href="http://webnewage.org/post/2008/3/9/keshirovanie-invalidatsiya-signalami-trapeza/"&gt;скрещивать что-то с сигналами&lt;/a&gt; в декларативном стиле:) В результате у меня получился &lt;a href="http://svn.turbion.org/turbion/trunk/turbion/utils/composition.py"&gt;CompositionField&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Как обычно буду показывать суть идеи на примере, по ходу углубляюсь в подробное описание интересных моментов.&lt;/p&gt;

&lt;p&gt;Для начал возьмем исходный вариант с постом и комментариями и перепишем его с применением &lt;code&gt;CompositionField&lt;/code&gt;:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;comment_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CompositionField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_save&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_delete&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                        &lt;span class="n"&gt;sender_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;field_holder_getter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Получилось немного громоздко, да? Но что мне нравится, даже не смотря на объем, это декларативность, которой удалось добиться. Теперь давайте разберемся, что же здесь происходит.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CompostionField&lt;/code&gt; на самом деле не класс, а функция, которая возвращает специально обработанный объект поля в &lt;code&gt;native&lt;/code&gt;, принимаемый в качестве параметра. Как легко догадаться, &lt;code&gt;native&lt;/code&gt; это непосредственно поле, которое будет создано и куда будет записан некий композиционный результат. Дальше самое интересное - мы описаваем тригер, который по возникновению неких сигналов будет производить операцию и записывать значение в поле. &lt;code&gt;on&lt;/code&gt; список сигналов для отслеживания. В данном случае мы будет отслеживать сохранение и удаление объекта. Но в принципе сигналов может быть и больше, либо всего один. &lt;code&gt;do&lt;/code&gt; - как раз это та функция, которая будет вызвана при поступлении сигнала. Она то и производит операцию подсчета комментариев. Принимает три параметра, которые говорят сами за себя. &lt;code&gt;sender_model&lt;/code&gt; - модель которая должна отослать сигнал. Все логично, у нас это Comment. Ну и последний параметр - &lt;code&gt;field_holder_getter&lt;/code&gt; это функция, которая по объекту комментария должна возвращать соответствующий пост.&lt;/p&gt;

&lt;p&gt;Вот и всё. Весь тот функционал, который я описал в первом примере кода. К чему я и стремился.&lt;/p&gt;

&lt;p&gt;Да, ещё бесплатный бонус - мы получаем автоматически сгенерированный &lt;code&gt;update_comment_count&lt;/code&gt;, который может быть использован в крайнем случае.&lt;/p&gt;

&lt;p&gt;Ну как вам? Нравится? Но это ещё не всё. Система намного гибче чем я показал в этом примера. Давайте посмотрим ещё одну ситуацию.&lt;/p&gt;

&lt;p&gt;Допустим, что есть некоторое событие &lt;code&gt;Event&lt;/code&gt; и есть несколько пришедших на него - &lt;code&gt;Visit&lt;/code&gt;. И мы хотим денормализовать количество пришедших. В принципе по структуре ситуация идентична случаю с постом и комментариями, но на ней я покажу другие возможности &lt;code&gt;CompositionFIeld&lt;/code&gt;.&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Event&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;visit_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CompositionField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_save&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;commons&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;sender_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;app.Visit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;field_holder_getter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;update_method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sync_visit_count&amp;quot;&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Кода стало ещё больше, но он за-то покажет другие интересные моменты. Как вы заметили, если раньше был один тригер, то теперь их два - т.е. тригеров может быть несколько. Теперь каждый из них повешен на свой сигнал и считают они комментарии на базе уже имеющегося значения счетчика. Хочу отметить, что лучше так не делать, поскольку если вы работает в транзакции, то можете получить неожиданные результаты по завершению:) Но в качестве демонстарции, это не особо важно. Далее, поскольку два тригера похожи, то общие их свойства выделены в отдельный параметр &lt;code&gt;commons&lt;/code&gt;, который создан для упрощения объявления родственных тригеров. &lt;code&gt;commit=True&lt;/code&gt; - говорит о том, что объект после применения обработчика нужно сохранить. Кстати, тут это просто для наглядности, поскольку это поведение по-умолчанию.&lt;/p&gt;

&lt;p&gt;Ну и, наконец, параметр &lt;code&gt;update_method&lt;/code&gt;, о котором расскажу более подробно. Он позволяет кастомизировать генерацию стандартного метода обновления. Т.к. обновление счетчика у нас инкрементальное, то во-первых перед выполнением метода, счетчик надо обнулить(&lt;code&gt;initial=0&lt;/code&gt;). Также нужно знать какой тригер применять. В данном случае счетчик мы будем каждый раз увеличивать, то я выбрал первый тригер с индексом 0 (&lt;code&gt;do=0&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;queryset&lt;/code&gt; это очень интересный параметр. Поскольку тут выполнение действия идет не по сигналу, а по факту вызова метода, то для правильной работы имеющегося тригера, нужно указать множество объктов для его применения. В этом случае множество посетителей заданного события. Ну и &lt;code&gt;name&lt;/code&gt; это переопределение стандартно сгенерированного &lt;code&gt;update_FOO&lt;/code&gt; имени метода на какое-то своё.&lt;/p&gt;

&lt;p&gt;Классно, да? Но и это не всё. Рассмотрим ещё одну ситуацию, похожую на ту которую предложил автор &lt;code&gt;DenormField&lt;/code&gt;. Есть кинофильм и есть его режиссер. Режиссер может быть автором нескольких фильмов, поэтому модель такая:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;director&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CompositionField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
             &lt;span class="n"&gt;sender_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;field_holder_getter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;movie_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
             &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;, by &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Тут уже меньше кода. Нового тут объяснять особо нечего. Это простой пример другой ситуации. Подсчет некого хедлайна получился достаточно простой.&lt;/p&gt;

&lt;p&gt;Ну вот пожалуй пока всё.&lt;/p&gt;

&lt;h2&gt;Что дальше?&lt;/h2&gt;

&lt;p&gt;Поскольку пока эта тема будоражит мой мозг, то возможно что-то будет меняться и дополняться в ближайшее время:) И не раз напишу об этом наверно. Но суть и принцип я думаю уже не изменятся. Как я понял, такой механизм покрывает все встречавшиеся мне примеры денормализации в реальных проектах. Поэтому уже совсем скоро буду это дело применять в рабочем коде и получать реальный фидбек от использования.&lt;/p&gt;

&lt;p&gt;Так же, уже сейчас понятно, что декларативную часть можно ещё уменьшать в пользу дополнительной интроспекции. Конечно прозрачности это не добавит, но за удобство использования придется платить обилием магии.&lt;/p&gt;

&lt;p&gt;Путь упрощения - написание более высокоуровневых шорткатов, которые покрывали бы самые распространенные варианты использования, т.е. что-то типа &lt;code&gt;CountField&lt;/code&gt;, &lt;code&gt;RelatedAttrField&lt;/code&gt; и т.п. Но это уже другая история.&lt;/p&gt;

&lt;p&gt;Сейчас я очень доволен тем что получилось. А получился именно достаточно общий и универсальный инструмент для денормализации некоторых наборов данных. А то что он завязан на сигналы дает огромнейший простор для творчества.&lt;/p&gt;

&lt;p&gt;Вот, теперь я замолкаю и даю вам возможность ещё раз посмотреть код, может быть залезть в реализацию и понять как бы вы могли применять подобное решение. Предлагайте какие-то новые ситуации, будем пробовать положить их на такую концепцию.&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/caching/"&gt;caching&lt;/a&gt;

	&lt;a href="/tag/db/"&gt;db&lt;/a&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/orm/"&gt;orm&lt;/a&gt;

	&lt;a href="/tag/python/"&gt;python&lt;/a&gt;

	&lt;a href="/tag/signals/"&gt;signals&lt;/a&gt;

	&lt;a href="/tag/snippets/"&gt;snippets&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

	&lt;a href="/tag/denormalizatsiya/"&gt;денормализация&lt;/a&gt;

</summary></entry><entry><title>Слишком много дров точка ком</title><link href="http://webnewage.org/post/2008/9/23/slishkom-mnogo-drov-tochka-kom/" rel="alternate" /><updated>2008-09-23T07:17:07Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/9/23/slishkom-mnogo-drov-tochka-kom/</id><summary type="html">
&lt;p&gt;За проектом &lt;a href="http://stackoverflow.com/"&gt;StackOverflow&lt;/a&gt; я наблюдал уже достаточно давно. Слушал подкасты о процессе разработки и ждал когда же увидит свет этот новый сервис. Гений его авторов позволил сделать грамотный ресурс для поиска ответов на разные программистские вопросы.&lt;/p&gt;

&lt;p&gt;Для себя я сразу отметил два тега за которыми можно последить - &lt;a href="http://stackoverflow.com/questions/tagged/python"&gt;python&lt;/a&gt; и &lt;a href="http://stackoverflow.com/questions/tagged/django"&gt;django&lt;/a&gt;. Там среди кучи достаточно банальных вопросов попадаются и те, что из разряда - "я бы тоже не прочь почитать ответы". И пока, даже сообщество не до конца сформировалось, всё равно есть что почитать.&lt;/p&gt;

&lt;p&gt;Так что всем рекомендую. Тем более, иной раз можно и самим кое-где свою точку зрения выразить. Я надеюсь что социальная часть сервиса себя не дискредитирует и получится действительно интересное сообщество с грамотными участниками.&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/python/"&gt;python&lt;/a&gt;

	&lt;a href="/tag/snippets/"&gt;snippets&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

	&lt;a href="/tag/ssyilki/"&gt;ссылки&lt;/a&gt;

</summary></entry><entry><title>Рождение Django 1.0</title><link href="http://webnewage.org/post/2008/9/4/rozhdenie-django-10/" rel="alternate" /><updated>2008-09-04T08:32:06Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/9/4/rozhdenie-django-10/</id><summary type="html">
&lt;p&gt;Ну вот. Даже как-то грустно. То чего все мы так долго ждали наконец &lt;a href="http://www.djangoproject.com/weblog/2008/sep/03/1/"&gt;свершилось&lt;/a&gt;. Джанга родилась в своем релизном варианте с гордым "1.0" на ярлыке.&lt;/p&gt;

&lt;p&gt;Почему грустно? Да потому, что тот темп, с которым менялась джанга последний год(с лишним), приучил нас быть всегда в тонусе, следить за изменениями, подстраиваться под них и как-то менять своё видение этого чудесного фреймворка. А что теперь? Мне кажется что темп упадет, а значит и драйв обитания на &lt;code&gt;trunk&lt;/code&gt; будет не такой как раньше.&lt;/p&gt;

&lt;p&gt;Ну ладно с ним с драйвом, будем искать в других областях. Важно то что наконец джанга должна приобрести общепризнанный статус &lt;em&gt;production/enterprise-ready&lt;/em&gt; и пойти в массы с ещё более быстрыми темпами. Компании (конечно в большей степени управленцы в них), которые раньше на этапе выбора платформы и инструмента воротили нос от продукта, который был, как им казалось, не готов для боевого применения, сейчас начнут пересматривать свои воззрения, и уже для новых проектов боле пристально смотреть на джагу как на реального кандидата в освоении и применении. Я на это надеюсь по крайней мере. Пусть для многих смелых и разумных, уже давно джанга вполне была готовой к бою, но всё равно оставались сомневающиеся.&lt;/p&gt;

&lt;p&gt;Так же теперь в споре на лидерство на рынке веб-фреймворков для быстрой разработки приложений у джанго есть новый веский довод.&lt;/p&gt;

&lt;p&gt;Кстати, то что была организована &lt;a href="http://www.djangoproject.com/foundation/"&gt;Django Software Foundation&lt;/a&gt;, тоже придает солидности и добавляет козырей в колоду нашего любимого фреймворка.&lt;/p&gt;

&lt;p&gt;Ожидаю всплеск интереса как к самой джанги, так и к сообществу в целом, как это было например после релиза &lt;a href="http://webnewage.org/post/2008/4/12/vse-bez-uma-ot-gae/"&gt;GAE&lt;/a&gt;. А это поможет нам, разработчикам и отчасти евангелистам, быть более на виду и на нас станут обращать больше внимания. А кому этого не хочется?:)&lt;/p&gt;

&lt;p&gt;Главное, чтобы после вселенской гулянки, корные разработчики продолжали привносить новые идеи и концепции для улучшения существующего положения вещей внутри фреймворка. А мы в свою очередь будем продолжать их осваивать и использовать в своих разработках, пусть с меньшей скоростью чем раньше. Но может быть это и не плохо? А может вообще всё будет по другому? Посмотрим.&lt;/p&gt;

&lt;p&gt;Огромный шаг в завоевании умов и сердец веб-разработчиков сделан, кто раньше ещё размышлял "быть или не быть", теперь с большей долей вероятности скажут - "быть"!&lt;/p&gt;

&lt;p&gt;Что же, живем и работаем дальше, но уже с чуть другим чувством. Да, и приходим обязательно на &lt;a href="http://kuda.yandex.ru/events/96069/"&gt;локальную гулянку&lt;/a&gt; в честь этого события!&lt;/p&gt;

&lt;p&gt;PS: Интересно, а я один был озабочен вопросом - кто быстрее: джанга или гугл хром:)&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/python/"&gt;python&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

</summary></entry><entry><title>Горизонтально, вертикально и вперемешку</title><link href="http://webnewage.org/post/2008/8/19/gorizontalno-vertikalno-i-vperemeshku/" rel="alternate" /><updated>2008-08-19T14:20:16Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/8/19/gorizontalno-vertikalno-i-vperemeshku/</id><summary type="html">
&lt;p&gt;Недавно очень ясно осознал на собственном опыте, насколько же разными могут быть &lt;a href="http://books.google.ru/books?id=hjfSPKirS3cC&amp;amp;pg=PA127&amp;amp;cad=1&amp;amp;sig=ACfU3U3bvm420QJQCYQv_33VRIjkT0xf_A"&gt;структурные модели XML&lt;/a&gt;. Разные они и по восприятию, и удобству обработки, и простоте создания. Но о вопросах генерации я могу только догадываться и предполагать, поскольку особо этим не занимался никогда, то про обработку могу действительно поделиться некоторыми мыслями, которые меня посетили после продолжительного периода "ковыряния" различных XML структур данных.&lt;/p&gt;

&lt;p&gt;Горизонтальная модель показалась мне более читаемой. Т.е. она более похожа на табличные выборки из баз данных. И как не странно, её проще парсить и обрабатывать. Ставка на атрибуты, идентификаторы которых уникальны в приделах одного элемента, себя оправдывает и не порождает излишеств.&lt;/p&gt;

&lt;p&gt;Вертикальная модель подразумевает активное использование дочерних элементов. Из-за этого документ получается более "толстым". Причем зачастую. использование дочерних элементов не очень оправдано. Они не используются как контейнеры для других элементов и даже &lt;code&gt;CDATA&lt;/code&gt; там не хранится. А читать труднее. Хотя и появляется некая древовидная структура, но достаточно вырожденная и почти плоская. Ну и много "паразитной" разметки получается как следствие.&lt;/p&gt;

&lt;p&gt;На практике выходит, что придерживаться только одного стиля не получает у схема-составителей. Обычно приходится иметь дело с гибридной моделью. Где вполне себе подчиненные логике мета-данные вынесены в атрибуты, другие же почему-то в дочерние элементы. Иногда кажется, что просто для разнообразия. Кому-то режет глаз, если элемент это длинная строчка с неким набором атрибутов и для гармонии обязательно нужно чтобы что-то было внутри самого элемента. Надо признать, что удобства в обработке и чтении это тоже не добавляет.&lt;/p&gt;

&lt;p&gt;Кстати, достаточно просто можно определить насколько XML модель соответствует внутренней модели данных в базе и насколько данные трансформировались и адаптировались для XML'ного представления. Когда вы работаете со структурой, которая полностью соответствует классической нормализованной схеме базы данных, то сразу понимаете, что XML является практически "снимком" данных этой базы. Да, генерация заметно упрощается, но это ставит крест на читаемости и удобстве обработки принимающей стороны. Разгребать ворохи "ссылок" и странных идентификаторов - каторга.&lt;/p&gt;

&lt;p&gt;Под чтением я понимаю, именно чтение человеком, т.е. мной. Я приверженец того мнения, что XML не для чтения и написания самим человеком, тем более, когда многие об удобстве забывают и генерируют ужасные схемы. Это машинный формат и то что он текстовый это всего лишь деталь реализации и одна из особенностей, а не параметр, который сразу записывает его в легко-человеком-воспринимаемые форматы. Воспринимать вертикальный и сильно "нормализованный" документ сложно. Конечно можно привыкнуть, но не стоит.  По крайней мере один раз прочесть документ надо - для того чтобы написать конвертер в свои бизнес объекты. А потом забыть и доверить генерацию/потребление на откуп программе. Отсюда вывод: не надо делать, например, конфигурационные файлы на база XML и уже тем более не стоит делать на его основе мало-мальски сложные DSL. Пусть на XML'е разговаривают программы. Но это мнение не все, увы, разделяют. Ant тому пример - популярен бешено, хоть в определенной среде, где XML культ.&lt;/p&gt;

&lt;p&gt;XML вызывает у меня радугу эмоций. Это не плохо на самом то деле...&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/db/"&gt;db&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

	&lt;a href="/tag/xml/"&gt;xml&lt;/a&gt;

</summary></entry><entry><title>А вы поймали новые сигналы?</title><link href="http://webnewage.org/post/2008/8/7/a-vyi-pojmali-novyie-signalyi/" rel="alternate" /><updated>2008-08-07T15:30:49Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/8/7/a-vyi-pojmali-novyie-signalyi/</id><summary type="html">
&lt;p&gt;Вчера джанга сделал ещё один существенный шаг на пути к долгожданному релизу 1.0. На этот раз посчастливилось обновиться &lt;a href="http://code.djangoproject.com/changeset/8223"&gt;инфраструктуре сигналов&lt;/a&gt;.
Основное и главное отличие новой подсистемы сигналов - это почти двукратное увеличение производительности.&lt;/p&gt;

&lt;p&gt;Исторически сигналы в джанге были сделаны на базе пакета &lt;code&gt;pyDispatcher&lt;/code&gt;. который почти без изменений был скопирован в &lt;code&gt;django.dispatch&lt;/code&gt;. Со временем выяснилось, что он избыточен и медленен.&lt;/p&gt;

&lt;p&gt;Сигналы практически везде пронизывают код джанги. Повесить обработчик можно и на изменение объекта, и на начало и конец обработчик запроса и рендеринг шаблона и много чего ещё. Причем они не только предоставляют некий интерфейс взаимодействия с внешним миром, но и активно применяются внутри самой джанги. Например, удаление загруженных файлов вслед за объектами и закрытие соединения с базой при завершении обработки запроса(!).&lt;/p&gt;

&lt;p&gt;Понятно, что производительность всей подсистемы сигналов очень важна и напрямую влияет на скорость отклика приложения. Хотя многие разработчики и не особо их используют в своём коде, а некоторые даже говорят, что они не нужны, всё равно они играют значимую роль в общей производительности. А если ещё вы их активно применяете, как это делаю я для умного кеширования, то вопрос улучшения работы сигналов касается нас очень сильно.&lt;/p&gt;

&lt;p&gt;Возможности погонять тесты у меня нет, так что поверим разработчикам на слово. Лучше копнем поглубже в суть.&lt;/p&gt;

&lt;p&gt;Конечно, такие большие перемены сказались и на внешнем интерфейсе подсистемы сигналов, поэтому данные изменения обратно не совместимы.&lt;/p&gt;

&lt;p&gt;Для того чтобы понять, что нам надо будет поменять в имеющимся коде, для начала разберемся как же надо пользоваться сигналами сейчас.&lt;/p&gt;

&lt;p&gt;Первым делом объявим сигнал:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;

&lt;span class="n"&gt;dinner_is_served&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Первое что бросается в глаза, это то что сами сигналы теперь полноценные классы. С одной стороны это более логично, но с другой красивые и элегантные решения, вроде использования в качестве идентификаторов сигналов строковые литералы, уже не пройдут. Сигнал это полноценная сущность которую надо предварительно объявить где-то.&lt;/p&gt;

&lt;p&gt;Как мы знаем, сигналы обычно обозначают наступление какого-либо события. События происходят в каком-то окружении, а следовательно вместе с сигналами распространяется некая мета информация, описывающая как раз контекст его возникновения. Теперь у разработчика есть явная возможность помимо стандартного &lt;code&gt;sender&lt;/code&gt; описать ещё дополнительные параметры, с которыми сигнал должен посылаться. Выглядит это так:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;dinner_is_served&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;providing_args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;courses&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;dessert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Т.е. по логике при посылке данного сигналы должна быть указано подмножество этих аргументов. Но это нигде не проверяется и получается что это всего лишь такой вариант самодокументирования:) Допилят позже надо полагать.&lt;/p&gt;

&lt;p&gt;Ну всё, сигнал объявили и надо его послать всем желающим:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;dinner_is_served&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Barrymore&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;soup&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;oatmeal porridge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dessert&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pudding&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Добавить то желающих тоже просто:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sherlock_holmes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dessert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;dinner_is_served&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sherlock_holmes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как легко заметить, все операции по присоединению хендлеров и посылки происходят с самим объектом событий. Но для некоторой обратной совместимости, сохранились и старые методы через диспетчер &lt;code&gt;dispatcher.send&lt;/code&gt; и &lt;code&gt;dispatcher.connect&lt;/code&gt;, но  их уже не рекомендуется использовать.&lt;/p&gt;

&lt;p&gt;Итак, подведем итог. Для того чтобы максимально просто и безболезненно заставить работать старый код после очередного &lt;code&gt;svn up&lt;/code&gt; джанги, надо явным образом создавать объект сигнала. Всё остальное должно работать. Если конечно вы не использовали какие-то более изощренные способы работы с сигналами.&lt;/p&gt;

&lt;p&gt;Избавиться совсем от наследия &lt;code&gt;pyDispatcher&lt;/code&gt; не получилось. Был оставлен модуль &lt;code&gt;saferef.py&lt;/code&gt; в котором реализована "слабая ссылка"(weak reference) и по умолчанию обработчики, присоединяемые к сигналу, таковыми являются. Но это поведение можно изменить, добавив при присоединении хендлера параметр &lt;code&gt;weak=False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Кстати, косвенно с этим связанна ещё одна интересная особенность подсистемы сигналов.&lt;/p&gt;

&lt;p&gt;Представим, что у нас есть код:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="k"&gt;pass&lt;/span&gt;

   &lt;span class="n"&gt;some_signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И потом вы эту &lt;code&gt;some_func&lt;/code&gt; зовете несколько раз. И когда сигнал сработает, что должно произойти? Правильно. Ничего. Хендлер не сработает потому что из-за "слабой" ссылки GC его убьет, а то что мы его зарегистрировали его не волнует. Поэтому добавляем &lt;code&gt;weak=False&lt;/code&gt;:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;some_signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weak&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А что теперь будет? Ага, обработчик сработает несколько раз, точнее столько раз сколько мы вызвали функцию &lt;code&gt;some_func&lt;/code&gt; в рамках жизни данного процесса. Для того чтобы избежать повторного добавления обработчика, есть очень полезный параметр &lt;code&gt;dispatch_uid&lt;/code&gt;, который, если присутствует, должен быть уникальным идентификатором данного хендлера - строка или что угодно &lt;code&gt;hashable&lt;/code&gt;. Вот так например:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;some_signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weak&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch_uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my_useful_unique_handler&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Занятно то, что такую же проблему я решал когда делал &lt;a href="http://webnewage.org/post/2008/3/9/keshirovanie-invalidatsiya-signalami-trapeza/"&gt;инвалидацию кеша сигналами&lt;/a&gt;. И тоже "подписывал" хендлер уникальным идентификатором и на основе него не давал регистрироваться дубликатам. Теперь всё уже сделано за меня, что очень радует.&lt;/p&gt;

&lt;p&gt;Шаг очередной джанга сделала, мы его обсудили и ждем следующего. А это "file storage" между прочим - тоже веха!&lt;/p&gt;

&lt;h2&gt;Обновление:&lt;/h2&gt;

&lt;p&gt;Произошло одно маленькое изменение - хендлеры сигналов должны обязательно принимать &lt;code&gt;**kwargs&lt;/code&gt;, а то получите эксепшен при попытке его присоединить&lt;/p&gt;

&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/python/"&gt;python&lt;/a&gt;

	&lt;a href="/tag/signals/"&gt;signals&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

</summary></entry><entry><title>Спринтеры в Яндексе</title><link href="http://webnewage.org/post/2008/7/22/sprinteryi-v-yandekse/" rel="alternate" /><updated>2008-07-22T20:11:21Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/7/22/sprinteryi-v-yandekse/</id><summary type="html">
&lt;p&gt;Нет, конечно не Майкл Джонсон и Ко, а мы - отчаянные джангисты. Прошло уже больше недели, а я вот только сейчас могу рассказать, а главное даже чуть-чуть показать как это было.
&lt;/p&gt;&lt;p&gt;
Технические итоги спринта подвел очень лаконично &lt;a href="http://softwaremaniacs.org/blog/2008/07/13/sprint-finished/"&gt;Ваня Сагалаев&lt;/a&gt;, я же больше расскажу чуть про процесс. 
&lt;/p&gt;&lt;p&gt;
И так, в субботу 12 июля в одном из московских офисов Яндекса, собрались люди чтобы подправить джангу. Собирались долго, поскольку для многих оказалось неожиданностью, что привычные шатлы от метро по выходным не ходят и надо добираться пешком. Сюрпризом это стало и для некоторых сотрудников самого Яндекса, к слову, которых набралось целых 3 человека.
&lt;/p&gt;&lt;p&gt;
Принимал нас Яндекс в большой столовой, с заранее приготовленными для нас вкусностями.
&lt;/p&gt;&lt;p&gt;
Когда уже почти все собрались и немного познакомились, Ваня взял вступительное слово, рассказал немного про процесс спринтования и все неспеша полезли в джанговский трак чтобы захватить себе тикет получше. Те кто просмотрел их список заранее, сразу выбрали что-то себе. Откровенно говоря, интересных задачек было мало. Они либо слишком нудные, либо какие-то очень заумные и специфичные.
&lt;/p&gt;&lt;p&gt;
Кстати, была группа, которая занималась не доводкой NFA, а реализацией улучшенного бекэнда для Oracle, как потом оказалось, из очень серьезной конторы:) Но об этом попозже.
&lt;/p&gt;&lt;p&gt;
Так, я оказался в числе тех самых, подготовленных, и выбрал очень простой себе тикет,  т.к. на что-то более серьезное не хватило бы времени. Суть его заключалась в том, чтобы избавится от ненужных проверок на "залогиненность" пользователя. Я быстро набросал патч &lt;a href="http://code.djangoproject.com/ticket/6991"&gt;#6991&lt;/a&gt;, который потом всё равно Ване пришлось подправлять, залил его в трак и уж было собрался уходит на дерби. Как вспомнил, что у меня есть телефон-фотоаппарат. И устроил маленькую фотоохоту на спринтеров, которые были погружены в процесс. Вот что получилось:
&lt;/p&gt;
&lt;br/&gt;
&lt;div style="text-align:center;"&gt;
&lt;p&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_1.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_1_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="/media/upload/turbion/assets/yandex_django-sprint_2.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_2_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_3.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_3_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_4.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_4_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_5.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_5_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_6.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_6_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_7.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_7_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_8.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_8_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_10.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_10_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_11.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_11_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;a href="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_9.jpg"&gt;&lt;img src="http://webnewage.org/media/upload/turbion/assets/yandex_django-sprint_9_thumb.jpg" border="0"/&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;br/&gt;
&lt;p&gt;
Было немного грустно, что все вот так сидят и мало кто представляет, откуда кто приехал и чем занимается. И тут я вспомнил что у меня телефон ещё и видеокамера!:) Быстро сообразив как это использовать, я попросил всех немного рассказать и себе и о своем занятии. Получился такой не плохой ролик с представлением участников:
&lt;/p&gt;
&lt;br/&gt;
&lt;p style="text-align:center;"&gt;
&lt;object width="400" height="533"&gt;	&lt;param name="allowfullscreen" value="true" /&gt;	&lt;param name="allowscriptaccess" value="always" /&gt;	&lt;param name="movie" value="http://www.vimeo.com/moogaloop.swf?clip_id=1330617&amp;amp;server=www.vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" /&gt;	&lt;embed src="http://www.vimeo.com/moogaloop.swf?clip_id=1330617&amp;amp;server=www.vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="400" height="533"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;a href="http://www.vimeo.com/1330617?pg=embed&amp;sec=1330617"&gt;Django-sprint at Yandex 12 July 2008&lt;/a&gt; from &lt;a href="http://www.vimeo.com/user600132?pg=embed&amp;sec=1330617"&gt;Alex Koshelev&lt;/a&gt; on &lt;a href="http://vimeo.com?pg=embed&amp;sec=1330617"&gt;Vimeo&lt;/a&gt;.
&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;
Потом я быстро собрал свой ноутбук и отправился в дорогу. Но это уже совсем другая история. И в противовес этой - очень грустная.
&lt;/p&gt;&lt;p&gt;
В итоге, я был очень доволен что всё таки приехал на спринт и познакомился с интересными людьми. Было немного жаль, что не досидел до конца, потому что там было много интересного и пицца:)
&lt;/p&gt;&lt;p&gt;
Хочу ещё!
&lt;/p&gt;
&lt;hr/&gt;&lt;br/&gt;
Теги:&lt;br/&gt;

	&lt;a href="/tag/django/"&gt;django&lt;/a&gt;

	&lt;a href="/tag/opensource/"&gt;opensource&lt;/a&gt;

	&lt;a href="/tag/web/"&gt;web&lt;/a&gt;

	&lt;a href="/tag/lichnoe/"&gt;личное&lt;/a&gt;

	&lt;a href="/tag/yandeks/"&gt;яндекс&lt;/a&gt;

</summary></entry><entry><title>У админки новые формы </title><link href="http://webnewage.org/post/2008/7/19/u-adminki-novyie-formyi-/" rel="alternate" /><updated>2008-07-19T07:00:30Z</updated><author><name>Александр Кошелев</name></author><id>http://webnewage.org/post/2008/7/19/u-adminki-novyie-formyi-/</id><summary type="html">
&lt;p&gt;Опять говорю "Ура!". В предыдущий раз это это было в связи с апгрейдом ORM. На это раз кардинальным улучшениям подверглась ещё одна очень важная часть Django - автоматический CRUD, она же в официальной терминологии - админка. После достаточно продолжительного этапа разработки и тестирования NFA бранч влился в транк и подарил пользователям джанги кучу новых ощущений: от радости за сей факт, до тревоги по поводу необходимости переписывать имеющийся код. Но нас, любителей самых "свежих срезов", это никогда не останавливало.:)&lt;/p&gt;

&lt;p&gt;Многие уже успели изменить транку с NFA брачем и некоторое время погонять новую админку в своих проектах. Но не все такие смелые:)&lt;/p&gt;

&lt;p&gt;Вообще это очень интересный процесс когда ты видишь по увеличившемуся числу changeset'ов перед релизом чего-то нового, что вот оно скоро свершится. Так было с Малкольмом, когда он допиливал рефакторинг ORM и произошло сейчас, благо спринт организовали. На этот раз главная звезда Брайен Роснер. Эти приятные хлопоты по полировке деталей, правки уже кажущихся не очень существенными сообщениями об ошибках, именования переменных и функций, и т.п. - как часть неотъемлемая часть.&lt;/p&gt;

&lt;p&gt;Так что же мы получили? Во первых случился важнейший переворот в принципе описания админки. Если раньше её описание было частью определения модели, то теперь оно происходит отдельно. Что, наконец, дает возможность кастомизировать админку для моделей сторонних приложений без всяких хаков и редактирования чужого кода.&lt;/p&gt;

&lt;p&gt;Помимо этого, возможности для кастомизации админки как таковой значительно расширились. Теперь более тонко можно настраивать её отображение и функционал.&lt;/p&gt;

&lt;p&gt;И конечно же, сам факт появления адмника на новых формах в транке, приближает нас к долгожданному релизу версии 1.0 джанги. Который, если всё сложится как надо случиться уже в начале осени.&lt;/p&gt;

&lt;p&gt;А теперь пройдемся по нововведениям и попытаемся сообразить, где нам это пригодится.&lt;/p&gt;

&lt;h2&gt;Настраиваем админку по новому&lt;/h2&gt;

&lt;p&gt;Для начала придумаем вот такую простую модель:&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;250&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Далее создадим класс описания админки для нашей модели. Кстати, нам рекомендуется, код админки хранить в файле &lt;code&gt;admin.py&lt;/code&gt;, который автоматически импортируется в нужный момент.&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В последней строке происходит регистрация данного описания админки и модели. Но это мы сделали дефолтную админку, теперь мы её будем кастомизировать.&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;fieldsets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="s"&gt;&amp;#39;fields&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;}),&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Meta&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="s"&gt;&amp;#39;classes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;collapse&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
                 &lt;span class="s"&gt;&amp;#39;fields&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;author&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;}),&lt;/span&gt;
         &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видно, изменилось название атрибута отвечающего за группировку полей при отображении формы редактирования. Раньше это были &lt;code&gt;fields&lt;/code&gt;, теперь же это более логичные &lt;code&gt;fieldsets&lt;/code&gt;. В остальном формат описания не изменился.&lt;/p&gt;

&lt;p&gt;Ещё не плохо было бы избавиться от селект бокса, который отвечает а выбор автора. Благо теперь есть возможность превратить его в радио селект.&lt;/p&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;code&gt;&lt;span class="n"&gt;radio_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;author&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HORIZONTAL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Аналогично можно сделать его и &lt;code&gt;VERTICAL&lt;/code&gt;. Такую кастомизацию можно делать либо для &lt;code&gt;ForeignKey&lt;/code&gt; полей, либо для тех у которых есть &lt;code&gt;choices&lt;/code&gt;.&lt;/p