<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7061791285623738411</id><updated>2012-02-16T17:22:09.327+04:00</updated><category term='install'/><category term='1.2'/><category term='weblog'/><category term='TextField'/><category term='list clustering'/><category term='en'/><category term='raw sql'/><category term='decorator'/><category term='sphinx'/><category term='книга'/><category term='декоратор'/><category term='tortoise'/><category term='deseb'/><category term='тестирование'/><category term='dvd'/><category term='CharField'/><category term='Jacob Kaplan-Moss'/><category term='unit test'/><category term='python'/><category term='web 2.0'/><category term='nginx'/><category term='покрытие кода'/><category term='блог'/><category term='замыкание'/><category term='EmailField'/><category term='list clusterization'/><category term='webui'/><category term='social network'/><category term='deploy'/><category term='админка'/><category term='введение'/><category term='coverage'/><category term='mysql'/><category term='DateFields'/><category term='ForeignKey'/><category term='django advent'/><category term='models'/><category term='django'/><category term='growisofs'/><category term='post'/><category term='book'/><category term='blog'/><category term='links'/><category term='blogengine'/><category term='cell'/><category term='user'/><category term='alex gaynor'/><category term='crud'/><category term='newforms-admin'/><category term='request rate limit'/><category term='closure'/><category term='orm'/><category term='ru'/><category term='messages'/><category term='burn'/><category term='model'/><category term='модульное тестирование'/><category term='evolvedb'/><category term='zip'/><category term='subversion'/><category term='ссылки'/><title type='text'>Изучаем Django</title><subtitle type='html'>Очерки о разработке</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-1899645955669862134</id><published>2010-06-07T00:57:00.008+04:00</published><updated>2010-06-08T10:58:56.055+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='raw sql'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='orm'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><category scheme='http://www.blogger.com/atom/ns#' term='django advent'/><title type='text'>Smoothing The Curve</title><content type='html'>&lt;style&gt;
.article header {
 font-family:LeagueGothicRegular,Impact,sans-serif;
 font-weight:normal;
 height:100px;
 margin-bottom:20px;
 position:relative;
 text-transform:uppercase;
 display: block;
}
.article header h1 {
 color:#586C78;
 font-size:42px;
 font-weight:normal;
 height:60px;
 left:0;
 letter-spacing:1px;
 line-height:60px;
 position:absolute;
 top:10px;
}
.article header h2 {
 color:#CCCCCC;
 font-size:28px;
 font-weight:normal;
 height:40px;
 left:0;
 letter-spacing:1px;
 line-height:40px;
 position:absolute;
 top:0;
}
.article header h2 a {
 border:medium none;
 color:#ABB9C1;
 padding:0;
 text-decoration:none;
}
.article header h2 sup {
 font-size:0.7em;
 line-height:1em;
 text-decoration:underline;
 vertical-align:6px;
}
.article p, .article ul, .article ol, .article dl {
 font-size:18px;
 line-height:1.75em;
 margin-bottom:1em;
 font-family:Palatino,Georgia,"Times New Roman",Times,serif;
} 
.article .content {
 width: 100% !important;
}
.article .editor {
 font-size: 80%;
 margin-top: 0;
 padding: 0 0 0 0;
 line-height: 100%;
 display: block;
 font-style: italic;
}
.article .footer p {
 font-size: 14px;
 line-height: 1.75em;
}
code {
 font-size: 13px;
}
&lt;/style&gt;

&lt;div class="article"&gt;
&lt;header&gt;
  &lt;hgroup&gt;
   &lt;h1&gt;Smoothing The Curve&lt;/h1&gt;
   &lt;h2&gt;&lt;a href="http://djangoadvent.com/authors/sean-oconnor/"&gt;Sean O'Connor&lt;/a&gt; February 11&lt;sup&gt;th&lt;/sup&gt; 2010&lt;/h2&gt;
  &lt;/hgroup&gt;
&lt;/header&gt;

&lt;div class="content"&gt;
    &lt;p&gt;Практически для всех возможностей Django существуют способы модификации и&amp;nbsp;расширения. Крайне редко может понадобиться переписывать существенную часть функционала Django только для того, чтобы изменить действие &lt;nobr&gt;какого-то&lt;/nobr&gt; инструмента. Например, если вы&amp;nbsp;хотите изменить внешний вид формы, вы&amp;nbsp;можете отказаться от&amp;nbsp;внешнего вида &amp;laquo;по&amp;nbsp;умолчанию&amp;raquo; и&amp;nbsp;создать собственное поле, или даже просто использовать собственный &lt;acronym title="HyperText Markup Language" lang="en"&gt;HTML&lt;/acronym&gt;. В&amp;nbsp;обоих случаях вы&amp;nbsp;выигрываете по&amp;nbsp;спектру возможностей, сохраняя все остальные преимущества библиотеки форм.&lt;/p&gt;
    &lt;p&gt;Это расширение возможностей можно представить в&amp;nbsp;виде кривой. На&amp;nbsp;одном конце базовый вариант, который прост в&amp;nbsp;написании, но&amp;nbsp;ограничивает возможности контроля. На&amp;nbsp;противоположном конце кривой будет специально созданный класс для форм со&amp;nbsp;статическим у&amp;nbsp;&lt;nobr&gt;HTML-кодом&lt;/nobr&gt;, который обеспечивает больший уровень управления, но&amp;nbsp;который и&amp;nbsp;более сложен в&amp;nbsp;создании. В&amp;nbsp;примере с&amp;nbsp;созданием форм эта кривая будет довольно гладкая, поскольку для всего спектра функций библиотеки форм существуют возможности постепенного замещения базовых опций на&amp;nbsp;собственные пользовательские расширения.&lt;/p&gt;
    &lt;p&gt;До&amp;nbsp;выхода в&amp;nbsp;свет версии Django 1.2 для ORM такая кривая имела аналогичный вид, за&amp;nbsp;одним исключением: существенный скачок в&amp;nbsp;конце. Этот скачок появлялся в&amp;nbsp;связи с&amp;nbsp;тем, что при необходимости создания нестандартного &lt;nobr&gt;SQL-запроса&lt;/nobr&gt; требовалось выйти за&amp;nbsp;пределы ORM. Чтобы использовать определённый функционал ORM, пользователю пришлось&amp;nbsp;бы создавать его заново самостоятельно, хоть это и&amp;nbsp;не&amp;nbsp;является само по&amp;nbsp;себе катастрофой. В&amp;nbsp;версии Django 1.2 добавлен метод &lt;code&gt;Model.objects.raw ()&lt;/code&gt;, который решает эту проблему и, таким образом, сглаживает эту кривую для ORM.&lt;/p&gt;
    &lt;h1&gt;Старый способ&lt;/h1&gt;
    &lt;p&gt;До&amp;nbsp;выхода версии Django 1.2 при необходимости создать SQL запрос, нужно было написать нечто подобное:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from django.db import connection
from library.models import Author 

cursor = connection.cursor()
query = "SELECT * FROM library_author"
cursor.execute(query)
results = cursor.fetchall() 

authors = []
for result in results:
    author = Author(*result)
    authors.append(author)&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Не&amp;nbsp;то&amp;nbsp;чтобы это было совсем ужасно, но&amp;nbsp;здесь мы&amp;nbsp;теряем доступ к&amp;nbsp;функционалу ORM во&amp;nbsp;всём, что не&amp;nbsp;касается создания SQL запросов. В&amp;nbsp;частности, недоступна автоматическая трансформация результатов запроса в&amp;nbsp;экземпляр модели. В&amp;nbsp;меру трудоёмкие способы восстановить утраченную функциональность, конечно, существуют, но&amp;nbsp;фактически это будет изобретением велосипеда.&lt;/p&gt;
    &lt;h1&gt;Новый способ&lt;/h1&gt;
    &lt;p&gt;В&amp;nbsp;версии Django 1.2 для создания прямого SQL запроса нужно написать следующее:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from library.models import Author 

query = "SELECT * FROM library_author"
authors = Author.objects.raw(query)&lt;/code&gt;&lt;/pre&gt;

    &lt;p&gt;&lt;code&gt;authors&lt;/code&gt; здесь будет экземпляром &lt;code&gt;RawQuerySet&lt;/code&gt;. &lt;code&gt;RawQuerySet&lt;/code&gt; во&amp;nbsp;многом похож на&amp;nbsp;&lt;code&gt;QuerySet&lt;/code&gt;. В&amp;nbsp;частности, сходство в&amp;nbsp;том, что это&amp;nbsp;&amp;mdash; итерируемый объект, который возвращает экземпляр модели из&amp;nbsp;результатов запроса с&amp;nbsp;каждой итерацией. Отличие его от&amp;nbsp;&lt;code&gt;QuerySet&lt;/code&gt; состоит в&amp;nbsp;том, что его нельзя встроить в&amp;nbsp;цепочку. Но&amp;nbsp;здесь это и&amp;nbsp;не&amp;nbsp;важно, поскольку запросы больше не&amp;nbsp;формируются автоматически.&lt;/p&gt;
    &lt;p&gt;Как и&amp;nbsp;при использовании &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;курсора мы&amp;nbsp;можем передать набор параметров запроса, Django заботливо их&amp;nbsp;экранирует.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;query = "SELECT * FROM library_author WHERE first_name = %s"
params = ('bob',)
authors = Author.objects.raw(query, params)&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Так это&amp;nbsp;же здорово! Код SQL защищён от&amp;nbsp;атак, у&amp;nbsp;нас экземпляр модели, который мы&amp;nbsp;хотели, и&amp;nbsp;никакого изобретения велосипеда.&lt;/p&gt;
    &lt;h1&gt;&amp;laquo;Но&amp;nbsp;и&amp;nbsp;это ещё не&amp;nbsp;всё!&amp;raquo;&lt;/h1&gt;
    &lt;p&gt;Как и&amp;nbsp;большинство инструментов Django, метод &lt;code&gt;raw ()&lt;/code&gt; оставляет пространство для использования дополнительных функций в&amp;nbsp;неудобных ситуациях или для особо сложных запросов:&lt;/p&gt;
    &lt;h2&gt;Независимый порядок полей&lt;/h2&gt;
    &lt;p&gt;Для метода &lt;code&gt;Model.objects.raw ()&lt;/code&gt; неважно, в&amp;nbsp;каком порядке возвращаются поля по&amp;nbsp;запросу. Единственное, что важно&amp;nbsp;&amp;mdash; соответствуют&amp;nbsp;ли имена полей запроса полям в&amp;nbsp;модели.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# All of these queries will work the same
Author.objects.raw("SELECT * FROM library_author")
Author.objects.raw("SELECT id, first_name, last_name FROM library_author")
Author.objects.raw("SELECT last_name, id, first_name FROM library_author")&lt;/code&gt;&lt;/pre&gt;
    &lt;h2&gt;Аннотации&lt;/h2&gt;
    &lt;p&gt;Если в&amp;nbsp;ответ на&amp;nbsp;запрос мы&amp;nbsp;получаем поля, которые не&amp;nbsp;существуют в&amp;nbsp;классе модели, они добавляются в&amp;nbsp;качестве аннотаций к&amp;nbsp;тем экземплярам модели, которые возвращает метод &lt;code&gt;RawQueryset&lt;/code&gt;. Это с&amp;nbsp;лёгкостью позволяет использовать все преимущества действий или вычислений, выполнение которых более эффективно на&amp;nbsp;уровне базы данных.&lt;/p&gt;
    &lt;pre&gt;&lt;code&gt;&gt;&gt;&gt; authors = Author.objects.raw("SELECT *, age(birth_date) as age FROM library_authors")
&gt;&gt;&gt; for author in authors:
...     print "%s is %s." % (author.first_name, author.age)
John is 37.
Jane is 42.
...&lt;/code&gt;&lt;/pre&gt;
    &lt;h2&gt;Определения соотношения между полями модели и запроса&lt;/h2&gt;
    &lt;p&gt;Если по&amp;nbsp;&lt;nobr&gt;какой-либо&lt;/nobr&gt; причине не&amp;nbsp;удаётся точно сопоставить имена поля запросов и&amp;nbsp;имена поля модели, метод &lt;code&gt;Model.objects.raw ()&lt;/code&gt; предоставляет возможность указать его вручную.&lt;/p&gt;
    &lt;p&gt;Чтобы сопоставить поля запроса с&amp;nbsp;полями модели, нужно просто использовать словарь, содержащий требуемые соответствия, в&amp;nbsp;качестве одного из&amp;nbsp;параметров метода &lt;code&gt;raw ()&lt;/code&gt;. Соответствия необходимо определить только для тех полей, которые не&amp;nbsp;удалось сопоставить с&amp;nbsp;полями модели.&lt;/p&gt;
    &lt;pre&gt;&lt;code&gt;field_map = {'first': 'first_name', 'last': 'last_name'}
    query = 'SELECT id, first_name AS first, last_name as last FROM library_author'
    authors = Author.objects.raw(query, translations=field_map)&lt;/code&gt;&lt;/pre&gt;
    &lt;h2&gt;Отложенные поля&lt;/h2&gt;
    &lt;p&gt;Те&amp;nbsp;поля, которые предполагаются в&amp;nbsp;модели, но&amp;nbsp;не&amp;nbsp;возвращаются запросом, отмечаются как &lt;a href="http://docs.djangoproject.com/en/dev/ref/models/querysets/#queryset-defer"&gt;отложенные&lt;/a&gt;. Они будут заполнены только при запросе к&amp;nbsp;полю экземпляра модели. Это полезно в&amp;nbsp;тех случаях, когда данные запрашиваются не&amp;nbsp;из&amp;nbsp;&amp;laquo;реальной&amp;raquo; таблицы для модели, или когда сами таблицы достаточно большие. Здесь надо иметь в&amp;nbsp;виду, что первичный ключ не&amp;nbsp;может быть отложен и&amp;nbsp;должен быть возвращён всеми запросами. Если запрос не&amp;nbsp;возвращает первичный ключ, будет вызвано исключение InvalidQuery.&lt;/p&gt;
    &lt;h1&gt;Ограничения&lt;/h1&gt;
    &lt;p&gt;На&amp;nbsp;действия метода &lt;code&gt;raw ()&lt;/code&gt; накладываются некоторые ограничения. Самое существенное из&amp;nbsp;них заключается в&amp;nbsp;том, что метод &lt;code&gt;raw ()&lt;/code&gt; поддерживает только &amp;nbsp;&lt;code&gt;SELECT&lt;/code&gt; запросы. При попытке использовать любой другой тип запроса, будет вызвано исключение &lt;code&gt;InvalidQuery&lt;/code&gt;. Первоначально это было сделано с&amp;nbsp;целью защиты, но&amp;nbsp;отчасти это сделано так и&amp;nbsp;потому что нет смысла возвращать экземпляр модели для &lt;nobr&gt;чего-либо&lt;/nobr&gt; ещё кроме запроса типа &lt;code&gt;SELECT&lt;/code&gt;. Изменение данных с&amp;nbsp;помощью прямого SQL&amp;nbsp;&amp;mdash; это последнее, что стоит делать с&amp;nbsp;помощью Django. Чтобы не&amp;nbsp;провоцировать эти действия, мы&amp;nbsp;ни&amp;nbsp;в&amp;nbsp;коем случае не&amp;nbsp;хотим делать их&amp;nbsp;удобнее для пользователя. Если вам требуется использовать именно прямые запросы SQL кроме запроса типа &lt;code&gt;SELECT&lt;/code&gt;, у&amp;nbsp;вас всегда остаётся возможность создать курсор &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;и&amp;nbsp;работать оттуда.&lt;/p&gt;
    &lt;h1&gt;Вот и&amp;nbsp;всё&lt;/h1&gt;
    &lt;p&gt;Ну&amp;nbsp;вот и&amp;nbsp;всё. В&amp;nbsp;версии Django 1.2 существенно упрощена работа с&amp;nbsp;SQL запросами там, где это необходимо. Кривая, о&amp;nbsp;которой мы&amp;nbsp;говорили выше, имеет гораздо более гладкий вид. Официальную документацию для этой функции можно найти в&amp;nbsp;&lt;a href="http://docs.djangoproject.com/en/dev/topics/db/sql/#topics-db-sql"&gt;разделе, посвящённому SQL&lt;/a&gt;.&lt;/p&gt;

&lt;div class="footer"&gt;
&lt;p&gt;
    Шон О’Коннор&amp;nbsp;&amp;mdash; разработчик &lt;a href="http://hugeinc.com/"&gt;HUGE&lt;/a&gt; и&amp;nbsp;один из&amp;nbsp;партнёров организаторов сайта для группы пользователей &lt;a href="http://www.djangonyc.org/"&gt;Django NYC&lt;/a&gt;. В&amp;nbsp;течение трёх предыдущих лет Шон работал над приложениями Django и&amp;nbsp;последнее время внёс существенный вклад в&amp;nbsp;базовые усовершенствования раздела прямых &lt;a href="http://docs.djangoproject.com/en/dev/topics/db/sql/#topics-db-sql"&gt;SQL запросов&lt;/a&gt; в&amp;nbsp;Django 1.2. В&amp;nbsp;свободное от&amp;nbsp;разработки Django и&amp;nbsp;HUGE время он&amp;nbsp;пьёт пиво и&amp;nbsp;играет в&amp;nbsp;настольные игры. Найти в&amp;nbsp;интернете его можно по&amp;nbsp;адресу seanoc.com (http://seanoc.com/), его твиттер&amp;nbsp;&amp;mdash; &lt;a href="http://twitter.com/theSeanOC"&gt;@theSeanOC&lt;/a&gt;.&lt;/p&gt;
    &lt;/div&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="http://djangoadvent.com/1.2/smoothing-curve/"&gt;Оригинальная статья&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-1899645955669862134?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/1899645955669862134/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=1899645955669862134' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1899645955669862134'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1899645955669862134'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2010/06/smoothing-curve.html' title='Smoothing The Curve'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-3565304731862910278</id><published>2010-05-27T00:17:00.001+04:00</published><updated>2010-05-27T00:20:02.617+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='messages'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><category scheme='http://www.blogger.com/atom/ns#' term='django advent'/><title type='text'>Messages for the rest of us</title><content type='html'>&lt;style&gt;
.article header {
 font-family:LeagueGothicRegular,Impact,sans-serif;
 font-weight:normal;
 height:100px;
 margin-bottom:20px;
 position:relative;
 text-transform:uppercase;
 display: block;
}
.article header h1 {
 color:#586C78;
 font-size:42px;
 font-weight:normal;
 height:60px;
 left:0;
 letter-spacing:1px;
 line-height:60px;
 position:absolute;
 top:10px;
}
.article header h2 {
 color:#CCCCCC;
 font-size:28px;
 font-weight:normal;
 height:40px;
 left:0;
 letter-spacing:1px;
 line-height:40px;
 position:absolute;
 top:0;
}
.article header h2 a {
 border:medium none;
 color:#ABB9C1;
 padding:0;
 text-decoration:none;
}
.article header h2 sup {
 font-size:0.7em;
 line-height:1em;
 text-decoration:underline;
 vertical-align:6px;
}
.article p, .article ul, .article ol, .article dl {
 font-size:18px;
 line-height:1.75em;
 margin-bottom:1em;
 font-family:Palatino,Georgia,"Times New Roman",Times,serif;
} 
.article .content {
 width: 100% !important;
}
.article .editor {
 font-size: 80%;
 margin-top: 0;
 padding: 0 0 0 0;
 line-height: 100%;
 display: block;
 font-style: italic;
}
.article .footer p {
 font-size: 14px;
 line-height: 1.75em;
}
code {
 font-size: 13px;
}
&lt;/style&gt;

&lt;div class="article"&gt;
&lt;header&gt;
  &lt;hgroup&gt;
   &lt;h1&gt;Messages for the rest of us&lt;/h1&gt;
   &lt;h2&gt;&lt;a href="http://djangoadvent.com/authors/jeff-croft/"&gt;Jeff Croft&lt;/a&gt; February 10&lt;sup&gt;th&lt;/sup&gt; 2010&lt;/h2&gt;
  &lt;/hgroup&gt;
&lt;/header&gt;

&lt;div class="content"&gt; 

&lt;p&gt;Эта статья описывает изменения механизма сообщений в Django. В Django 1.2 можно посылать сообщения не только зарегистрированным пользователям, но и анонимным. Полностью переработан API.&lt;/p&gt;
&lt;p&gt;Статья уже переведена мои другом Дмитрием Медвинским:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://habrahabr.ru/blogs/django/87473/"&gt;http://habrahabr.ru/blogs/django/87473/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Дмитрий мой бывший коллега и просто замечательный человек, follow &lt;a href="http://twitter.com/justadeveloper"&gt;@justadeveloper&lt;/a&gt;!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-3565304731862910278?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/3565304731862910278/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=3565304731862910278' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/3565304731862910278'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/3565304731862910278'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2010/05/messages-for-rest-of-us.html' title='Messages for the rest of us'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-1406563391888490488</id><published>2010-05-22T21:20:00.006+04:00</published><updated>2010-05-23T01:08:23.338+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><category scheme='http://www.blogger.com/atom/ns#' term='alex gaynor'/><category scheme='http://www.blogger.com/atom/ns#' term='django advent'/><title type='text'>Multiple database support</title><content type='html'>&lt;style&gt;
.article header {
 font-family:LeagueGothicRegular,Impact,sans-serif;
 font-weight:normal;
 height:100px;
 margin-bottom:20px;
 position:relative;
 text-transform:uppercase;
 display: block;
}
.article header h1 {
 color:#586C78;
 font-size:42px;
 font-weight:normal;
 height:60px;
 left:0;
 letter-spacing:1px;
 line-height:60px;
 position:absolute;
 top:10px;
}
.article header h2 {
 color:#CCCCCC;
 font-size:28px;
 font-weight:normal;
 height:40px;
 left:0;
 letter-spacing:1px;
 line-height:40px;
 position:absolute;
 top:0;
}
.article header h2 a {
 border:medium none;
 color:#ABB9C1;
 padding:0;
 text-decoration:none;
}
.article header h2 sup {
 font-size:0.7em;
 line-height:1em;
 text-decoration:underline;
 vertical-align:6px;
}
.article p, .article ul, .article ol, .article dl {
 font-size:18px;
 line-height:1.75em;
 margin-bottom:1em;
 font-family:Palatino,Georgia,"Times New Roman",Times,serif;
} 
.article .content {
 width: 100% !important;
}
.article .editor {
 font-size: 80%;
 margin-top: 0;
 padding: 0 0 0 0;
 line-height: 100%;
 display: block;
 font-style: italic;
}
.article .footer p {
 font-size: 14px;
 line-height: 1.75em;
}
code {
 font-size: 13px;
}
&lt;/style&gt;

&lt;div class="article"&gt;
&lt;header&gt;
  &lt;hgroup&gt;
   &lt;h1&gt;Multiple database support&lt;/h1&gt;
   &lt;h2&gt;&lt;a href="http://djangoadvent.com/authors/alex-gaynor/"&gt;Alex Gaynor&lt;/a&gt; February 9&lt;sup&gt;th&lt;/sup&gt; 2010&lt;/h2&gt;
  &lt;/hgroup&gt;
&lt;/header&gt;

&lt;div class="content"&gt; 
 &lt;p&gt;Изначально Django предполагал работу только с&amp;nbsp;одной базой данных (системное ограничение включающее такие вещи как группа настроек &lt;code&gt;DATABASE_*)&lt;/code&gt;. В&amp;nbsp;течение всего этого времени явно ощущалась необходимость поддержки возможности работы с&amp;nbsp;несколькими &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;. В&amp;nbsp;рамках работы над версией 1.2 в&amp;nbsp;течение &lt;a href="http://code.google.com/intl/ru/soc/"&gt;Google Summer of&amp;nbsp;Code&lt;/a&gt; поддержка нескольких &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;была включена в&amp;nbsp;trunk. С&amp;nbsp;этими новшествами связаны как целый ряд внутренних изменений, так и&amp;nbsp;несколько удачных расширений для существующих интерфейсов работы с&amp;nbsp;&lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;.&lt;/p&gt;
 &lt;h1&gt;Интерфейс работы с&amp;nbsp;несколькими &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&lt;/h1&gt;
 &lt;p&gt;Наиболее заметное изменение в&amp;nbsp;Django состоит в&amp;nbsp;том, что, вместо того, чтобы писать настройки базы данных таким образом:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_ENGINE = "postgresql_psycopg2"
DATABASE_NAME = "my_big_project"
DATABASE_USER = "mario"
DATABASE_PASSWORD = "princess_peach"&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Вы&amp;nbsp;пишете следующее:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": "my_big_project",
        "USER": "mario",
        "PASSWORD": "princess_peach",
    },

    "credentials": {
        "ENGINE": "django.db.backends.oracle",
        "NAME": "users",
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;В&amp;nbsp;конечном итоге все проекты должны будут быть переведены в&amp;nbsp;такой вид, хотя старый формат будет поддерживаться в&amp;nbsp;Django вплоть до&amp;nbsp;версии 1.4. Единственный ключ в&amp;nbsp;словаре &lt;code&gt;DATABASES&lt;/code&gt;, имеющий критическое значение, это &lt;code&gt;&amp;laquo;default&amp;raquo;&lt;/code&gt;&amp;nbsp;&amp;mdash; &amp;laquo;по&amp;nbsp;умолчанию&amp;raquo;. Если вы&amp;nbsp;используете базу данных, вам необходимо определить базу данных &lt;code&gt;&amp;laquo;default&amp;raquo;&lt;/code&gt;.&lt;/p&gt;
 &lt;p&gt;Теперь, когда вы&amp;nbsp;сообщили Django о&amp;nbsp;всех своих базах данных, надо найти способ сообщить Django о&amp;nbsp;том, как их&amp;nbsp;использовать. Первым изменением интерфейса в&amp;nbsp;этой области является метод &lt;code&gt;using()&lt;/code&gt;. &lt;code&gt;using()&lt;/code&gt; принимает единственный параметр имя &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;(имена &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;служат ключами словаря &lt;code&gt;DATABASES&lt;/code&gt;), и&amp;nbsp;привязывает &lt;code&gt;QuerySet&lt;/code&gt; к&amp;nbsp;этой базе данных. Как и&amp;nbsp;любое другое средство класса &lt;code&gt;QuerySet&lt;/code&gt;, он&amp;nbsp;может быть при необходимости поставлен в&amp;nbsp;цепочку (как, например, метод &lt;code&gt;order_by()&lt;/code&gt;, обновляющий результаты первого запроса при повторном запросе). По&amp;nbsp;сути, это даёт вам возможность полностью контролировать, откуда будут читаться данные (а&amp;nbsp;при ловком использовании методов &lt;code&gt;create()&lt;/code&gt;, &lt;code&gt;delete()&lt;/code&gt; и&amp;nbsp;&lt;code&gt;update()&lt;/code&gt; даёт даже возможность контролировать запись):&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;&gt;&gt;&gt;User.objects.filter(username__startswith="admin").using("credentials")&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Новые методы работы с&amp;nbsp;данными класса &lt;code&gt;QuerySet&lt;/code&gt; &lt;code&gt;delete()&lt;/code&gt; и&amp;nbsp;&lt;code&gt;save()&lt;/code&gt; принимают дополнительный аргумент using, в&amp;nbsp;который передаётся, опять&amp;nbsp;же, имя &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;.&lt;/p&gt;
 &lt;p&gt;Кроме того, появляется новый метод класса &lt;code&gt;Managers&lt;/code&gt;, &lt;code&gt;db_manager()&lt;/code&gt;, который тоже принимает имя &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;. Его функция по&amp;nbsp;сути близка функции метода &lt;code&gt;using()&lt;/code&gt;. Основное различие заключается в&amp;nbsp;следующем: вместо того, чтобы возвращать &lt;code&gt;QuerySet&lt;/code&gt;, он&amp;nbsp;возвращает новый &lt;code&gt;Manager&lt;/code&gt;. Сценарий его использования делает возможным связать его в&amp;nbsp;цепочку с&amp;nbsp;теми методами класса Managers, которые не&amp;nbsp;возвращают &lt;code&gt;QuerySet&lt;/code&gt; (например, такими как &lt;code&gt;create_user()&lt;/code&gt; класса &lt;code&gt;UserManager&lt;/code&gt;).&lt;/p&gt;
 &lt;h1&gt;Маршрутизаторы &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&lt;/h1&gt;
 &lt;p&gt;Используя все эти методы, вы&amp;nbsp;получаете возможность реализовать любую систему, использующую несколько баз данных. В&amp;nbsp;том числе &lt;nobr&gt;master-slave&lt;/nobr&gt; replication, partitioning и&amp;nbsp;sharding. Однако, это не&amp;nbsp;обязательно будет удобно. Вам придётся часто использовать в&amp;nbsp;коде запросы к&amp;nbsp;методу &lt;code&gt;using()&lt;/code&gt;, что не&amp;nbsp;лучшим образом совместимо с&amp;nbsp;принципом реиспользуемых приложений Django (именно по&amp;nbsp;этой причине опция &lt;code&gt;using&lt;/code&gt; была удалена из&amp;nbsp;класса модели &lt;code&gt;Meta&lt;/code&gt;). Кроме того, в&amp;nbsp;некоторых случаях класс &lt;code&gt;QuerySet&lt;/code&gt; не&amp;nbsp;полностью определён. Так, например, для получения доступа к&amp;nbsp;данным класса &lt;code&gt;User&lt;/code&gt; метод &lt;code&gt;my_obj.user&lt;/code&gt; неявно создаст &lt;code&gt;QuerySet&lt;/code&gt;, но&amp;nbsp;методу &lt;code&gt;using()&lt;/code&gt; будет некуда отправить запрос. Этим и&amp;nbsp;было обусловлено появление концепции &amp;laquo;маршрутизатора &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;raquo;. Маршрутизаторы &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;получают всю информацию о&amp;nbsp;запросе, который вы&amp;nbsp;хотите выполнить, и&amp;nbsp;определяют, какую базу необходимо обратиться. Маршрутизаторы задаются в&amp;nbsp;файле настроек в&amp;nbsp;виде списка &lt;code&gt;DATABASE_ROUTERS&lt;/code&gt;:&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;DATABASE_ROUTERS = [
     "path.to.AuthRouter",
     "path.to.MasterSlaveRouter",
 ]&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;code&gt;DATABASE_ROUTERS&lt;/code&gt; задаётся списком, поскольку маршрутизатор на&amp;nbsp;любой стадии может вернуть значение None и&amp;nbsp;тогда Django перейдет к&amp;nbsp;следующему маршрутизатору из&amp;nbsp;списка. Маршрутизаторы могу определять следующие методы:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;code&gt;db_for_read&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;db_for_write&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;allow_relation&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;allow_syncdb&lt;/code&gt;&lt;/li&gt;
 &lt;/ul&gt;
 &lt;p&gt;Первые два метода очевидны, они возвращают имя &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;, по&amp;nbsp;отношению к&amp;nbsp;которой выполняется запрос. Метод &lt;code&gt;allow_relation&lt;/code&gt; призван контролировать разумность запросов. Django не&amp;nbsp;позволит вам создать заведомо некорректные связи между &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;. То&amp;nbsp;есть, если вы&amp;nbsp;попытаетесь создать взаимосвязь между двумя структурами данных из&amp;nbsp;разных &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&amp;nbsp;(для &lt;code&gt;ForeignKey&lt;/code&gt; или для &lt;code&gt;ManyToManyField&lt;/code&gt;), с&amp;nbsp;помощью данного метода Django запросит необходимое подтверждение. И&amp;nbsp;последний метод &lt;code&gt;allow_syncdb&lt;/code&gt; позволяет определить, в&amp;nbsp;какой из&amp;nbsp;баз данных будет создана конкретная модель.&lt;/p&gt;
 &lt;p&gt;В&amp;nbsp;документации Django по&amp;nbsp;работе &lt;a href="http://docs.djangoproject.com/en/dev/topics/db/multi-db/"&gt;с&amp;nbsp;несколькими &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;&lt;/a&gt;, как всегда, приведена масса практических примеров. В&amp;nbsp;том числе приведены примеры, описывающие, как начать применять распространённые паттерны с&amp;nbsp;маршрутизаторами баз данных. Поддержка нескольких баз данных дает огромные преимущества и&amp;nbsp;значительно расширяет области применения Django.&lt;/p&gt;
 &lt;div class="footer"&gt;
 &lt;p&gt;Алекс Гейнор&amp;nbsp;&amp;mdash; студент второго года обучения политехнического института Renesselear по&amp;nbsp;специальности вычислительная техника, работает в&amp;nbsp;качестве &lt;nobr&gt;веб-разработчика&lt;/nobr&gt; в&amp;nbsp;&lt;a href="http://eldarion.com/"&gt;Eldarion&lt;/a&gt;. Работает с&amp;nbsp;Django около двух лет, и&amp;nbsp;примерно столько&amp;nbsp;же активно участвует в&amp;nbsp;его дальнейшей разработке. Кроме работы с&amp;nbsp;Django, он&amp;nbsp;вносит свой вклад в&amp;nbsp;развитие таких проектов, как &lt;a href="http://pinaxproject.com/"&gt;Pinax&lt;/a&gt;, &lt;a href="http://github.com/robhudson/django-debug-toolbar"&gt;&lt;nobr&gt;django-debug-toolbar&lt;/nobr&gt;&lt;/a&gt;, а&amp;nbsp;также запустил несколько собственных&amp;nbsp;&amp;mdash; как, например, &lt;a href="http://github.com/alex/django-filter"&gt;&lt;nobr&gt;django-filter&lt;/nobr&gt;&lt;/a&gt;, и&amp;nbsp;&lt;a href="http://github.com/alex/django-taggit"&gt;&lt;nobr&gt;django-taggit&lt;/nobr&gt;&lt;/a&gt;. Поклонник качественных программных интерфейсов, гибкого масштабирования и&amp;nbsp;компиляторов.&lt;/p&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href="http://djangoadvent.com/1.2/multiple-database-support/"&gt;Оригинал статьи&lt;/a&gt; | &lt;a href="http://alarin.blogspot.com/search/label/django%20advent"&gt;Все статьи&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Не могу не упомянуть в теме о работе с базами данных в Django, замечательную утилиту миграции структуры БД &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt;, которая уже &lt;a href="http://south.aeracode.org/ticket/233"&gt;поддерживает новый интерфейс&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Если вам понравился перевод &lt;a href="http://alarin.blogspot.com/feeds/posts/default?alt=rss"&gt;подписывайтесь на RSS&lt;/a&gt;, я планирую перевести всю серию статей.&lt;/p&gt;
&lt;p&gt;Хотите сообщить об ошибке в статье? Обсудить или задать вопрос по работе с несколькими БД? Добро пожаловать в &lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=1406563391888490488"&gt;комментарии&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-1406563391888490488?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/1406563391888490488/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=1406563391888490488' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1406563391888490488'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1406563391888490488'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2010/05/multiple-database-support.html' title='Multiple database support'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-1541087407830867638</id><published>2010-05-19T01:57:00.008+04:00</published><updated>2010-05-22T19:05:05.627+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><category scheme='http://www.blogger.com/atom/ns#' term='Jacob Kaplan-Moss'/><category scheme='http://www.blogger.com/atom/ns#' term='django advent'/><category scheme='http://www.blogger.com/atom/ns#' term='1.2'/><title type='text'>Welcome to Django Advent</title><content type='html'>&lt;style&gt;
.article header {
 font-family:LeagueGothicRegular,Impact,sans-serif;
 font-weight:normal;
 height:100px;
 margin-bottom:20px;
 position:relative;
 text-transform:uppercase;
 display: block;
}
.article header h1 {
 color:#586C78;
 font-size:42px;
 font-weight:normal;
 height:60px;
 left:0;
 letter-spacing:1px;
 line-height:60px;
 position:absolute;
 top:10px;
}
.article header h2 {
 color:#CCCCCC;
 font-size:28px;
 font-weight:normal;
 height:40px;
 left:0;
 letter-spacing:1px;
 line-height:40px;
 position:absolute;
 top:0;
}
.article header h2 a {
 border:medium none;
 color:#ABB9C1;
 padding:0;
 text-decoration:none;
}
.article header h2 sup {
 font-size:0.7em;
 line-height:1em;
 text-decoration:underline;
 vertical-align:6px;
}
.article p, .article ul, .article ol, .article dl {
 font-size:18px;
 line-height:1.75em;
 margin-bottom:1em;
 font-family:Palatino,Georgia,"Times New Roman",Times,serif;
} 
.article .content {
 width: 100% !important;
}
.article .editor {
 font-size: 80%;
 margin-top: 0;
 padding: 0 0 0 0;
 line-height: 100%;
 display: block;
 font-style: italic;
}
&lt;/style&gt;

&lt;div class="article"&gt;
&lt;header&gt;
  &lt;hgroup&gt;
   &lt;h1&gt;Welcome to Django-Advent&lt;/h1&gt;
   &lt;h2&gt;&lt;a href="http://djangoadvent.com/authors/jacob-kaplan-moss/"&gt;Jacob Kaplan-Moss&lt;/a&gt; February 8&lt;sup&gt;th&lt;/sup&gt; 2010&lt;/h2&gt;
  &lt;/hgroup&gt;
&lt;/header&gt;

&lt;div class="content"&gt;
&lt;p&gt;Релиз Django 1.2 невероятно меня воодушевил. Оглядываясь назад, я&amp;nbsp;могу сказать, что было несколько моментов, которые оказались ключевыми как для самого проекта Django, так и&amp;nbsp;для общества в&amp;nbsp;целом. Слияние ветки &lt;nobr&gt;&amp;laquo;magic-removal&amp;raquo;&lt;/nobr&gt; и&amp;nbsp;последующий релиз 0.96 был первым таким моментом: мы&amp;nbsp;выпустили улучшенный Django и&amp;nbsp;наше сообщество начало развиваться огромными скачками. Релиз Django 1.0 стал следующим ключевым моментом. Метка 1.0 гордо символизировала наш профессионализм и зрелость проекта.&lt;/p&gt;

&lt;p&gt;И&amp;nbsp;я&amp;nbsp;абсолютно уверен, что версия 1.2 станет очередной ключевой точкой проекта.&lt;/p&gt;

&lt;p&gt;Новые особенности&amp;nbsp;&amp;mdash; поддержка множественных соединений с&amp;nbsp;базами данных, валидация моделей, усовершенствованная защита от&amp;nbsp;CSRF, улучшение административного интерфейса&amp;nbsp;&amp;mdash; являются одними из&amp;nbsp;самых ожидаемых в&amp;nbsp;истории Django. Эти особенности позволят нашим пользователям покорять новые горизонты с Django, и&amp;nbsp;я&amp;nbsp;с&amp;nbsp;нетерпением жду, когда смогу увидеть, какие новые задачи вы сможете решить.&lt;/p&gt;

&lt;p&gt;В&amp;nbsp;течение нескольких следующих недель мы будем писать статьи, где более подробно и&amp;nbsp;глубоко обсуждаются новые особенности проекта; многие из&amp;nbsp;статей написаны теми, кто разрабатывал и&amp;nbsp;вводил эти особенности. И&amp;nbsp;чтобы не&amp;nbsp;отбирать у&amp;nbsp;них хлеб, я&amp;nbsp;постараюсь рассмотреть их в общем, и&amp;nbsp; показать как они закладывают основу для Django 1.3, 1.4 и&amp;nbsp;выше.&lt;/p&gt;

&lt;p&gt;Поддержка множественных соединений с&amp;nbsp;базами данных это &amp;laquo;киллер фича&amp;raquo; Django 1.2. Добавление функции такого рода &amp;mdash; интересная задача: существуют разногласие между нуждами начинающих и&amp;nbsp; разработчиков ненагруженных приложений&amp;nbsp;&amp;mdash; которые более всего ценят простоту и&amp;nbsp;удобство в&amp;nbsp;использовании&amp;nbsp;&amp;mdash; и&amp;nbsp;возрастающего числа разработчиков высоконагруженных приложений, которым необходимо больше &amp;nbsp;контроля и&amp;nbsp; гибкости, то есть большей сложности фремворка.&lt;/p&gt;
 
&lt;p&gt;Я&amp;nbsp;думаю, это разногласие между простотой и&amp;nbsp;конфигурируемостью, между простотой использования и&amp;nbsp;масштабируемостью, будет и&amp;nbsp;дальше усиливаться. По&amp;nbsp;мере развития Django, пользователи будут прибегать к&amp;nbsp;нему для выполнения все более и&amp;nbsp;более сложных задач, и&amp;nbsp;мы&amp;nbsp;хотим, чтобы Django соответствовал этим задачам. В&amp;nbsp;то&amp;nbsp;же время мы&amp;nbsp;не&amp;nbsp;хотим топить пользователей, мало знакомых с&amp;nbsp;Django, в&amp;nbsp;болоте повышенной сложности, иначе они обратятся &lt;nobr&gt;куда-нибудь&lt;/nobr&gt; еще и&amp;nbsp;наше сообщество ожидает стагнация.&lt;/p&gt;

&lt;p&gt;В&amp;nbsp;новой поддержке множественных соединений с&amp;nbsp;базами данных Django я&amp;nbsp;вижу, что мы&amp;nbsp;весьма близко подобрались к&amp;nbsp;границе. Я&amp;nbsp;очень горжусь той работой, которую мы&amp;nbsp;сделали, балансируя между конкурирующими потребностями: начать пользоваться Django 1.2 просто, как всегда, но&amp;nbsp;мы&amp;nbsp;весьма расширили горизонты Django. И&amp;nbsp;я&amp;nbsp;надеюсь, что мы&amp;nbsp;сможем нащупать баланс между мирами.&lt;/p&gt;

&lt;p&gt;Я&amp;nbsp;также очень впечатлён изменениями, которые я&amp;nbsp;наблюдаю в&amp;nbsp;нашем сообществе разработчиков. В&amp;nbsp;процессе выпуска версии 1.2&amp;nbsp;мы&amp;nbsp;заметили большое количество новичков. И это хорошо, поскольку существующие члены теряют интерес к&amp;nbsp;проекту, но благодаря новым разработчикам работа &lt;nobr&gt;по-прежнему&lt;/nobr&gt; выполняется.&lt;/p&gt;
 
&lt;p&gt;Многие из&amp;nbsp;новичков попали к&amp;nbsp;нам через Google&amp;rsquo;s Summer of&amp;nbsp;Code 2009. В&amp;nbsp;том году мы&amp;nbsp;приняли 6 проектов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;поддержка множественных соединений с&amp;nbsp;базами данных (Alex Gaynor)
&lt;li&gt;улучшения HTTP и&amp;nbsp;WSGI (Chris Cahoon)&lt;/li&gt;
&lt;li&gt;улучшенние локализации (Marc Garcia)&lt;/li&gt;
&lt;li&gt;валидация моделей (Honza Kr&amp;#225;l)&lt;/li&gt;
&lt;li&gt;дополнения к&amp;nbsp;тестовому фреймворку (Kevin Kubasic)&lt;/li&gt;
&lt;li&gt;улучшения администрировного интерфейса (Zain Memon).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Программа имела потрясающий успех: все проекты произвели хороший, работающий код. Часть его вошла в&amp;nbsp;версию 1.2 часть продолжит свой путь в&amp;nbsp;выпуске 1.3. Многие из&amp;nbsp;этих проектов были воодушевлены долгим ожиданием новых функций, а&amp;nbsp;работа, сделанная в&amp;nbsp;течение Summer of&amp;nbsp;Code оказалась как раз тем, что было нам нужно для их реализации.&lt;/p&gt;

&lt;p&gt;Мы&amp;nbsp;также добавили пару новых разработчиков&amp;nbsp;&amp;mdash; Jannis Leidel и James Tauber&amp;nbsp;&amp;mdash; и&amp;nbsp;я&amp;nbsp;готов спорить, что мы&amp;nbsp;увидим еще ребят, которые будут задействованы в&amp;nbsp;проекте до&amp;nbsp;финального релиза.&lt;/p&gt;

&lt;p&gt;Кстати, когда же он?
&lt;span class="editor"&gt;(Django 1.2 вышел 17 мая 2010) прим. переводчика&lt;/span&gt;
&lt;/p&gt;

&lt;p&gt;Запланированная дата релиза&amp;nbsp;&amp;mdash; 9&amp;nbsp;марта 2010 года. И&amp;nbsp;мы&amp;nbsp;стоим на&amp;nbsp;пути к&amp;nbsp;тому, что выпуск осуществится вовремя&amp;hellip; но&amp;nbsp;мы&amp;nbsp;никогда не&amp;nbsp;сделали&amp;nbsp;бы этого без Вашей помощи!
&lt;/p&gt;

&lt;p&gt;Каждый релиз Django&amp;nbsp;&amp;mdash; это плод усилий тысяч людей. Нам нужны пользователи, которые могут испытать наши &lt;nobr&gt;бета-версии&lt;/nobr&gt;. Если вы&amp;nbsp;найдете ошибки, вы&amp;nbsp;можете сообщить о&amp;nbsp;них на&amp;nbsp;&lt;a href="http://code.djangoproject.com"&gt;наш трекер&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Еще больше мы&amp;nbsp;любим, когда вы&amp;nbsp;помогаете исправить эти ошибки. Вклад в&amp;nbsp;нашу работу на&amp;nbsp;любом уровне&amp;nbsp;&amp;mdash; разработки кода, написании технической документации, или просто сортировка сообщений и&amp;nbsp;помощь в&amp;nbsp;тестировании предлагаемых способов исправления ошибок&amp;nbsp;&amp;mdash; всегда приветствуется и&amp;nbsp;высоко ценится.&lt;/p&gt;

&lt;p&gt;Онлайн документация Django включает описание того, как вы&amp;nbsp;можете &lt;a href="http://docs.djangoproject.com/en/dev/internals/contributing/#internals-contributing"&gt;помочь проекту&lt;/a&gt;.
 
&lt;p&gt;Мы&amp;nbsp;также проводим спринт для разработчиков по&amp;nbsp;Django 1.2 на&amp;nbsp;PyCon US&amp;nbsp;2010. В&amp;nbsp;течении четырех дней спринта (&lt;nobr&gt;22&amp;mdash;25 февраля&lt;/nobr&gt;) мы&amp;nbsp;будем рады, если вы&amp;nbsp;присоединитесь к&amp;nbsp;нам лично или виртуально по&amp;nbsp;IRC (#&lt;nobr&gt;django-dev&lt;/nobr&gt; на&amp;nbsp;irc.freenode.net) или на&amp;nbsp;&lt;a href="http://groups.google.com/group/django-developers"&gt;http://groups.google.com/group/&lt;nobr&gt;django-developers&lt;/nobr&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Я&amp;nbsp;надеюсь, что вам понравятся остальные статьи и&amp;nbsp;что в&amp;nbsp;конце концов вы&amp;nbsp;будете столь&amp;nbsp;же впечатлены выходом Django 1.2, как и&amp;nbsp;я!&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="http://djangoadvent.com/1.2/welcome-django-advent/"&gt;Статья на Английском&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;В честь &lt;a href="http://www.djangoproject.com/weblog/2010/may/17/12/"&gt;выхода Django 1.2&lt;/a&gt; решил перевести статьи с замечательного сайта &lt;a href="http://djangoadvent.com"&gt;Django Advent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Сейчас на сайте опубликовано 20 статей, и каждая из них рассказывает нам о новой возможности Django. Поверьте, там есть на что посмотреть.&lt;/p&gt;

&lt;p&gt;В планах перевести все, на переведенные другими статьи буду собирать ссылки. Следите за &lt;a href="http://alarin.blogspot.com/feeds/posts/default"&gt;обновлениями&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-1541087407830867638?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/1541087407830867638/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=1541087407830867638' title='Комментарии: 6'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1541087407830867638'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1541087407830867638'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2010/05/welcome-to-django-advert.html' title='Welcome to Django Advent'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-2373928679327922249</id><published>2010-03-09T12:10:00.001+03:00</published><updated>2010-03-09T12:14:48.848+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='en'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='list clustering'/><category scheme='http://www.blogger.com/atom/ns#' term='zip'/><title type='text'>List clustering</title><content type='html'>&lt;p&gt;Today I will tell you about just one line of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;zip(*[a.__iter__()]*3)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, if you know, how it works, you can skip to the comments right now. :)&lt;/p&gt;

&lt;p&gt;This statement may look short and simple but it's actually very interesting
and, sometimes, useful Python construction. In a job interviews we ask people
to tell how does it work. If a person gets along with it, well, this is a good
sign.&lt;/p&gt;

&lt;p&gt;Here is the example of what it does:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; a = [1,2,3,4,5,6,7,8,10]
&gt;&gt;&gt; zip(*[a.__iter__()]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 10)]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lets look at it piecemeal.&lt;/p&gt;

&lt;h3&gt;__iter__()&lt;/h3&gt;
&lt;p&gt;Returns iterator object for a list (or tuple or any other iterable object).&lt;/p&gt;
&lt;p&gt;This method is used by the interpreter when he encounters a list in, for
example, a &lt;code&gt;for&lt;/code&gt; loop.&lt;/p&gt;
&lt;p&gt;The iterator object by itself has a &lt;code&gt;next()&lt;/code&gt; method, which returns list items
one by one.&lt;/p&gt;

&lt;h3&gt;[]*3&lt;/h3&gt;
&lt;p&gt;Multiplication of a list creates a new list repeating given amount of times the items from source list:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; [1,2,3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Important thing here: &lt;b&gt;all identifiers in Python are references&lt;/b&gt;. It means that &lt;code&gt;[a.__iter__()]*3&lt;/code&gt; gives us a list of three references on the same iterator object.&lt;/p&gt;
&lt;p&gt;A little example (note the address of listiterator object is the same):&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; iters = [a.__iter__()]*3
&gt;&gt;&gt; print iters
[&amp;lt;listiterator object at 0x85db0&amp;gt;, &amp;lt;listiterator object at 0x85db0&amp;gt;, &amp;lt;listiterator object at 0x85db0&amp;gt;]
&gt;&gt;&gt; (iters[0].next(), iters[1].next(), iters[2].next())
(1, 2, 3)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;*&lt;/h3&gt;
&lt;p&gt; Takes an iterable object and passes it's members to a function as non-keyword arguments.&lt;/p&gt;
&lt;p&gt;This is one of my favourite Python features. The following lines are equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;my_func(*[1,2,3])
my_func(1,2,3)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;zip&lt;/h3&gt;
&lt;p&gt;zip returns a list of tuples, where the i-th tuple consists of i-th member of
each function argument. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; zip((1,2), (3,4), (4,5))
[(1, 3, 4), (2, 4, 5)]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Official  &lt;a href="http://docs.python.org/library/functions.html#zip"&gt;Python documentation declares&lt;/a&gt;:&lt;/p&gt;
&lt;p class="clip"&gt;&lt;i&gt;The left-to-right evaluation order of the iterables is guaranteed. This makes possible an idiom for clustering a data series into n-length groups using zip(*[iter(s)]*n).&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;You may have noticed the construction we are trying to understand here. So, this isn't some &lt;i&gt;Perl one-liner&lt;/i&gt;, but an official Python feature.&lt;/p&gt;

&lt;p&gt;Use the power of Python, &lt;br /&gt;and may the Force be with you.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-2373928679327922249?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/2373928679327922249/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=2373928679327922249' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/2373928679327922249'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/2373928679327922249'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2010/03/list-clustering.html' title='List clustering'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-4727453758596579111</id><published>2010-03-08T12:51:00.010+03:00</published><updated>2010-03-10T08:44:20.112+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='list clusterization'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='zip'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><title type='text'>Кластеризация списка (List clustering)</title><content type='html'>&lt;p&gt;И снова здравствуйте :)&lt;/p&gt;

&lt;p&gt;Давненько я не писал, и с того времени произошло много хороших вещей:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;
 &lt;p&gt;Вышла &lt;a href="http://www.djangoproject.com/download/1.2-beta-1/tarball/"&gt;бета Django 1.2&lt;/a&gt;, в ней огромное количество &lt;a href="http://docs.djangoproject.com/en/dev/releases/1.2-beta-1/"&gt;позитивных&lt;/a&gt; &lt;a href="http://docs.djangoproject.com/en/dev/releases/1.2-alpha-1/"&gt;изменений&lt;/a&gt;.
Такое чувство, что мне не хватало каждого из них. Взять хотя бы smart if tag, object-level permissions, session messages и улучшения интернационализации (теперь дата форматируется согласно текущей локали по умолчанию).&lt;/p&gt;
&lt;p&gt;Более подробно об этих изменениях вы можете прочитать на замечательном сайте &lt;a href="http://djangoadvent.com/"&gt;Django Advent&lt;/a&gt;, статьи из которого, кстати, я собираюсь переводить и публиковать в этом блоге.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://alarin.blogspot.com/feeds/posts/default"&gt;Следите за обновлениями&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
 &lt;p&gt;На работе мы стали использовать &lt;a href="http://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;, очень удобная вещь на мой взгляд.&lt;/p&gt;
 &lt;p&gt;Вот, например, файл зависимостей из &lt;a href="http://tripithere.ru"&gt;одного проекта&lt;/a&gt;:&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;Django==1.1
south==0.6.2
django-debug-toolbar==0.8.1
django-extensions==0.4.1
django-notification==0.1.2 #0.1.3-4 has a bug in lockfile.py
django-messages==0.4.2
django-sphinx==2.1.2
supercaptcha==0.1.1
-e svn+http://django-session-messages.googlecode.com/svn/trunk@5#egg=django-session-messages
http://github.com/sunlightlabs/django-blogdor/zipball/65adc834b3255e82ea7eb3efa484af49a7438f62
django-markupfield==0.3.0 #blogdor use
el-django-compress==1.0.3 # problems with installing original django-compress
sorl-thumbnail==3.2.5

# apps
--requirement=apps/facebook_login/libs.txt
--requirement=apps/twitter_login/libs.txt
--requirement=apps/payment/libs.txt&lt;/pre&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
 &lt;p&gt;Перешли с &lt;a href="http://code.google.com/p/deseb/"&gt;deseb&lt;/a&gt; на &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt;.&lt;/p&gt;
 &lt;p&gt;Фактически, deseb не работал у нас в команде, в самых простых случаях он помогал, но не более. Для применения изменений на боевом сервере я бы точно не стал его использовать.&lt;/p&gt;
 &lt;p&gt;На South я возлагаю большие надежды. Он использует другой подход, и этот подход действительно работает! После периода "привыкания" команды, можно сказать, что он нам подходит. Любой разработчик запускает &lt;code&gt;migrate&lt;/code&gt; и, о чудо, база данных станет актуальной. Точно так же можно без опаски запускать его на сервере.&lt;/p&gt;
 &lt;p&gt;Подробнее об этом расскажу в &lt;a href="http://alarin.blogspot.com/feeds/posts/default"&gt;следующем посте&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;27 марта в Санкт-Петербурге, я буду &lt;a href="http://www.zfconf.ru/2010/topics/history-of-e-shtab-ru/"&gt;выступать с докладом&lt;/a&gt; на конференции &lt;a href="http://www.zfconf.ru/"&gt;ZFConf 2010&lt;/a&gt;, посвященной PHP Zend Framework, приходите, кому интересно :)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Кластеризация списка (List clustering)&lt;/h2&gt;
&lt;p&gt;Сегодня я буду рассказывать всего лишь об одной строчке кода:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;zip(*[iter(a)]*3)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Если вы знаете как она работает, можете дальше не читать ;)&lt;/p&gt;

&lt;p&gt;Не смотря на внешнюю простоту, это очень интересная (и иногда полезная) конструкция Python. На собеседованиях мы просим объяснить как она работает, если человек справляется, это хороший знак.&lt;/p&gt;

&lt;p&gt;Полный пример работы кластеризации:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; a = [1,2,3,4,5,6,7,8,10]
&gt;&gt;&gt; zip(*[iter(a)]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 10)]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Разберем эту конструкцию по частям.&lt;/p&gt;

&lt;h3&gt;iter()&lt;/h3&gt;
&lt;p&gt;Возвращает объект-итератор для списка (кортежа или любого другого итерируемого элемента).&lt;/p&gt;
&lt;p&gt;Этот метод используется интерпретатором, когда список попадает, например, в цикл &lt;code&gt;for&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Сам объект итератор имеет метод &lt;code&gt;next()&lt;/code&gt;, который один за другим возвращает элементы списка.&lt;/p&gt;

&lt;h3&gt;[]*3&lt;/h3&gt;
&lt;p&gt;Операция умножения для списков формирует новый список, используя элементы начального заданное количество раз.&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; [1,2,3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Здесь важный момент: &lt;b&gt;все идентификаторы в Python — это ссылки&lt;/b&gt;. То есть конструкция &lt;code&gt;[iter(a)]*3&lt;/code&gt; даст нам список из трех &lt;i&gt;ссылок&lt;/i&gt; на один и тот же итератор списка. Небольшой пример:&lt;/p&gt; 
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; iters = [iter(a)]*3
&gt;&gt;&gt; print iters
[&amp;lt;listiterator object at 0x85db0&amp;gt;, &amp;lt;listiterator object at 0x85db0&amp;gt;, &amp;lt;listiterator object at 0x85db0&amp;gt;]
&gt;&gt;&gt; (iters[0].next(), iters[1].next(), iters[2].next())
(1, 2, 3)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;*&lt;/h3&gt;
&lt;p&gt;Превращает итерируемый объект в список неименованных аргументов функции.&lt;/p&gt;
&lt;p&gt;Одна из моих любимых возможностей Python. Следующие конструкции эквивалентны:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;my_func(*[1,2,3])
my_func(1,2,3)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;zip&lt;/h3&gt;
&lt;p&gt;zip возращает список кортежей, где i-тый кортеж состоит из i-тых элементов каждого из агрументов функции. Например:&lt;/p&gt;
&lt;pre&gt;&lt;code class="python"&gt;&gt;&gt;&gt; zip((1,2), (3,4), (4,5))
[(1, 3, 4), (2, 4, 5)]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;В официальной &lt;a href="http://docs.python.org/library/functions.html#zip"&gt;документации к функции zip&lt;/a&gt; описана последняя особенность, благодаря которой кластеризация работает.&lt;/p&gt;
&lt;p class="clip"&gt;&lt;i&gt;The left-to-right evaluation order of the iterables is guaranteed. This makes possible an idiom for clustering a data series into n-length groups using zip(*[iter(s)]*n).&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Как вы заметили, там содержится и рассматриваемая нами конструкция. Так что это не какой-нибудь &lt;i&gt;однострочник на Perl&lt;/i&gt;, а вполне официальная особенность. Пользуйтесь на здоровье.&lt;/p&gt;

&lt;p&gt;А какие интересные конструкции Python &lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=4727453758596579111"&gt;знаете&lt;/a&gt; вы?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-4727453758596579111?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/4727453758596579111/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=4727453758596579111' title='Комментарии: 8'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/4727453758596579111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/4727453758596579111'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2010/03/list-clusterization.html' title='Кластеризация списка (List clustering)'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-4249636470437430935</id><published>2009-06-07T22:34:00.007+04:00</published><updated>2010-03-09T12:35:05.676+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='тестирование'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='coverage'/><category scheme='http://www.blogger.com/atom/ns#' term='модульное тестирование'/><category scheme='http://www.blogger.com/atom/ns#' term='покрытие кода'/><category scheme='http://www.blogger.com/atom/ns#' term='unit test'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><title type='text'>Покрытометр (Django coverage)</title><content type='html'>&lt;p&gt;В рамках программы по улучшению качества кода начали писать на работе &lt;a href="http://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5"&gt;модульные тесты&lt;/a&gt;.&lt;/p&gt; 

&lt;p&gt;Они оказались достаточно полезными. Пока, конечно рано, говорить насколько увеличилось качество и предсказуемость кода. Но теперь я не боюсь делать рефакторинг, даже переписать целый модуль. Тесты дают мне уверенность, что я ничего не сломал :).&lt;/p&gt;

&lt;p&gt;В этой уверенности кроется одна проблема, я не знаю какая часть кода покрыта тестами. Так как нам еще далеко до &lt;a href="http://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5"&gt;TDD&lt;/a&gt;, тесты проверяют лишь некоторые функции.&lt;/p&gt;

&lt;p&gt;Если бы степень покрытия различных модулей системы была известна, мы бы могли ответить на два вопроса:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;С какой вероятностью рефакторинг не добавил ошибок.&lt;/li&gt;
 &lt;li&gt;Над тестами к каким модулям стоит поработать для более равномерного покрытия.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Сказано &amp;mdash; сделано. Для Python есть замечательная библиотека &lt;a href="http://nedbatchelder.com/code/modules/rees-coverage.html"&gt;coverage&lt;/a&gt;, которая умеет определять степень покрытия кода.&lt;/p&gt;

&lt;p&gt;Библиотеки такого рода работают достаточно просто, в исходных файлах подсчитывается количество строк кода. Затем библиотека запускает хук, который отслеживает, какие строчки кода были выполнены. В итоге степень покрытия это число выполненных во время тестов строк кода, поделенное на общее число строк кода.&lt;/p&gt;

&lt;p&gt;Итак, coverage умеет высчитывать степень покрытия кода и строить изумительные html-отчеты, осталось немного &amp;mdash; подружить ее с Django.&lt;/p&gt;

&lt;p&gt;Для себя я выбрал следующий вариант. Обертка для стандратного test runner, которая по окончании работы генерирует html-отчет. На мой взгляд, достаточно удобно. Одна команда:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./manage.py test&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;и после запуска тестов у нас уже есть красивые отчеты, которые можно показать коллегам или начальству&lt;/p&gt;

&lt;p&gt;Перейдем к самому интересному, к коду обертки, она требует совсем немного настроек в &lt;code&gt;settings.py&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#переопределиние стандартного test runner на наш
TEST_RUNNER = "core.test.coverage_runner.run_tests"
#путь к сгенерированным отчетам
COVERAGE_REPORT_PATH = os.path.join(workdir, 'coverage_report')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;и кода в файле &lt;code&gt;coverage_runner.py&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;""" 
Test runner with code coverage.
Falls back to django simple runner if coverage-lib not installed 
"""
import os

from django.test import simple
from django.conf import settings


#попытка импорта coverage библиотеки
try:
    from coverage import coverage as Coverage
except ImportError:
#если она не установлена, просто запустим стандартный обработчик
    run_tests = simple.run_tests 
else:
#если установлена примемся за построение отчета
    coverage = Coverage()
    def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):

# часть непосредственно отвечающая за получение данных о покрытии (включить сбор; выполнить тесты; выключить сбор)
        coverage.start()
        test_results = simple.run_tests(test_labels, verbosity, interactive, extra_tests)
        coverage.stop()

# Как известно, manage.py test может не все тесты, а только из определенных приложений
# немного не логично, что в этом случае покрытие будет определено для всего кода в проекте
# и для не ваших приложений и для библиотек.
# Следующий код берет имена приложений для тестирования из test_labels, определяет все модули
# входящие в их состав и передает список для построения отчета.
# Это очень удобно, мы тестируем только свой код и получаем только его покрытие.
        coverage_modules = []
        for app in test_labels:
            try:
                module = __import__(app, globals(), locals(), [''])
            except ImportError:
# Эта ситуация возникает в случае если тестирование ограничено одним TestCase, либо модуль недоступен
# В обоих случая нам не нужен отчет о покрытии.
                coverage_modules = None
                break
            if module:
                base_path = os.path.join(os.path.split(module.__file__)[0], "")
# Ищем внутри приложения непустые .py файлы
                for root, dirs, files in os.walk(base_path):
                    for fname in files:
                        if fname.endswith(".py") and os.path.getsize(os.path.join(root, fname)) &gt; 1:
                            try:
                                mname = os.path.join(app, os.path.join(root, fname).replace(base_path, "")) 
                                coverage_modules.append(mname)
                            except ImportError:
                                pass #do nothing
        
# Строим html-отчет        
        if coverage_modules or not test_labels:
            coverage.html_report(coverage_modules, directory=settings.COVERAGE_REPORT_PATH)
                    
        return test_results
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Для успешного использования coverage runner необходимо:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="http://media.djangoproject.com/releases/1.1/Django-1.1-beta-1.tar.gz"&gt;Django 1.1-beta&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="http://pypi.python.org/pypi/coverage/3.0b3"&gt;Coverage v3.0 beta 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;На выходе получаются красивые картинки. Общий отчет &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_cZBqZfv4DaY/Si0_bTineFI/AAAAAAAAAG8/I-qG0nP1kYw/s1600-h/coverage_indes+copy.png"&gt;&lt;img style="display:block;cursor:pointer; cursor:hand;width: 132px; height: 200px;" src="http://4.bp.blogspot.com/_cZBqZfv4DaY/Si0_bTineFI/AAAAAAAAAG8/I-qG0nP1kYw/s200/coverage_indes+copy.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5344998071093131346" /&gt;&lt;/a&gt;
, отчет покрытия одного файла &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_cZBqZfv4DaY/Si0_bSz9QGI/AAAAAAAAAG0/3XrLFV4RcYU/s1600-h/Coverage+for+core-decorators-1+copy.png"&gt;&lt;img style="display:block;cursor:pointer; cursor:hand;width: 153px; height: 200px;" src="http://2.bp.blogspot.com/_cZBqZfv4DaY/Si0_bSz9QGI/AAAAAAAAAG0/3XrLFV4RcYU/s200/Coverage+for+core-decorators-1+copy.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5344998070897426530" /&gt;&lt;/a&gt;.&lt;/p&gt;
 
&lt;p&gt;С радостью выслушаю замечания, мысли и истории "как у нас" по модульному тестированию и TDD &lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=4249636470437430935"&gt;в комментариях.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Еще предлагаю померяться &lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=4249636470437430935"&gt;в комментариях&lt;/a&gt; степенью покрытия кода. У &lt;a href="http://e-legion.com"&gt;нас&lt;/a&gt; &amp;mdash; &lt;b&gt;49%&lt;/b&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-4249636470437430935?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/4249636470437430935/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=4249636470437430935' title='Комментарии: 19'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/4249636470437430935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/4249636470437430935'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2009/06/django-coverage.html' title='Покрытометр (Django coverage)'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_cZBqZfv4DaY/Si0_bTineFI/AAAAAAAAAG8/I-qG0nP1kYw/s72-c/coverage_indes+copy.png' height='72' width='72'/><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-1815039723659711085</id><published>2009-05-25T21:41:00.005+04:00</published><updated>2010-03-09T12:51:48.032+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='request rate limit'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='closure'/><category scheme='http://www.blogger.com/atom/ns#' term='замыкание'/><category scheme='http://www.blogger.com/atom/ns#' term='декоратор'/><category scheme='http://www.blogger.com/atom/ns#' term='decorator'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><category scheme='http://www.blogger.com/atom/ns#' term='cell'/><title type='text'>Тайна замыкания</title><content type='html'>&lt;p&gt;Перед нами на работе встала задача, ограничить частоту публикации комментариев. Реализовать решили через декоратор для вьюшек.&lt;/p&gt;
&lt;p&gt;Другими словами нужно создать декоратор, не допускающий обращение к конкретной view, конкретным пользователем в течении определенного времени.&lt;/p&gt;

&lt;style&gt;
.clip {
 display: block;
 background: #EEE;
 border: 1px dashed #999;
 padding: 1em 1em 0 1em;
}
&lt;/style&gt;
&lt;div class="clip"&gt;&lt;h3&gt;Что такое &lt;a href="http://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)"&gt;декоратор&lt;/a&gt;?&lt;/h3&gt;
 &lt;p&gt;Декоратор &amp;mdash; обертка для функции. С помощью декоратора можно изменять поведение декорируемой функции, ее входные или выходные параметры.&lt;/p&gt;
 &lt;p&gt;Примеры декораторов из Django:&lt;/p&gt;
 &lt;dl&gt;
  &lt;dt&gt;login_required&lt;/dt&gt;&lt;dd&gt;при обращении анонимного пользователя к декорированому view, перенаправляет его на страницу логина.&lt;/dd&gt;
  &lt;dt&gt;transaction.commit_on_success&lt;/dt&gt;&lt;dd&gt;выполняет все запросы из декорируемой функции к БД в одной транзакции и коммитит ее при успешном завершении.&lt;/dd&gt;
  &lt;dt&gt;cache_page(sec)&lt;/dt&gt;&lt;dd&gt;кеширует результат выполнения view на sec секунд.&lt;/dd&gt;
 &lt;/dl&gt;
 
 &lt;p&gt;Два способа применения декоратора:&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;@login_required
def my_view(request):
 ...&lt;/code&gt;&lt;/pre&gt;
 &lt;pre&gt;&lt;code&gt;def my_view(request):
 ...
my_view = login_required(my_view) #python 2.3&lt;/pre&gt;&lt;/code&gt;
&lt;/div&gt;

&lt;p&gt;Получение id пользователя не вызывает проблем — &lt;code&gt;request.user&lt;/code&gt;. А вот с получением имени функции не так все просто. Вот шаблон декоратора:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
def decorator(function):
    def actual_decorator(request, *args, **kwargs):
        pass
    return actual_decorator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;То есть, мы имеем указатель на декорируемую функцию &lt;code&gt;function&lt;/code&gt;, и можно получить ее имя через &lt;code&gt;function.func_name&lt;/code&gt;. Но тут возникает проблема, к функции часто применяется последовательность декораторов, например:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
@limit_rate_request
@login_required
def my_view(request):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;В этой ситуации &lt;code&gt;function&lt;/code&gt; будет ссылаться на декоратор &lt;code&gt;login_required&lt;/code&gt; и имя декорируемой функции мы получить не можем.&lt;/p&gt;
&lt;div class="clip"&gt;&lt;h3&gt;Что такое &lt;a href="http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%BC%D1%8B%D0%BA%D0%B0%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)"&gt;замыкание&lt;/a&gt;?&lt;/h3&gt;
 &lt;p&gt;Это внутренняя функция, содержащая ссылки на локальные переменные внешней функции.&lt;/p&gt;
 &lt;p&gt;Например:&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;
def func1():
    closure = 1
    def func2():
        print closure
 &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;В этом примере переменная &lt;code&gt;closure&lt;/code&gt; замкнута в функции &lt;code&gt;func2&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Так как на &lt;code&gt;login_required&lt;/code&gt; замкнут указатель на декорируемую функцию, можно попробовать получить ее значение. У функции в питоне существует параметр &lt;code&gt;func_closure&lt;/code&gt; &amp;mdash; все замкнутые на функцию переменную. Но, к сожалению, их значения представлены классом &lt;code&gt;cell&lt;/code&gt; и получить значение напрямую нельзя.&lt;/p&gt;
&lt;p&gt;На помощь приходит &lt;a href="http://code.activestate.com/recipes/439096/"&gt;Решение&lt;/a&gt; :)

&lt;pre&gt;&lt;code&gt;
def get_cell_value(cell):
 # функция, которая возращает функцию, которая в свою очередь вернет замкнутую на нее переменную
    def make_closure_that_returns_value(use_this_value):
        def closure_that_returns_value():
            return use_this_value
        return closure_that_returns_value
    # получаем экземпляр функции с замыканием с параметром 0
    dummy_function = make_closure_that_returns_value(0)
    # получаем байт-код этой функции
    dummy_function_code = dummy_function.func_code
    # создаем новый экземпляр функции вместо старой замкнутой переменной используем наш cell
    our_function = new.function(dummy_function_code, {}, None, None, (cell,))
    # вызываем функцию, и она возвращает значение нашей замкнутой переменной из cell
    value_from_cell = our_function()
    return value_from_cell 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Далее дело за малым — создать декоратор. Для хранения времени последнего вызова достаточно удобно использовать кеш, установив время его жизни равным времени ограничения на запуск функции.&lt;/p&gt;
&lt;p&gt;Таким образом, после первого запроса View в кеше будет установлена переменная PREFIX_USER_ID_FUNCTION_NAME, пока повторный запуск запрещен эта переменная хранится в кеше, как только таймаут вышел, кеш &amp;laquo;протухает&amp;raquo; и пользователь снова может сделать запрос.&lt;/p&gt;

&lt;p&gt;Полный код декоратора:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
def get_cell_value(cell):
    def make_closure_that_returns_value(use_this_value):
        def closure_that_returns_value():
            return use_this_value
        return closure_that_returns_value
    dummy_function = make_closure_that_returns_value(0)
    dummy_function_code = dummy_function.func_code
    our_function = types.FunctionType(dummy_function_code, {}, None, None, (cell,))    
    value_from_cell = our_function()
    return value_from_cell


def get_decorated_function(function):
    """ Returns actual decorated function in decorators stack """
    while function.func_closure is not None:
        function = get_cell_value(function.func_closure[0])
    return function
 
class limit_request_rate(object):
    """
    Decorator for view that limit request rate for view for concrete user.
    Anonymous users not limited. 
    @todo: differentiate anonymous users by IP
    """    
    CACHE_VAR_PREFIX = "limit_request_rate_"
    
    def __init__(self, timeout = None):
        self.timeout = timeout or settings.DEFAULT_REQUEST_TIMEOUT
    
    def __call__(self, function):        
        def actual_decorator(request, *args, **kwargs):
            if request.user.is_authenticated():                
                dec_func = get_decorated_function(function)
                cache_key = self.CACHE_VAR_PREFIX + str(request.user.id) + dec_func
.func_name + dec_func.func_code.co_filename
                if not cache.get(cache_key):
                    cache.set(cache_key, 1, self.timeout)
                    return function(request, *args, **kwargs)
                else:
                    return HttpResponseServiceUnavailable()            
            return function(request, *args, **kwargs)
        return actual_decorator
&lt;/pre&gt;&lt;/code&gt;
&lt;p&gt;up: deprecated new.function заменен на Types.FunctionType&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=1815039723659711085"&gt;Оставьте комментарий&lt;/a&gt;, если вам есть что добавить, вы нашли ошибку в коде или что-то осталось непонятным.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-1815039723659711085?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/1815039723659711085/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=1815039723659711085' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1815039723659711085'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/1815039723659711085'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2009/05/blog-post.html' title='Тайна замыкания'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-276364153935073640</id><published>2009-03-24T08:37:00.007+03:00</published><updated>2010-03-09T12:53:43.793+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='deploy'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='nginx'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='ru'/><category scheme='http://www.blogger.com/atom/ns#' term='sphinx'/><title type='text'>Система развертывания (deploy system)</title><content type='html'>&lt;style&gt;
.clip {
 display: block;
 background: #EEE;
 border: 1px dashed #999;
 padding: 1em 1em 0 1em;
}
&lt;/style&gt;
&lt;p&gt;Как я&amp;nbsp;и&amp;nbsp;обещал в&amp;nbsp;&lt;a href="http://alarin.blogspot.com/2009/02/deseb.html"&gt;предыдущей серии&lt;/a&gt;, посвящаю этот пост &lt;a href="http://e-legion.com"&gt;нашей&lt;/a&gt; системе развертывания.&lt;/p&gt;
&lt;p&gt;Система самописная, достаточно тесно связана с&amp;nbsp;процессом разработки и&amp;nbsp;реализована при помощи &lt;a href="http://ru.wikipedia.org/wiki/Sh"&gt;&lt;nobr&gt;sh-скрипта&lt;/nobr&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="common"&gt;Общая схема&lt;/h2&gt;
&lt;p&gt;На&amp;nbsp;рисунке приведена немного сумбурная схема процесса развертывания.&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_cZBqZfv4DaY/SchyGU7ZNII/AAAAAAAAAGU/-VaPdsZB-E0/s1600-h/deploy_system.png"&gt;&lt;img style="margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 400px; height: 283px;" src="http://1.bp.blogspot.com/_cZBqZfv4DaY/SchyGU7ZNII/AAAAAAAAAGU/-VaPdsZB-E0/s400/deploy_system.png" border="0" alt="Система развертывания, deploy, django, sh, mysql, python."id="BLOGGER_PHOTO_ID_5316624813133476994" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Разработка ведется с&amp;nbsp;использованием SVN установленным на&amp;nbsp;локальном сервере, там&amp;nbsp;же расположен и&amp;nbsp;локальный сервер MySQL. MySQL используется всеми при разработке, структура создается через initdb. Начальные данные в&amp;nbsp;обязательном порядке (&lt;a href="#mysql"&gt;скоро&lt;/a&gt; узнаете почему) сохраняются либо в&amp;nbsp;&lt;a href="http://docs.djangoproject.com/en/dev/howto/initial-data/#providing-initial-data-with-fixtures"&gt;fixtures&lt;/a&gt;, либо в&amp;nbsp;init.sql, а&amp;nbsp;в&amp;nbsp;случае прав пользователей создается &lt;a href="http://docs.djangoproject.com/en/dev/howto/custom-management-commands/#howto-custom-management-commands"&gt;команда manage.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;После каждого коммита &lt;a href="http://svnbook.red-bean.com/en/1.1/ch05s02.html#svn-ch-5-sect-2.1" title="svn hook scripts"&gt;автоматически&lt;/a&gt; запускается скрипт развертывания на&amp;nbsp;&lt;i&gt;DEV&lt;/i&gt; версию проекта. При этом исходный код на&amp;nbsp;сервере обновляется (или загружается если проекта не&amp;nbsp;существует), а&amp;nbsp;структура базы обновляется при помощи замечательного &lt;code&gt;&lt;a href="http://adamspiers.org/computing/mysqldiff/"&gt;mysqldiff&lt;/a&gt;&lt;/code&gt;. На&amp;nbsp;схеме этот процесс помечен цифрой &lt;b&gt;1&lt;/b&gt;.&lt;/p&gt;
&lt;div class="clip"&gt;&lt;p&gt;&lt;b&gt;DEV версия проекта&lt;/b&gt;&amp;nbsp;&amp;mdash; инструмент разработчиков, расположен на&amp;nbsp;хостинге проекта, но&amp;nbsp;закрыт для просмотра не&amp;nbsp;из&amp;nbsp;локальной сети. Обновляется после каждой загрузки кода в&amp;nbsp;репозиторий.&lt;/p&gt;&lt;p&gt;Используется редко, в&amp;nbsp;основном для проверки ошибок возникающих &lt;nobr&gt;из-за&lt;/nobr&gt; разных версий ПО&amp;nbsp;и&amp;nbsp;ОС&amp;nbsp;на&amp;nbsp;компьютерах разработчиков и&amp;nbsp;хостинге.&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;Когда проект готов для тестирования, менеджер проекта запускает (по&amp;nbsp;ssh;) развертывание на&amp;nbsp;&lt;i&gt;TEST&lt;/i&gt; он&amp;nbsp;же помечен цифрой &lt;b&gt;2&lt;/b&gt; на&amp;nbsp;схеме. Структура база данных переносится из&amp;nbsp;DEV версии.&lt;/p&gt;
&lt;div class="clip"&gt;&lt;p&gt;&lt;b&gt;TEST версия проекта&lt;/b&gt;&amp;nbsp;&amp;mdash; инструмент тестировщиков, как и&amp;nbsp;DEV расположен на&amp;nbsp;хостинге проекта, но&amp;nbsp;закрыт для просмотра не&amp;nbsp;из&amp;nbsp;локальной сети. Обновляется по&amp;nbsp;запросу, когда система готова к&amp;nbsp;тестированию.&lt;/p&gt;&lt;p&gt;Практически все тестирование проводится на&amp;nbsp;этой версии, так как обновляется она только по&amp;nbsp;запросу, разработчики спокойно продолжают работать над системой во&amp;nbsp;время тестирования.&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;На&amp;nbsp;этот раз файлы проекта не&amp;nbsp;обновляются из&amp;nbsp;репозитория, а&amp;nbsp;копируются из&amp;nbsp;DEV версии. Старая версия проекта удаляется. В&amp;nbsp;сложной ситуации это дает больше свободы, и&amp;nbsp;базу данных и&amp;nbsp;файлы проекта мы&amp;nbsp;можем подкорректировать на&amp;nbsp;хостинге и&amp;nbsp;эти изменения попадут в&amp;nbsp;тестирование.&lt;/p&gt;
&lt;p&gt;Процесс деплоя на&amp;nbsp;TEST выполняется итеративно, пока не&amp;nbsp;будут исправлены все обнаруженные ошибки. И&amp;nbsp;тогда наступает час X, менеджер проекта достает большую красную кнопку и&amp;nbsp;запускает развертывание на&amp;nbsp;PROD версию.&lt;/p&gt;
&lt;div class="clip"&gt;&lt;p&gt;&lt;b&gt;PROD версия проекта&lt;/b&gt;&amp;nbsp;&amp;mdash; инструмент пользователей. Используется интернет пользователями по&amp;nbsp;назначению. (например &lt;a href="http://e-shtab.ru"&gt;для связи бизнеса и&amp;nbsp;СМИ&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Обновляется по&amp;nbsp;запросу, после окончания тестирования.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Публичная версия обновляется исключительно с&amp;nbsp;TEST версии, копированием файлов и&amp;nbsp;структуры базы. Это с&amp;nbsp;одной стороны дает гарантию что мы&amp;nbsp;запустим именно тот код который был оттестирован, с&amp;nbsp;другой подразумевает что любые изменения должны быть проверены (задеплоиться с&amp;nbsp;DEV или репозитория нельзя).&lt;/p&gt;
&lt;p&gt;Расскажу об&amp;nbsp;одном интересном моменте, почему при деплое не&amp;nbsp;теряются пользовательские данные. Дело в&amp;nbsp;том, что mysqldiff генерирует ALTER срипт, который изменяет лишь структуру &lt;acronym title="База данных" lang="ru"&gt;БД&lt;/acronym&gt;, но&amp;nbsp;не&amp;nbsp;трогает сами данные. Файлы загруженные пользователями хранятся в&amp;nbsp;отдельном месте и&amp;nbsp;подключаются в&amp;nbsp;структуру проекта с&amp;nbsp;помощью символической ссылки. Таким образом мы&amp;nbsp;имеем три независимых версии со&amp;nbsp;своими данными в&amp;nbsp;базе и&amp;nbsp;загруженными файлами.&lt;/p&gt;
&lt;h2&gt;Конфигурация системы&lt;/h2&gt;
&lt;p&gt;Система планировалась &lt;nobr&gt;более-менее&lt;/nobr&gt; универсальной, поэтому у&amp;nbsp;каждого проекта есть опции развертывания, которые должны храниться в&amp;nbsp;файле &lt;code&gt;deploy/params.sh&lt;/code&gt;. Основные параметры:&lt;/p&gt;
&lt;ul&gt; &lt;li&gt;Параметры SVN репозитория, для скачивания и&amp;nbsp;обновления исходного кода.&lt;/li&gt; &lt;li&gt;Параметры MySQL сервера, который используется для переноса изменений структуры на&amp;nbsp;DEV.&lt;/li&gt; &lt;li&gt;Имя конечного конфигурационного файла, который &lt;a href="#config"&gt;формируется&lt;/a&gt; из&amp;nbsp;шаблона для каждой версии проекта.&lt;/li&gt; &lt;li&gt;Путь к&amp;nbsp;media и&amp;nbsp;загружаемым файлам пользователя.&lt;/li&gt; &lt;li&gt;Базовые порты FCGI и&amp;nbsp;SPHINX.&lt;/li&gt; &lt;li&gt;Команда выполняемая сразу после деплоя (обычно команда инициализации прав пользователей).&lt;/li&gt;
&lt;/ul&gt;
Параметры представлены как переменные и&amp;nbsp;добавляются с&amp;nbsp;помощью включения скрипта.
&lt;h2 id="mysql"&gt;MySQL&lt;/h2&gt;
&lt;p&gt;С&amp;nbsp;базой при деплое все достаточно просто, если её&amp;nbsp;не&amp;nbsp;существует, скрипт запускает создание базы &lt;code&gt;manage.py initdb&lt;/code&gt;, Django загружает fixtures, скрипт запускает &lt;code&gt;init.sql&lt;/code&gt; и&amp;nbsp;в&amp;nbsp;конце выполняет прописанную в&amp;nbsp;конфиге команду. Огромное разнообразие методов начальной загрузки данных удовлетворяет любые даже самые сложные случаи.&lt;/p&gt;
&lt;p&gt;Например, в&amp;nbsp;свое время мы&amp;nbsp;столкнулись со&amp;nbsp;сложностью распределения прав пользователей по&amp;nbsp;группам. На&amp;nbsp;уровне fixtures или init.sql ну&amp;nbsp;никак не&amp;nbsp;получалось этого сделать, &lt;nobr&gt;из-за&lt;/nobr&gt; того что на&amp;nbsp;разных версиях проекта, созданные initdb права, имеют разные id. В&amp;nbsp;итоге и&amp;nbsp;появилась команда выполняемая в&amp;nbsp;конце развертывания, которая используя стандартные методы Django, формирует необходимые группы пользоватей и&amp;nbsp;распределяет права доступа.&lt;/p&gt;
&lt;p&gt;Все эти сложности с&amp;nbsp;начальными данными очень помогают в&amp;nbsp;процессе разработки и&amp;nbsp;при первом деплое на&amp;nbsp;продакшн. Если &lt;nobr&gt;что-то&lt;/nobr&gt; пошло не&amp;nbsp;так, база сбрасывается и&amp;nbsp;автоматически наполняется нужными данными. Очень удобно.&lt;/p&gt;
&lt;p&gt;Если база данных уже создана, то&amp;nbsp;при развертывании переносится только структура с&amp;nbsp;помощью mysqldiff. Этот скрипт делает экспорт структуры двух баз данных (откуда и&amp;nbsp;куда переносим), сравнивает эти данные и&amp;nbsp;формирует sql скрипт для обновления целевой базы. Скрипт уже стандартными средствами СУБД применяется к&amp;nbsp;базе. Причем при деплое на&amp;nbsp;PROD, скрипт просматривается вручную, дабы в&amp;nbsp;случае сбоя не&amp;nbsp;удалить пользовательские данные.&lt;/p&gt;
&lt;h2 id="config"&gt;Конфигурационные файлы&lt;/h2&gt;
&lt;p&gt;Одна из&amp;nbsp;моих любимых функций нашей системы&amp;nbsp;&amp;mdash; генерация конфигурационных файлов.&lt;/p&gt;
&lt;p&gt;Конфиг разработчиков хранится в&amp;nbsp;отдельном файле &lt;code&gt;base_settings.py&lt;/code&gt; а&amp;nbsp;в&amp;nbsp;стандартном &lt;code&gt;settings.py&lt;/code&gt; всего одна строчка включения базовых настроек &lt;code&gt;from base_settings.py import *&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Для настроек применяемых на&amp;nbsp;хостинге создается специальный шаблон настроек &lt;code&gt;template_settings.py&lt;/code&gt; в&amp;nbsp;котором переопределяются нужные настройки из&amp;nbsp;&lt;code&gt;base_settings.py&lt;/code&gt;, причем следуя принципу &lt;a href="http://docs.djangoproject.com/en/dev/misc/design-philosophies/#don-t-repeat-yourself-dry"&gt;DRY&lt;/a&gt; шаблонный файл всего один для всех трех версий системы.&lt;/p&gt;
&lt;p&gt;Поведение специфичное для конкретной версии (имена баз данных, например, у&amp;nbsp;всех разные) поддерживается &lt;nobr&gt;макро-язык&lt;/nobr&gt; суть которого в&amp;nbsp;замене конструкции &lt;code&gt;{имя версии: значение}&lt;/code&gt; на&amp;nbsp;&lt;code&gt;значение&lt;/code&gt;. Допустим строка &lt;pre&gt;&lt;code&gt;DATABASE_NAME={dev:eshtab_dev}{test:eshtab_test}{prod:eshtab}&lt;/code&gt;&lt;/pre&gt; при развертывании на&amp;nbsp;TEST будет исправлена на&amp;nbsp;DATABASE_NAME = eshtab_test.&lt;/p&gt;
&lt;p&gt;Но&amp;nbsp;с&amp;nbsp;другой стороны мы&amp;nbsp;уже указали имя базы данных в&amp;nbsp;&lt;code&gt;params.sh&lt;/code&gt;, воскликнет внимательный читатель. Для решения этой проблемы активны специальные подстановки, вот примерный их&amp;nbsp;список:&lt;/p&gt;
&lt;dl&gt; &lt;dt&gt;{instance}&lt;/dt&gt;&lt;dd&gt;DEV, TEST или PROD&amp;nbsp;&amp;mdash; версия системы.&lt;/dd&gt; &lt;dt&gt;{dbname}&lt;/dt&gt;&lt;dd&gt;имя базы данных.&lt;/dd&gt; &lt;dt&gt;{sitepath}&lt;/dt&gt;&lt;dd&gt;путь к&amp;nbsp;файлам проекта.&lt;/dd&gt; &lt;dt&gt;{domain}&lt;/dt&gt;&lt;dd&gt;базовый адрес версии проектам (например dev.eshtab.ru).&lt;/dd&gt; &lt;dt&gt;{fcgi_port}&lt;/dt&gt;&lt;dd&gt;FCGI порт, используется как Django так и&amp;nbsp;NGINX.&lt;/dd&gt; &lt;dt&gt;{sphinx_port}&lt;/dt&gt;&lt;dd&gt;порт поискового движка, используемого на&amp;nbsp;наших проектах.&lt;/dd&gt; &lt;dt&gt;{media_path}&lt;/dt&gt;&lt;dd&gt;путь к&amp;nbsp;статическим файлам.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Подстановки изумительны с&amp;nbsp;точки зрения DRY, они используются как в&amp;nbsp;Django так и&amp;nbsp;в&amp;nbsp;конфигурационных файлах SPHINX, NGINX, так и&amp;nbsp;в&amp;nbsp;скриптах запуска.&lt;/p&gt;
&lt;p&gt;&lt;nobr&gt;Макро-замены&lt;/nobr&gt; и&amp;nbsp;подстановки осуществляются потоковым текстовым редактором &lt;code&gt;sed&lt;/code&gt;. После замен &lt;code&gt;settings.py&lt;/code&gt; заменяется шаблоном.&lt;/p&gt;
&lt;h2 id="nginx"&gt;SPHINX, NGINX&lt;/h2&gt;
&lt;p&gt;Может быть это и&amp;nbsp;не&amp;nbsp;очень правильно, но&amp;nbsp;конфигурационные файлы &lt;nobr&gt;web-сервера&lt;/nobr&gt;, и&amp;nbsp;поискового движка, также хранятся в&amp;nbsp;репозитории и&amp;nbsp;правятся программистами. С&amp;nbsp;одной стороны возрастает ответственность, с&amp;nbsp;другой стороны мы&amp;nbsp;получаем больше контроля над системой.&lt;/p&gt;
&lt;p&gt;Плюс, благодаря заменам и&amp;nbsp;подстановке, на&amp;nbsp;все версии системы по&amp;nbsp;одному файлу конфигурации. Если их&amp;nbsp;было&amp;nbsp;бы по&amp;nbsp;одному на&amp;nbsp;каждую, правки доставляли некоторые неудобства. Вот небольшая часть конфигурационного файла &lt;nobr&gt;веб-сервера&lt;/nobr&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{dev:#}{test:#}    server {
{dev:#}{test:#}        listen       80;
{dev:#}{test:#}        server_name .{domain};
{dev:#}{test:#}        rewrite ^/(.*) http://www.{domain}/$1 permanent;
{dev:#}{test:#}    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Как вы&amp;nbsp;наверно догадались на&amp;nbsp;DEV и&amp;nbsp;TEST эти строки будут закомментированы, а&amp;nbsp;на&amp;nbsp;PROD &lt;nobr&gt;веб-сервер&lt;/nobr&gt; будет переадресовывать запросы с&amp;nbsp;domain на&amp;nbsp;www.domain из&amp;nbsp;эстетических соображений.&lt;/p&gt;
&lt;h2&gt;На&amp;nbsp;правах заключения&lt;/h2&gt;
&lt;p&gt;Так работает наша замечательная система развертывания, если у&amp;nbsp;вас остались &lt;nobr&gt;какие-нибудь&lt;/nobr&gt; &lt;b&gt;вопросы&lt;/b&gt; по&amp;nbsp;ней, задавайте их&amp;nbsp;в&amp;nbsp;&lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=276364153935073640"&gt;комментариях&lt;/a&gt;, с&amp;nbsp;удовольствием отвечу. Особо мне будут интересны замечания и&amp;nbsp;предложения.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-276364153935073640?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/276364153935073640/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=276364153935073640' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/276364153935073640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/276364153935073640'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2009/03/deploy-system.html' title='Система развертывания (deploy system)'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_cZBqZfv4DaY/SchyGU7ZNII/AAAAAAAAAGU/-VaPdsZB-E0/s72-c/deploy_system.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-7502911207339152017</id><published>2009-02-08T16:49:00.004+03:00</published><updated>2009-02-08T17:19:36.454+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='evolvedb'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='deseb'/><title type='text'>Полезные штуки: deseb</title><content type='html'>&lt;p&gt;&lt;i&gt;Здравствуйте, мои дорогие друзья.&lt;/i&gt;&lt;/p&gt;

Что-то я давно не писал, хотя с последнего раза много интересного произошло:
&lt;ul&gt;
 &lt;li&gt;Вышел &lt;a href="http://docs.djangoproject.com/en/dev/releases/1.0/"&gt;Django 1.0&lt;/a&gt;, и даже &lt;a href="http://docs.djangoproject.com/en/dev/releases/1.0.1/"&gt;Django 1.0.1&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;На работе &lt;a href="http://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BB%D0%B0_(%D0%97%D0%B2%D1%91%D0%B7%D0%B4%D0%BD%D1%8B%D0%B5_%D0%B2%D0%BE%D0%B9%D0%BD%D1%8B)#.D0.A1.D0.B2.D0.B5.D1.82.D0.BB.D0.B0.D1.8F_.D1.81.D1.82.D0.BE.D1.80.D0.BE.D0.BD.D0.B0"&gt;светлая сторона силы&lt;/a&gt; взяла свое и мы написали &lt;a href="http://refobr.ru"&gt;два&lt;/a&gt; &lt;a href="http://baesdo.ru"&gt;проекта&lt;/a&gt; на Django&lt;/li&gt; для министерства образования. К тому же используя &lt;a href="http://www.citforum.ru/SE/project/scrum/"&gt;scrum&lt;/a&gt;, который сам по себе заслужил отдельного поста. На подходе еще один проект для &lt;a href="http://rbtour.ru"&gt;РосБизнесТура&lt;/a&gt;.&lt;/ul&gt;
 
 &lt;p&gt;Сегодня я расскажу о замечательной утилите для Django &amp;mdash; &lt;a href="http://code.google.com/p/deseb/"&gt;deseb&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Deseb добавляет к &lt;code&gt;manage.py&lt;/code&gt; всего одну команду &lt;code&gt;evolvedb&lt;/code&gt;, эта команда позволяет сгенерировать новую схему базы данных из моделей не теряя данных уже находящихся в базе&lt;/p&gt;

&lt;p&gt;Django изначально не умеет этого делать и единственным вариантом изменения схемы является команда &lt;code&gt;resetdb&lt;/code&gt;, которая удаляет все таблицы приложения и создает их заново, что согласитесь не очень удобно.&lt;/p&gt;

&lt;p&gt;Рассмотрим пример использования команды &lt;code&gt;evolvedb&lt;/code&gt;. Для начала нужно установить deseb:&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;Перейдем в директорию с проектом &lt;code&gt;coblog&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;Получим исходные коды deseb
  &lt;pre&gt;&lt;code&gt;svn checkout http://deseb.googlecode.com/svn/trunk/src/deseb&lt;/code&gt;&lt;/pre&gt;
 &lt;/li&gt;
 &lt;li&gt;И добавим &lt;code&gt;import deseb&lt;/code&gt; в начало файла &lt;code&gt;setting.py&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Для примера исправим одну из ошибок в нашем проекте, непозволяющую создавать комментарии без &lt;code&gt;parent&lt;/code&gt; и еще пару мелочей. В файле &lt;code&gt;default/models.py&lt;/code&gt; заменим&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;posted = models.DateTimeField()&lt;/code&gt;&lt;/pre&gt;
 в модели &lt;code&gt;Post&lt;/code&gt; на 
 &lt;pre&gt;&lt;code&gt;posted = models.DateTimeField(blank=True, null=True)&lt;/code&gt;&lt;/pre&gt;
 
    В модели &lt;code&gt;Comment&lt;/code&gt; строчку
 &lt;pre&gt;&lt;code&gt;parent = models.ForeignKey('self')&lt;/code&gt;&lt;/pre&gt;
 на
 &lt;pre&gt;&lt;code&gt;parent = models.ForeignKey('self', null=True, blank=True)&lt;/code&gt;&lt;/pre&gt;
 
 И строчку
 &lt;pre&gt;&lt;code&gt;__rate = models.IntegerField()&lt;/code&gt;&lt;/pre&gt;
 в той же модели на 
 &lt;pre&gt;&lt;code&gt;__rate = models.IntegerField(default=0)&lt;/code&gt;&lt;/pre&gt;
 
&lt;p&gt;Теперь для изменения базы данных и пригодится команда &lt;code&gt;evolvedb&lt;/code&gt;. Запустим ее:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python manage.py evolvedb&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;#133; ничего не происходит. Оказалось, что &lt;code&gt;evolvedb&lt;/code&gt; не срабатывает на изменение параметров &lt;a href="http://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9_%D0%BA%D0%BB%D1%8E%D1%87"&gt;внешних ключей&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Придется немного доработать &lt;code&gt;deseb&lt;/code&gt; для того чтобы он воспринимал эти изменения. В файле &lt;code&gt;deseb/schema_evolution.py&lt;/code&gt; заменим строчку&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if data_types.has_key(data_type):&lt;/code&gt;&lt;/pre&gt;
на строчку
&lt;pre&gt;&lt;code&gt;if not f.db_type() is None:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;и чудесным образом &lt;code&gt;evolvedb&lt;/code&gt; предложит внести изменения в базу.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Я не уверен, что внесенные изменения &lt;code&gt;deseb&lt;/code&gt; оправданы и ничего не сломается при работе с другими БД, кто может что сказать по этому поводу?&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Вот такой полезный иструмент &lt;code&gt;deseb&lt;/code&gt;, надеюсь он будет вам полезен.&lt;/p&gt;

&lt;h2&gt;Беспрецедентная акция&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=7502911207339152017"&gt;&lt;b&gt;Оставьте комментарий&lt;/b&gt;&lt;/a&gt; о теме которую вы бы хотели узнать в следующий раз. И я напишу о ней.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-7502911207339152017?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/7502911207339152017/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=7502911207339152017' title='Комментарии: 10'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/7502911207339152017'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/7502911207339152017'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2009/02/deseb.html' title='Полезные штуки: deseb'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-6825761901096786052</id><published>2008-07-20T19:30:00.014+04:00</published><updated>2008-07-20T22:58:33.597+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crud'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='админка'/><category scheme='http://www.blogger.com/atom/ns#' term='newforms-admin'/><title type='text'>Админка</title><content type='html'>Не успел я написать про административный интерфейс в Django, как произошло &lt;a href="http://webnewage.org/post/2008/7/19/u-adminki-novyie-formyi-/"&gt;радостное&lt;/a&gt; &lt;a href="http://groups.google.com/group/django-developers/browse_thread/thread/53ace41d27dfa7d9#"&gt;событие&lt;/a&gt; — он сильно изменился, став еще лучше. Поэтому не вижу смысла описывать старую функциональность и расскажу про новый вариант.

Для использования newforms-admin необходимо:
&lt;ol&gt;
&lt;li&gt;Обновить Django, выполнив следующие команды в директории с рабочей копией (полученной при помощи svn checkout).
&lt;pre&gt;&lt;code highlight="no-highlight"&gt;svn up
python setup.py install&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Удалить все блоки &lt;i&gt;class Admin: pass&lt;/i&gt; из файла &lt;i&gt;default/models.py&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Что это&lt;/h2&gt;
Админка — это автоматически генерируемое средство для управления моделями вашего приложения. Она обладает большими возможностями по настройке под нужды конкретного приложения. В нашем случаем мы могли бы использовать админку для:
&lt;ul&gt;
&lt;li&gt;Модерации постов и комментариев&lt;/li&gt;
&lt;li&gt;Создании и удалении пользователей, причем при удалении автоматически удаляются все зависимые от пользователя объекты: профайл, сообщения и комментарии.&lt;/li&gt;
&lt;li&gt;Блокирования пользователей&lt;/li&gt;
&lt;li&gt;Просмотра статистики зарегистрированных пользователей, количества постов и комментариев.&lt;/li&gt;
&lt;/ul&gt;
Административный интерфейс в Django не только является инструментом разработчика, но и полностью готово для использования при эксплуатации сайтов. В своем php-проекте на работе нам пришлось писать админку самим, причем почти все существующие и планируемые функции повторяют функциональность Django-админки.



&lt;h2&gt;Объекты ModelAdmin&lt;/h2&gt;
Админка описывается в файле admin.py при помощи специальных объектов ModelAdmin, различные поля этих объектов позволяют управлять функциональностью.

Например создадим ModelAdmin для записи в блоге:
&lt;pre&gt;&lt;code highlight="python"&gt;class PostAdmin(admin.ModelAdmin):
    pass
admin.site.register(Post, PostAdmin)&lt;/code&gt;&lt;/pre&gt;
последняя строка говорит Django о том, что мы хотим использовать класс PostAdmin для описания админки класса Post.

Сгенерированный интерфейс будет выглядеть так:
&lt;a style="display:block" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_cZBqZfv4DaY/SINw-bjg0-I/AAAAAAAAACA/b5Tn3TqQ-Kg/s1600-h/%D0%92%D1%8B%D0%B1%D0%B5%D1%80%D0%B8%D1%82%D0%B5+post+%D0%B4%D0%BB%D1%8F+%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216573624759.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; cursor: pointer;" src="http://bp2.blogger.com/_cZBqZfv4DaY/SINw-bjg0-I/AAAAAAAAACA/b5Tn3TqQ-Kg/s320/%D0%92%D1%8B%D0%B1%D0%B5%D1%80%D0%B8%D1%82%D0%B5+post+%D0%B4%D0%BB%D1%8F+%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216573624759.png" alt="" id="BLOGGER_PHOTO_ID_5225144210530816994" border="0" /&gt;&lt;/a&gt;

&lt;a style="display:block" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_cZBqZfv4DaY/SINw-k27PnI/AAAAAAAAACI/wtGva1rR-Uw/s1600-h/%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C+post+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216573634185.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; cursor: pointer;" src="http://bp2.blogger.com/_cZBqZfv4DaY/SINw-k27PnI/AAAAAAAAACI/wtGva1rR-Uw/s320/%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C+post+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216573634185.png" alt="" id="BLOGGER_PHOTO_ID_5225144213028159090" border="0" /&gt;&lt;/a&gt;

Немного приукрасим его, сгрупировав поля:
&lt;pre&gt;&lt;code highlight="python"&gt;class PostAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': ('blog', 'poster', 'posted')
        }),
        ('Содержимое', {
            'fields': ('draft', 'caption', 'content')
        })
    )&lt;/code&gt;&lt;/pre&gt;
&lt;a  style="display:block" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_cZBqZfv4DaY/SIN0Ogt06KI/AAAAAAAAACQ/21kopXA-tYc/s1600-h/%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C+post+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216574483756.png"&gt;&lt;img style="cursor: pointer;" src="http://bp3.blogger.com/_cZBqZfv4DaY/SIN0Ogt06KI/AAAAAAAAACQ/21kopXA-tYc/s320/%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C+post+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216574483756.png" alt="" id="BLOGGER_PHOTO_ID_5225147785329043618" border="0" /&gt;&lt;/a&gt;

Для тэгов к записи можно было объявить такие же объекты, но они появлялись бы в отдельном списке и редактировались по одному, что, согласитесь, не очень удобно. Встроим редактирование тегов в редактирование поста, при помощи специального класса InlineModelAdmin:
&lt;pre&gt;&lt;code highlight="python"&gt;class TagInline(admin.TabularInline):
    model = Tag

class PostAdmin(admin.ModelAdmin):
    ...
    inlines = [
        TagInline,
    ]&lt;/code&gt;&lt;/pre&gt;
Вот такой блок добавится к редактированию поста в результате:
&lt;a  style="display:block" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_cZBqZfv4DaY/SIN5b6K7meI/AAAAAAAAACY/Ir9vHQtvSlc/s1600-h/%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C+post+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216575797629.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; cursor: pointer;" src="http://bp3.blogger.com/_cZBqZfv4DaY/SIN5b6K7meI/AAAAAAAAACY/Ir9vHQtvSlc/s320/%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C+post+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216575797629.png" alt="" id="BLOGGER_PHOTO_ID_5225153513058441698" border="0" /&gt;&lt;/a&gt;


Файл &lt;i&gt;admin.py&lt;/i&gt; для всех моделей:
&lt;pre&gt;&lt;code highlight="python"&gt;from coblog.default.models import *
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from django.contrib import admin

class BlogAdmin(admin.ModelAdmin):
    pass

class TagInline(admin.TabularInline):
    model = Tag

class CommentInline(admin.StackedInline):
    model = Comment
    fieldsets = (
        (None, {
            'fields': (('user', 'parent'), 'content'),
            'classes': ['collapse']
        }),
    )


class PostAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': ('blog', 'poster', 'posted')
        }),
        ('Содержимое', {
           'fields': (('caption','draft'), 'content')
        })
    )
    inlines = [
       TagInline,
       CommentInline
    ]

class ProfileInline(admin.StackedInline):
    model = Profile
    extra = 0
    max_num = 1

#Расширим стандартный интерфейс редактирования пользователя нашим профайлом
class MyUserAdmin(UserAdmin):
    inlines = [
        ProfileInline
    ]

#перерегистрируем админку для пользователя
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)

admin.site.register(Blog, BlogAdmin)
admin.site.register(Post, PostAdmin)&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;Подлючение&lt;/h2&gt;
Для активации админки исправим &lt;i&gt;urls.py&lt;/i&gt;:
&lt;pre&gt;&lt;code highlight="python"&gt;from django.conf.urls.defaults import *
from django.contrib import admin

#автоматическое подключение необходимых приложений
admin.autodiscover()

urlpatterns = patterns('',
  # Example:
  # (r'^coblog/', include('coblog.foo.urls')),

  # Uncomment this for admin:
  ('^admin/(.*)', admin.site.root),
)&lt;/code&gt;&lt;/pre&gt;

Можете запустить сервер и посмотреть на результат:
&lt;pre&gt;&lt;code highlight="no-highlight"&gt;python manage.py runserver&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-6825761901096786052?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='enclosure' type='text/html' href='http://www.djangoproject.com/documentation/admin/' length='0'/><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/6825761901096786052/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=6825761901096786052' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/6825761901096786052'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/6825761901096786052'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2008/07/blog-post.html' title='Админка'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_cZBqZfv4DaY/SINw-bjg0-I/AAAAAAAAACA/b5Tn3TqQ-Kg/s72-c/%D0%92%D1%8B%D0%B1%D0%B5%D1%80%D0%B8%D1%82%D0%B5+post+%D0%B4%D0%BB%D1%8F+%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F+%7C+%D0%90%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9+%D1%81%D0%B0%D0%B9%D1%82+Django_1216573624759.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-2413568839803038504</id><published>2008-07-13T11:06:00.009+04:00</published><updated>2008-07-13T11:54:38.435+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='growisofs'/><category scheme='http://www.blogger.com/atom/ns#' term='burn'/><category scheme='http://www.blogger.com/atom/ns#' term='webui'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='dvd'/><title type='text'>Запись DVD при помощи Django</title><content type='html'>&lt;blockquote&gt;&lt;i&gt;Этот пост является небольшим отступлением в сторону от идеи блога и предназначен более для людей немного знающих про Django. Если вы еще не знаете как пишутся view и шаблоны &amp;mdash; просто пропустите этот пост, вы сможете вернуться к нему позже.&lt;/i&gt;&lt;/blockquote&gt;

Недавно установил сервер с &lt;a href="http://azureus.sourceforge.net"&gt;Azureus&lt;/a&gt; &lt;a href="http://azureus.sourceforge.net/plugins/howto/webui.html"&gt; Web UI&lt;/a&gt; для скачивания фильмов. Но осталась одна проблема &amp;mdash; запись DVD для просмотра на домашнем кинотеатре. Немного поискав так и не нашел веб-приложения для записи дисков, ну что ж, напишем сами.

Основная идея скрипта &amp;mdash; позволить пользователю выбрать директорию или файл на сервере и выполнить команду записи из пакета dvd+rw-tools:
&lt;pre&gt;&lt;code highlight='no-highlight'&gt;growisofs -dvd-compat -Z &lt;устройство&gt; -R -J -pad "&lt;путь к директории для записи&gt;"&lt;/code&gt;&lt;/pre&gt;к фильмам выложенным без VIDEO_TS необходимо его добавить:
&lt;pre&gt;&lt;code highlight='no-highlight'&gt;growisofs -dvd-compat -Z &lt;устройство&gt; -R -J -pad -graftpoints "/VIDEO_TS=&lt;путь к директории для записи&gt;"&lt;/code&gt;&lt;/pre&gt;
Используемые возможности Django:&lt;ul&gt;&lt;li&gt;Человеко-понятные урлы&lt;/li&gt;&lt;li&gt;Шаблонный движок&lt;/li&gt;&lt;li&gt;MVC&lt;/li&gt;&lt;/ul&gt;неиспользуемые возможности (ORM, i18n, админка) нисколько не мешают, благодаря модульной архитектуре Django.

&lt;h2&gt;Библиотека записи&lt;/h2&gt;
Начнем с &lt;a href="http://paste.org.ru/?qyt01i"&gt;библиотеки-обертки&lt;/a&gt; над growisofs. Она достаточно простая, объект Burner в конструкторе принимающий имя записывающего устройства, пытается определить версию growisofs и если ее нет вызывает исключение. В объекте метод burndvd с параметром path, используя subprocess.Popen пытается нарезать диск. Ошибки определяются чтением stderr и отражаются вызовом нескольких исключений.
&lt;pre&gt;&lt;code highlight='python'&gt;# -*- coding: utf-8 -*-

import os, re, subprocess, types

class Burner:
 """Simple dvd burner class. Uses growisofs"""

 class MediaNotFoundException(Exception):
  pass
 class GrowisofsNotFoundException(Exception):
  pass
 class NotEnoughSpaceException(Exception):
  pass

 __device = None 
 __growisofs = 'growisofs'
 __videodir = 'VIDEO_TS'
 __version = None
 
 def __init__(self, device):
  self.__device = device
  #determine growisofs version
  try:
   popen = subprocess.Popen([self.__growisofs, '--help'], stdout = subprocess.PIPE)
  except OSError, (errno,strerror):
   raise self.GrowisofsNotFoundException((errno,strerror))
  popen.wait()
  str = popen.stdout.readline()
  if len(str) == 0:
   raise self.GrowisofsNotFoundException('no output procuced by growisofs --help')
  match = re.compile('version (\d.\d.\d)').search(str)
  if match == None:
   raise self.GrowisofsNotFoundException('version info not found')
  self.__version = match.group(1)

 def get_growisofs_version(self):
  return self.__version

 def burndvd(self, path):
  """
  burn file or directory from path variable on disk as video
  for dirs automatically determines VIDEO_TS exists inside, if not
  files will been writen to VIDEO_TS directory
  """
  #TODO split DVD9 to two DVD5
  #TODO parse percentages when burning
  if not os.path.exists(path):
   raise IOError('path not found')

  if os.path.isfile(path):
   burnpath = path
  elif os.path.isdir(path):
   videodir = filter(lambda arg:os.path.split(arg[0])[1] == self.__videodir, 
    os.walk(path))
   if len(videodir) == 0:
    burnpath = '/' + self.__videodir + '=' + path
   else:
    burnpath = path
  
  #start burning
  popen = subprocess.Popen([self.__growisofs,'-dvd-compat', '-R', '-J', 
   '-Z', self.__device, '-graft-points', burnpath], stderr = subprocess.PIPE)
  popen.wait()
  errs = popen.communicate()[1]
  if type(errs) == types.StringType:
   errs = [errs]
  regMedia = re.compile('media is not recognized as recordable DVD',re.I)
  regSpace = re.compile('(\d+) blocks are free, (\d+) to be written!',re.I)
  regError = re.compile(':-\(')
  for err in errs:
   match = regMedia.search(err)
   if match != None:
    raise self.MediaNotFoundException()
   match = regSpace.search(err)
   if match != None:
    raise self.NotEnoughSpaceException()
   match = regError.search(err)
   if match is not None:
    raise IOError(err)
  return errs&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Пользовательский интерфейс&lt;/h2&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_cZBqZfv4DaY/SHm0bVxt4UI/AAAAAAAAABk/u1RV4UedLa0/s1600-h/dvd.png"&gt;&lt;img style=" margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp1.blogger.com/_cZBqZfv4DaY/SHm0bVxt4UI/AAAAAAAAABk/u1RV4UedLa0/s320/dvd.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5222403624707940674" /&gt;&lt;/a&gt;
Правило формирования url:&lt;pre&gt;&lt;code highlight='python'&gt;
urlpatterns = patterns('',
 (r'^/dvdburn$', 'default.dvdburn.views.index', {'dirname':''}),
 (r'^/dvdburn/(?P&lt;dirname&gt;[^?]+)/$', 'default.dvdburn.views.index'),
)&lt;/code&gt;&lt;/pre&gt;все что начинается с /dvdburn попадает в метод index, оставшаяся часть url передается как параметр dirname.

Метод view index содержит основную часть логики скрипта. В этом методе к полученному в url пути добавляется путь к корню хранилища фильмов, путь разбивается на составлящие для расставления ссылок на родительские директории, содержимое текущего пути передается в шаблон навигации. Если обнаружен post вызов запускается запись, в текущей версии отображение процесса не реализовано, поэтому пока идет запись страница будет грузиться, по окончании записи отображется шаблон результата.
&lt;pre&gt;&lt;code highlight='python'&gt;# -*- coding: utf-8 -*-

from django.http import * 
from django.shortcuts import render_to_response
import os, re, sys, dircache, locale
from libburn import Burner

def index(request, dirname):
 root = u'/storage/video/'
 device = u'/dev/hda'
 
 path = root;
 if dirname != u'':
  path += dirname

 if request.method == 'POST':
  error = None
  try:
   burner = Burner(device)
   burner.burndvd(path)
  except Burner.MediaNotFoundException:
   error = u'вставьте диск с возможностью записи'
  except Burner.NotEnoughSpaceException:
   error = u'недостаточно места для записи'
  except Burner.GrowisofsNotFoundException:
   error = u'Growisofs не найден, попробуйте sudo apt-get dvd+rw-tools'
  except IOError, (errno, strerror):
      print "I/O error(%s): %s" % (errno, strerror)
  return render_to_response('dvdburn/burnresult.html', {'error':error})

 #разобьем путь на составляющие для ссылок назад
 curpath = []
 curpart = None
 parts = os.path.split(dirname)
 while parts[1] != '':
  curpath.append((curpart, parts[1]))
  curpart = parts[0]
  parts = os.path.split(curpart)
 curpath.reverse()

 #формирование списка содержимого директории в формате (путь, название)
 dirlist = None
 if os.path.isdir(path):
  dirlist = map(
   lambda dir: (os.path.join(dirname,dir), dir), dircache.listdir(path)
  )
 
 return render_to_response('dvdburn/dirlist.html', {'curpath': curpath, 'dirlist': dirlist})&lt;/code&gt;&lt;/pre&gt;

Шаблон навигации выводит текущий путь для записи разбитый на части для переходов назад. Затем выводится содержимое текущей директории с ссылками для продвижения вглубь. Внизу страницы &amp;mdash; форма с кнопкой "записать".
&lt;pre&gt;&lt;code highlight='django'&gt;&amp;lt;p&amp;gt;
{% if curpath %}
&amp;lt;a href='/dvdburn/'&amp;gt;/&amp;lt;/a&amp;gt;
{% for part in curpath %}
 {% if part.0 %}
 &amp;lt;a href='/dvdburn/{{part.0|iriencode}}'&amp;gt;{{part.1}}&amp;lt;/a&amp;gt; / 
 {% else %}
 {{part.1}}
 {% endif %}
{% endfor %}
{% endif %}
&amp;lt;/p&amp;gt;

{% if dirlist %}
Выберите директорию или файл:
&amp;lt;ul&amp;gt;
{% for dir in dirlist %}
 &amp;lt;li&amp;gt;&amp;lt;a href='/dvdburn/{{dir.0|iriencode}}'&amp;gt;{{dir.1}}&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
{% endfor %}
&amp;lt;/ul&amp;gt;
{% endif %}
&amp;lt;form method='post'&amp;gt;
&amp;lt;input type='submit' value='Нарезать' /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;

Шаблон результата выводит ошибку из переменной error или сообщает об успешной записи, если она пуста.
&lt;pre&gt;&lt;code highlight='django'&gt;&amp;lt;style&amp;gt;
 .error{background-color: #E0364F; padding: 1em; font-size:150%;}
 .success{background-color: #0CBE3D; padding: 1em;}
&amp;lt;/style&amp;gt;

{% if error %}
&amp;lt;div class='error'&amp;gt;
Ошибка: {{error}}
&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;
Для повторной попытки обновите (F5) страницу.
&amp;lt;/p&amp;gt;
{% else %}
&amp;lt;div class='success'&amp;gt;
Запись успешно завершена.
&amp;lt;/div&amp;gt;
{% endif %}
&amp;lt;p&amp;gt;
&amp;lt;a href='/dvdburn'&amp;gt;В начало&amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 
&lt;h2&gt;Заключение&lt;/h2&gt;
Что хотелось бы улучшить:&lt;ul&gt;&lt;li&gt;Запись DVD9 на 2 однослойных DVD при необходимости&lt;/li&gt;&lt;li&gt;Отображение процесса записи в процентах, получать информацию можно разбирая вывод growisofs а выводить используя ajax&lt;/li&gt;&lt;/ul&gt;

&lt;a href="http://larin.no-ip.org/dvdburn.tar.gz"&gt;исходный код скрипта&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-2413568839803038504?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/2413568839803038504/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=2413568839803038504' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/2413568839803038504'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/2413568839803038504'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2008/07/dvd-django.html' title='Запись DVD при помощи Django'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_cZBqZfv4DaY/SHm0bVxt4UI/AAAAAAAAABk/u1RV4UedLa0/s72-c/dvd.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-4052311223341634209</id><published>2008-06-10T23:28:00.014+04:00</published><updated>2008-07-03T23:40:16.604+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EmailField'/><category scheme='http://www.blogger.com/atom/ns#' term='DateFields'/><category scheme='http://www.blogger.com/atom/ns#' term='blogengine'/><category scheme='http://www.blogger.com/atom/ns#' term='blog'/><category scheme='http://www.blogger.com/atom/ns#' term='post'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='user'/><category scheme='http://www.blogger.com/atom/ns#' term='CharField'/><category scheme='http://www.blogger.com/atom/ns#' term='TextField'/><category scheme='http://www.blogger.com/atom/ns#' term='models'/><category scheme='http://www.blogger.com/atom/ns#' term='model'/><category scheme='http://www.blogger.com/atom/ns#' term='ForeignKey'/><title type='text'>Создание моделей</title><content type='html'>&lt;h2&gt;Немного теории&lt;/h2&gt;
Модель в Django это описание сущностей приложения при помощи специального синтаксиса, например у нас может быть сущность пользователь с полями логин, пароль, адрес электропочты, дата рождения и сущность запись в блоге с полями заголовок, содержание и ссылкой на пользователя, который опубликовал запись.
 
Модель описывается как класс, унаследованный от Model, поля объекта описываются путем присвоения значений из класса Model, каждое из значений означает один из допустимых типов полей.

Для примера опишем указанные выше сущности:
&lt;pre&gt;&lt;code highlight='python'&gt;        #сущность пользователь
 class User (models.Model):
  login = models.CharField(max_length=50)
  password = models.CharField(max_length=50)
  email = models.EmailField()
  age = models.DateField()
  
 #сущность запись в блоге
 class Post (models.Model):
  title = models.CharField(max_length=100)
  body = models.TextField()
  poster = models.ForeignKey('User')&lt;/code&gt;&lt;/pre&gt;

Рассмотрим что же мы написали.
Запись вида &lt;code&gt;login = CharField(max_length=50)&lt;/code&gt; означает, что сущность пользователь обладает свойством логин, которое представлено строкой символов максимальной длиной 50, просто не правда ли? 
EmailField это поле для хранения адреса электропочты, DateField &amp;mdash; для хранения даты, TextField для хранение текста, а ForeignKey определяет ссылку poster на объект класса пользователь.

По описанию моделей Django сможет разработает для нас схему базы данных, создаст административный интерфейс и валидаторы. Мы написали всего 10 строчек кода, а получили достаточно много функций, это одна из основных идей рассматриваемого фреймворка &amp;mdash; написание приложения с использованием меньшего количества кода. 

SQL-код сгенерированный Django по описанным моделям:
&lt;pre&gt;&lt;code highlight='sql'&gt;CREATE TABLE `default_post` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(100) NOT NULL,
    `body` longtext NOT NULL,
    `poster_id` integer NOT NULL
)
;
CREATE TABLE `default_user` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `login` varchar(50) NOT NULL,
    `password` varchar(50) NOT NULL,
    `email` varchar(75) NOT NULL,
    `age` date NOT NULL
)
;
ALTER TABLE `default_post` ADD CONSTRAINT poster_id_refs_id_3b3164d6 
FOREIGN KEY (`poster_id`) REFERENCES `default_user` (`id`);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Приложение&lt;/h2&gt;
Для завершения настройки нашего проекта создадим приложение &amp;mdash; осмысленная часть нашего проекта выполняющая какие-либо действия. В Zend Framework можно найти аналогичный механизм &amp;mdash; &lt;a href="http://framework.zend.com/manual/en/zend.controller.modular.html"&gt;модуль&lt;/a&gt;. 

Пока не вижу необходимости разбивать наш проект на приложения, поэтому ограничимся одним и назовем его default. Запустите эту команду находясь в директории coblogs 
&lt;code&gt;python manage.py startapp default&lt;/code&gt;

 Итак, мы получили новую директорию default, в ней вы найдете файл models.py, который содержит описания моделей, весь код описанный далее содержится в этом файле.

Так же необходимо отредактировать settings.py, включив default в список установленных приложений
&lt;pre&gt;&lt;code highlight='python'&gt;INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'coblog.default'
)&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Модели&lt;/h2&gt;
Напомню, что мы создаем сервис коллективных блогов с визуальным/wiki редактором, тегами, кармой и рейтингом постов и комментариев. Попробуем создать костяк моделей проекта, постепенно расширяя его при необходимости.

Первая модель &amp;mdash; наш дорогой и любимый пользователь, но описывать её не нужно в Django есть встроенный механизм аутентификации включающий модель User.

Поля &amp;laquo;стандартного пользователя&amp;raquo;:
&lt;dl&gt;
&lt;dt&gt;username&lt;/dt&gt;&lt;dd&gt;имя пользователя (логин), обязательное поле, не более 30 букв, цифр и знаков подчеркивания.&lt;/dd&gt;
&lt;dt&gt;first_name&lt;/dt&gt;&lt;dd&gt;имя пользователя (по паспорту ;), опционально, не более 30 символов.&lt;/dd&gt;
&lt;dt&gt;last_name&lt;/dt&gt;&lt;dd&gt;фамилия пользователя, опционально, не более 30 символов.&lt;/dd&gt;
&lt;dt&gt;email&lt;/dt&gt;&lt;dd&gt;электропочта.&lt;/dd&gt;
&lt;dt&gt;password&lt;/dt&gt;&lt;dd&gt;хеш пароля, обязательное поле, метод set_password поможет установить хэш по паролю.&lt;/dd&gt;
&lt;dt&gt;is_staff&lt;/dt&gt;&lt;dd&gt;определяет, может ли пользователь использовать админку.&lt;/dd&gt;
&lt;dt&gt;is_active&lt;/dt&gt;&lt;dd&gt;определяет, не заблокирован ли пользователь (true &amp;mdash; не заблокирован).&lt;/dd&gt;
&lt;dt&gt;is_superuser&lt;/dt&gt;&lt;dd&gt;если true,  то пользователь имеет все возможные права.&lt;/dd&gt;
&lt;dt&gt;last_login&lt;/dt&gt;&lt;dd&gt;дата и время последнего входа на сайт.&lt;/dd&gt;
&lt;dt&gt;date_joined&lt;/dt&gt;&lt;dd&gt;дата и время создания аккаунта.&lt;/dd&gt;
&lt;/dl&gt;

Кроме полей есть полезные стандартные методы, описанные в &lt;a href="http://www.djangoproject.com/documentation/authentication/"&gt;руководстве&lt;/a&gt;.

Для пользователя в нашем проекте не хватает одного поля &amp;mdash; кармы, добавим его, воспользовавшись механизмом профайлов:
&lt;pre&gt;&lt;code highlight='python'&gt;# -*- coding: utf8 -*-  укажем кодировку этого файла

#подлючение механизма моделей
from django.db import models
#подключение стандартной модели пользователя
from django.contrib.auth.models import User
import math

class Profile(models.Model):
        #Порог уровня изменения кармы см. get_karma_delta
        KARMA_DELTA_THRESHOLD = 10

        #ссылка на стандратную модель пользователя
  user = models.ForeignKey(User, unique=True)
        __karma = models.IntegerField(default=0)

        def inc_karma(self, delta):
                """Увеличивает(или уменьшает) карму пользователя на delta"""
                self.__karma += delta

        def get_karma(self):
                """Возвращает карму пользователя"""
                return self.__karma

        def get_karma_delta(self):
                """
                Возвращает на какое значение пользователь может
                изменить карму другого пользователя
                """
                if self.__karma == 0:
                        return 1
                else:
                        return math.ceil(self.__karma/self.KARMA_DELTA_THRESHOLD)

  #Данный класс нужен для появления модели в административном интерфейсе 
                #(об этом в следующий раз)
        class Admin:
                pass&lt;/code&gt;&lt;/pre&gt;
Для подключения нашего профиля добавим строку в settings.py:
&lt;pre&gt;&lt;code highlight='no-highlight'&gt;AUTH_PROFILE_MODULE = "default.profile"&lt;/code&gt;&lt;/pre&gt;
теперь профиль пользователя доступен при помощи метода get_profile().

Следующие модели &amp;mdash; блог (пока содержит только название и описание) и запись в блоге:
&lt;pre&gt;&lt;code highlight='python'&gt;class Blog(models.Model):
        caption = models.CharField(max_length=50)
        description = models.TextField()
        created = models.DateTimeField(auto_now_add=True)

        #функция __unicode__ определяет строковое представление объекта
  def __unicode__(self):
                return self.caption

        class Admin:
                pass

class Post(models.Model):
        """ Запись в блоге """
  #ссылка на блог в котором опубликована запись
        blog = models.ForeignKey(Blog)
  #ссылка на пользователя опубликовавшего запись
        poster = models.ForeignKey(User)

        #рейтинг записи
        __rate = models.IntegerField(default=0)

  #заголовок записи
        caption = models.CharField(max_length=50)
  #содержание записи (wiki-размеченный текст)
        content = models.TextField()
  #поле определяет, является ли запись черновиком
        draft = models.BooleanField(default = True)
  #дата создания
        created = models.DateTimeField(auto_now_add=True)
  #дата публикации (устанавливается после снятия опции черновик)
  posted = models.DateTimeField()

        def inc_rate(self, delta):
                self.__rate += delta

        def __unicode__(self):
                str = '';
                if self.draft:
                        str = ' (draft)';
                return self.caption + str

        class Admin:
                pass&lt;/code&gt;&lt;/pre&gt;

И последние две модели &amp;mdash; комментарий к записи в блоге и тэг к записи:
&lt;pre&gt;&lt;code highlight='python'&gt;class Tag(models.Model):
        """ Тэг (метка) для записи в блоге """
        post = models.ForeignKey(Post)
        tag = models.CharField(max_length=30)

        def __unicode__(self):
                return self.tag

        class Admin:
                pass

class Comment(models.Model):
        post = models.ForeignKey(Post)
        parent = models.ForeignKey('self')
        user = models.ForeignKey(User)

        __rate = models.IntegerField()
        content = models.TextField()
        created = models.DateTimeField(auto_now_add=True)

        def inc_rate(self, delta):
                self.__rate += delta

        def __unicode__(self):
                return self.content

        class Admin:
                pass
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Создание структуры базы данных&lt;/h2&gt;
Пришло время магии :) создадим структуру базы данных из наших моделей. Для этого достаточно ввести команду:
&lt;pre&gt;&lt;code highlight='no-highlight'&gt;python manage.py syncdb&lt;/code&gt;&lt;/pre&gt;

Сгенерированный SQL-код можно посмотреть при помощи команды:
&lt;pre&gt;&lt;code highlight='no-highlight'&gt;python manage.py sql default&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Манипуляции с моделями&lt;/h2&gt;
После создания структуры базы данных, можно познакомиться с &lt;a href="http://www.djangoproject.com/documentation/db-api/"&gt;Database API&lt;/a&gt; предоставляемый джанговским ORM.

Откроем Python консоль и поэкспериментируем с методами моделей:
&lt;pre&gt;&lt;code highlight='python'&gt;#&gt; python manage.py shell
#импортируем стандартную модель пользователя
&gt;&gt;&gt;from django.contrib.auth.models import User
#создадим экземпляр класса User и установим ему пароль '12345'
&gt;&gt;&gt; user = User(username = 'testuser', first_name='Василий', last_name='Пупкин')
&gt;&gt;&gt; user.set_password('12345')
#сохраним пользователя
&gt;&gt;&gt; user.save()
#пользователь был успешно добавлен в БД, посмотрим сгенерированный id
&gt;&gt;&gt; user.id
3L
#импортируем все наши модели
&gt;&gt;&gt; from coblog.default.models import *
#создадим профиль пользователя для testuser и сохраним его в БД
&gt;&gt;&gt; profile = Profile(user=user)
&gt;&gt;&gt; profile.save()
#посмотрим карму
&gt;&gt;&gt; user.get_profile().get_karma()
0L
&gt;&gt;&gt; выведем имена всех пользователей
&gt;&gt;&gt; for usr in User.objects.all() :
...     print usr
root
testuser
#напишем пост
&gt;&gt;&gt; blog = Blog(caption='Блог о Django')
&gt;&gt;&gt; blog.save()
&gt;&gt;&gt; post = Post(poster= User.objects.get(username='testuser'),
caption='Database API', blog=blog)
&gt;&gt;&gt; post.content = 'вот мы вкратце и ознакомились с Django Database API.'
&gt;&gt;&gt; post.save()
&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-4052311223341634209?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/4052311223341634209/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=4052311223341634209' title='Комментарии: 10'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/4052311223341634209'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/4052311223341634209'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2008/06/blog-post_10.html' title='Создание моделей'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-2885196451065855698</id><published>2008-06-09T22:10:00.025+04:00</published><updated>2008-12-12T07:56:40.359+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='install'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='subversion'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='tortoise'/><title type='text'>Установка и настройка</title><content type='html'>Для создания Django приложения понадобятся:
&lt;ul&gt;
&lt;li&gt;Python
&lt;li&gt;Python-Imaging для работы с изображениями
&lt;li&gt;Subversion (для получения исходного кода Django)
&lt;li&gt;MySQL и MySQLdb для связи БД с Python
&lt;li&gt;Текстовый редактор
&lt;li&gt;Django
&lt;/ul&gt;
Рассказывать буду кратко, применительно к Linux и Windows. Если возникнут какие-либо проблемы или вопросы спрашивайте, отвечу.
&lt;h2&gt;Linux&lt;/h2&gt;
Здесь все просто необходимые программы есть в репозитарии, приведу пример для Ubuntu:
&lt;pre&gt;&lt;code class='no-highlight'&gt;sudo apt-get install python
sudo apt-get install subversion
sudo apt-get install mysql-server
sudo apt-get install python-mysqldb
sudo apt-get install python-imaging
&lt;/code&gt;&lt;/pre&gt;
Текстовый редактор подойдет любой: kate &amp;mdash; если вы используете  KDE, gedit &amp;mdash; если Gnome или можете попробовать Komodo Edit.
&lt;h2&gt;Windows&lt;/h2&gt;
Ссылки на дистрибутивы:
&lt;ul&gt; 
&lt;li&gt;&lt;a href="http://python.org/ftp/python/2.5.2/python-2.5.2.msi"&gt;Python&lt;/a&gt;
&lt;li&gt;&lt;a href="http://effbot.org/downloads/PIL-1.1.6.win32-py2.5.exe"&gt;python-imaging&lt;/a&gt;
&lt;li&gt;Subversion: &lt;a href="http://subversion.tigris.org/files/documents/15/41686/svn-1.4.6-setup.exe"&gt;консольный клиент&lt;/a&gt; либо &lt;a href="http://downloads.sourceforge.net/tortoisesvn/TortoiseSVN-1.4.8.12137-win32-svn-1.4.6.msi?download"&gt;Tortoise&lt;/a&gt; &amp;mdash; удобная графическая оболочка.
&lt;li&gt;&lt;a href="http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.51b-win32.zip/from/http://mysql.mix.su/"&gt;MySQL&lt;/a&gt;, &lt;a href="http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&amp;big_mirror=0"&gt;MySQLdb&lt;/a&gt;
&lt;li&gt;Текстовый редактор подойдет любой, но лучше с подсветкой синтаксиса, например, &lt;a href="http://www.activestate.com/store/download_file.aspx?binGUID=0071d569-0b74-476a-abbc-90725cc33532"&gt;Komodo  Edit&lt;/a&gt;, прекрасно справляется как с Python кодом так и с Django шаблонами.

&lt;/ul&gt;
&lt;h2&gt;Django&lt;/h2&gt;
Для получения исходных кодов Django необходимо выполнить команду:
&lt;code&gt;svn co http://code.djangoproject.com/svn/django/trunk/&lt;/code&gt;
Либо, если вы установили Tortoise, выберите в контекстном меню (правая кнопка в проводнике) &amp;laquo;Svn checkout&amp;raquo; и введите адрес http://code.djangoproject.com/svn/django/trunk/.

Затем запустите консоль и выполните команду:
&lt;code&gt;python setup.py install&lt;/code&gt;

&lt;h2&gt;Создание проекта&lt;/h2&gt;
Django проект &amp;mdash; содержит в себе настройки доступа к бд, настройки Django и исходный код приложений. Перейдите в директорию, где вы собираетесь размещать ваш исходный код и выполните команду: 
&lt;code&gt;django-admin.py startproject coblogs&lt;/code&gt; 
Эта команда создаст директорию coblogs для нашего приложения и начальные настройки.

Необходимо создать базу данных для нашего приложения (убедитесь что MySQL сервер запущен):
&lt;pre&gt;&lt;code class='sql'&gt;$mysql -u root
mysql&gt; CREATE DATABASE coblog CHARACTER SET UTF8;
Query OK, 1 row affected (0.00 sec)
&lt;/pre&gt;&lt;/code&gt;
 
и настроить доступ к ней, откройте файл coblogs/setting.py и измените следующие настройки:
&lt;pre&gt;&lt;code class='python'&gt;DATABASE_ENGINE = 'mysql'      # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = 'coblog'       # Or path to database file if using sqlite3.
DATABASE_USER = 'root'         # Not used with sqlite3.
DATABASE_PASSWORD = ''         # Not used with sqlite3.
DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.

LANGUAGE_CODE = 'ru-ru'
&lt;/code&gt;&lt;/pre&gt;

Позволим Django записать свои данные в только что созданную базу:
&lt;pre&gt;&lt;code class='nohighlight'&gt;coblog$ python manage.py syncdb&lt;/code&gt;&lt;/pre&gt;
В ходе этого процесса вас спросят имя для аккаунта администратора и пароль, запомните введенные данные они нам еще понадобятся.

Пример правильного вывода команды:
&lt;pre&gt;&lt;code class='no-highlight'&gt;python manage.py syncdb
Creating table auth_message
Creating table auth_group
Creating table auth_user
Creating table auth_permission
Creating table django_content_type
Creating table django_session
Creating table django_site

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'root'): 
E-mail address: a@a.ru
Password: 
Password (again): 
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
&lt;/code&gt;&lt;/pre&gt;

Если то что вы увидели сильно отличается от приведенного листинга, скорее всего имеет место ошибка доступа к БД (не запущен сервер, неверные имя пользователя и пароль, ошибка установки драйвера). Проверьте настройки и попробуйте еще раз.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-2885196451065855698?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/2885196451065855698/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=2885196451065855698' title='Комментарии: 8'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/2885196451065855698'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/2885196451065855698'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2008/06/blog-post_09.html' title='Установка и настройка'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-5402195531778556824</id><published>2008-06-04T22:12:00.013+04:00</published><updated>2008-06-08T15:52:21.098+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='книга'/><category scheme='http://www.blogger.com/atom/ns#' term='блог'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='ссылки'/><category scheme='http://www.blogger.com/atom/ns#' term='weblog'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='links'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Пара слов о Django</title><content type='html'>&lt;blockquote&gt;
«The web framework for perfectionists with deadlines»
&lt;/blockquote&gt;
Django это веб-фреймворк для языка Python, позволяющий &lt;i&gt;быстро&lt;/i&gt;   разрабатывать динамические веб-приложения.   Один из основных принципов звучит как &amp;laquo;Don't Repeat Youself&amp;raquo; это означает, что единожды написанный код не должен повторяться в другом месте проекта. Что, скажу я вам, значительно отличается от принятой в большинстве проектов тактики  Copy &amp;amp; Paste, и это радует.

Основные возможности Django:
&lt;dl&gt;
&lt;dt&gt;&lt;a href="http://www.djangoproject.com/documentation/model-api/"&gt;Object-relation mapper&lt;/a&gt;&lt;/dt&gt;&lt;dd&gt;технология отображения объектов в базу данных. Данная особенность фреймворка позволяет абстрагироваться от SQL запросов при написании проекта  и использовать объекты для доступа к БД.

Это не избавляет от необходимости писать SQL-запросы, но в большинстве случаев вам не придеться делать этого.

Описание объектов доступа к БД в Django, называется моделями. В описании модели указываются тип поля, ограничения накладываемые на содержимое, реляционные связи с другими сущностями. Согласно принципу DRY вы не должны повторять эти данные в другом месте, поэтому по описанию модели генерируется средство управления объектами базы данных (админка) и валидаторы.   
&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://www.djangoproject.com/documentation/tutorial02/"&gt;Automatic admin interface&lt;/a&gt;&lt;/dt&gt;&lt;dd&gt;автоматическая генерация средства управления объектами бд.
Как уже я упоминал ранее по описанию модели автоматически строится админка, которая позволят создавать, удалять объекты и управлять их связями. Хочется отметить, что вы можете влиять через описание модели на интерфейс админки, например, сворачивать по умолчанию не важные поля или встраивать редактор зависимых объектов в редактор родительского объекта.
&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://www.djangoproject.com/documentation/url_dispatch/"&gt;Elegant URL Design&lt;/a&gt;&lt;/dt&gt;&lt;dd&gt;удобная система построения &lt;a href="http://www.artlebedev.ru/kovodstvo/sections/48/"&gt;человеко-понятных  урлов&lt;/a&gt; используется в Django по умолчанию&lt;/dd&gt;

&lt;dt&gt;&lt;a href="http://www.djangoproject.com/documentation/templates/"&gt;Template system&lt;/a&gt;&lt;/dt&gt;&lt;dd&gt;в состав фреймворка входит удобная система шаблонов для разделения логики и представления информации. Django является полноценным &lt;a href="http://ru.wikipedia.org/wiki/Model-view-controller"&gt;MVC&lt;/a&gt; фреймворком: ORM предоставляет Модель, шаблонная система &amp;mdash; Вид, и то что в Django называется View является контроллером.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://www.djangoproject.com/documentation/cache/"&gt;Cache system&lt;/a&gt;&lt;/dt&gt;&lt;dd&gt;для высоконагруженных сайтов предоставляется система кеширования, которая позволяет сохранять сгенерированные страницы, или их части в БД или memcached (или еще где-нибудь) и выдавать в следующий раз уже сохраненную копию страницы для уменьшения нагрузки&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://www.djangoproject.com/documentation/i18n/"&gt;Internatialization&lt;/a&gt;&lt;/dt&gt;&lt;dd&gt;хотите написать многоязычное веб-приложение? Легко ;) В Django входят средства интернационализации, в коде и шаблонах возможно использование указать что данную строку необходимо перевести на язык пользователя, и если вы предоставили файл перевода для этого языка, система вставит уже переведенную строку (идея &lt;a href="http://www.gnu.org/software/gettext/"&gt;gettext&lt;/a&gt;). &lt;/dd&gt;
&lt;/dl&gt;

В общем Django это каркас для веб-приложений, включающий практически все что может вам понадобится, причем все возможности следуют одной общей логике и стилю и легко интегрируются друг с другом. Попробуйте Django, я уверен, вам понравится.


Useful links:
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://djangoproject.com/"&gt;Официальный сайт Django&lt;/a&gt;
&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.djangoproject.com/documentation/tutorial01/"&gt;Пишем свое первое приложение на Django&lt;/a&gt; — официальный туториал, настоятельно рекомендуется к прочтению
&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.djangobook.com/"&gt;The Django Book&lt;/a&gt;
&lt;/li&gt;&lt;li&gt;&lt;a href="http://groups.google.com/group/django-users"&gt;Django users group&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;
Полезные ссылки:
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.python.ru/files/book-ods.pdf"&gt;Язык программирования Python&lt;/a&gt; — книга &lt;a href="http://ru.wikipedia.org/wiki/%D0%A0%D0%BE%D1%81%D1%81%D1%83%D0%BC,_%D0%93%D0%B2%D0%B8%D0%B4%D0%BE_%D0%B2%D0%B0%D0%BD"&gt;Гвидо Ван Россума&lt;/a&gt; создателя языка Пайтон.
&lt;/li&gt;&lt;li&gt;&lt;a href="http://cargo.caml.ru/djangobook/"&gt;Русский перевод Django Book&lt;/a&gt;
&lt;/li&gt;&lt;li&gt;&lt;a href="http://softwaremaniacs.org/"&gt;Software Maniacs&lt;/a&gt; — проект Ивана Сагалаева, посвященный веб разработке на Django. Здесь вы обнаружите много интересного, особенно рекомендую &lt;a href="http://softwaremaniacs.org/blog/category/web/primer/"&gt;учебник по блочной верстке&lt;/a&gt; и &lt;a href="http://softwaremaniacs.org/forum/"&gt;форумы&lt;/a&gt;.
&lt;/li&gt;&lt;li&gt;&lt;a href="http://webnewage.org/"&gt;Блог Александра Кошелева&lt;/a&gt;
&lt;/li&gt;&lt;li&gt;&lt;a href="http://community.livejournal.com/ru_django/"&gt;Русскоязычное Django сообщество&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-5402195531778556824?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/5402195531778556824/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=5402195531778556824' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/5402195531778556824'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/5402195531778556824'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2008/06/django.html' title='Пара слов о Django'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7061791285623738411.post-8806889011212336978</id><published>2008-06-04T11:46:00.000+04:00</published><updated>2008-06-04T12:38:43.815+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='введение'/><category scheme='http://www.blogger.com/atom/ns#' term='web 2.0'/><category scheme='http://www.blogger.com/atom/ns#' term='social network'/><category scheme='http://www.blogger.com/atom/ns#' term='blogengine'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><title type='text'>Введение</title><content type='html'>&lt;p&gt;Так получилось, что моя работа связана с web-разработкой, причем на &lt;a href="http://php.net/"&gt;PHP&lt;/a&gt;. После участия в паре проектов для меня стали очевидны некоторые минусы PHP в частности и использования компонентных фреймворков (&lt;a href="http://framework.zend.com/"&gt;Zend Framework&lt;/a&gt;).
&lt;/p&gt;Я немного знаю &lt;a href="http://python.org/"&gt;Python&lt;/a&gt; и &lt;a href="http://djangoproject.com/"&gt;Django&lt;/a&gt;, которые, вероятно, не обладают этими недостатками, но возможности применить их в реальном проекте не было. Поэтому я решил параллельно с проектом PHP + Zend Framework на работе, разработать похожий на Python + Django, дабы сравнить эти технологии в "боевых условиях". Весь процесс разработки с комментариями и моими мыслями я буду описывать в этом блоге.
&lt;p&gt;Для оценки возможностей Django был выбран, &lt;a href="http://d3.ru/"&gt;так&lt;/a&gt; &lt;a href="http://habrahabr.ru/"&gt;популярный&lt;/a&gt; &lt;a href="http://dvice.ru/"&gt;в наши дни&lt;/a&gt;, сервис коллективных блогов с элементами социальности.&lt;/p&gt;&lt;p&gt;Примерный список возможностей:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Коллективные блоги с визуальным/wiki (html на мой взгляд не удобен для этих целей) редактором&lt;/li&gt;&lt;li&gt;Древовидные комментарии к постам
&lt;/li&gt;&lt;li&gt;Голование за посты и комментарии
&lt;/li&gt;&lt;li&gt;Карма пользователей&lt;/li&gt;&lt;li&gt;Тэги&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7061791285623738411-8806889011212336978?l=alarin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://alarin.blogspot.com/feeds/8806889011212336978/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7061791285623738411&amp;postID=8806889011212336978' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/8806889011212336978'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7061791285623738411/posts/default/8806889011212336978'/><link rel='alternate' type='text/html' href='http://alarin.blogspot.com/2008/06/blog-post.html' title='Введение'/><author><name>Анатолий Ларин</name><uri>http://www.blogger.com/profile/15034605339234934142</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='23' height='32' src='http://bp3.blogger.com/_cZBqZfv4DaY/SGEraXn_SII/AAAAAAAAABI/AbsVMv3zOp0/S220/0_100db_b68ea810_XL.png'/></author><thr:total>1</thr:total></entry></feed>
