<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CEMBQHg8eip7ImA9WhRTF0s.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599</id><updated>2011-11-08T16:27:31.672+02:00</updated><category term="kikola" /><category term="django weblog" /><category term="security fix" /><category term="documentation" /><category term="полезные советы" /><category term="архитектура проектов" /><category term="queryset-refactor" /><category term="tips and tricks" /><category term="newforms" /><category term="sphinx" /><category term="pip" /><category term="администрирование django" /><category term="новости" /><category term="my projects" /><category term="python string methods" /><category term="github" /><category term="reusable apps" /><category term="autocomplete" /><category term="django forum" /><category term="custom model fields" /><category term="widgets" /><category term="file uploads" /><category term="sessions" /><category term="jquery" /><category term="newforms-admin" /><category term="django forms" /><category term="django orm" /><category term="django trunk" /><category term="системы управления версиями" /><category term="search" /><category term="о блоге" /><category term="bootstrap" /><category term="django 1.0" /><category term="testing" /><category term="virtualenv" /><category term="mediafiles" /><category term="tddspry" /><category term="django template" /><category term="django 1.2" /><title>Еще один блог о Django</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://djangonaut.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>29</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/djangonaut" /><feedburner:info uri="djangonaut" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;D0IEQnc8eCp7ImA9WhdWGEg.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-6299639894271700382</id><published>2011-09-12T22:24:00.000+03:00</published><updated>2011-09-12T22:25:03.970+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-12T22:25:03.970+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django template" /><title>Проверяем значение переменной на True, False, None в Django шаблонах</title><content type="html">&lt;p&gt;На собеседованиях часто приходиться спрашивать и слышать про шаблоны Django как узкое место фреймворка. И в большинстве случаев мы говорим про скорость выполнения, недостаточную гибкость и простоту при создании кастомных фильтров или тегов.&lt;/p&gt;

&lt;p&gt;Но сегодняшний инцидент стоит просто за гранью моего понимания. Итак, есть модель с полем &lt;code&gt;NullBooleanField&lt;/code&gt;, назовем его &lt;code&gt;verified&lt;/code&gt;. Далее в шаблоне нужно отображать специфический класс для HTML-тега если &lt;code&gt;verified is False&lt;/code&gt;. Что ж, это просто, за неимением &lt;code&gt;is&lt;/code&gt; возпользуюсь проверенным &lt;code&gt;==&lt;/code&gt;:&lt;/p&gt;

&lt;pre name="code" class="html"&gt;&amp;lt;div{% if verified == False %} class="ui-state-error-text"{% endif %}&amp;gt;
    Some text
    Some input
&amp;lt;/div&amp;gt;&lt;/pre&gt;

&lt;p&gt;Ок, обновляю страницу и думаю все ок. Однако, не тут-то было. Результата нет, в исходном тексте тоже никакого &lt;code&gt;class="ui-state-error-text"&lt;/code&gt; нет и в помине. Странно. Ладно, дай думаю проверю в &lt;code&gt;shell_plus&lt;/code&gt;. Проверка подтверждает мои опасения:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;In [2]: t = Template('{% if verified == False %}False{% else %}Not False{% endif %}')

In [3]: t.render(Context({'verified': False}))
Out[3]: u'Not False'

In [4]: t.render(Context({'verified': True}))
Out[4]: u'Not False'

In [5]: t.render(Context({'verified': None}))
Out[5]: u'False'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Хм, ясное дело свитч на &lt;code&gt;{% if not verified %}&lt;/code&gt; не достиг никаких результатов (теперь класс будет печататься и в случае, когда &lt;code&gt;verified is None&lt;/code&gt;):&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;In [6]: t = Template('{% if not verified %}False{% else %}Not False{% endif %}')

In [7]: t.render(Context({'verified': False}))
Out[7]: u'False'

In [8]: t.render(Context({'verified': True}))
Out[8]: u'Not False'

In [9]: t.render(Context({'verified': None}))
Out[9]: u'False'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Хм, что же делать? Как же быть? Почему &lt;code&gt;{% if verified == None %}&lt;/code&gt; работает если &lt;code&gt;verified is None&lt;/code&gt;. Спросил совета у команды. И надо сказать их совет подействовал, надо просто проверку на &lt;code&gt;False&lt;/code&gt; сменить на проверку на &lt;code&gt;0&lt;/code&gt;. И все начинает работать:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;In [10]: t = Template('{% if verified == 0 %}False{% else %}Not False{% endif %}')

In [11]: t.render(Context({'verified': False}))
Out[11]: u'False'

In [12]: t.render(Context({'verified': True}))
Out[12]: u'Not False'

In [13]: t.render(Context({'verified': None}))
Out[13]: u'Not False'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Теперь вопрос, что заставило Django-девов не добавить boolean константы &lt;code&gt;True&lt;/code&gt; и &lt;code&gt;False&lt;/code&gt; в &lt;code&gt;{% if %}&lt;/code&gt; темплейт тег? Где поддержка &lt;code&gt;var is (None|True|False)&lt;/code&gt;? Уже ж сделали вроде как вменяемый &lt;code&gt;if&lt;/code&gt;, что помешало сделать это раз и навсегда? Особенно в свете того, что поддержка конструкций &lt;code&gt;item in list&lt;/code&gt; и &lt;code&gt;item not in list&lt;/code&gt; была добавлена.&lt;/p&gt;

&lt;p&gt;В целом был разочарован я изрядно, но теперь буду помнить, что для проверки на &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt; в шаблонах надо сравнивать с &lt;code&gt;1&lt;/code&gt; и &lt;code&gt;0&lt;/code&gt;. Прям как хрен знает где еще :(&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-6299639894271700382?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6299639894271700382?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6299639894271700382?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/HkE4ssQHSUc/true-false-none-django.html" title="Проверяем значение переменной на True, False, None в Django шаблонах" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2011/09/true-false-none-django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08BQHw7eip7ImA9WhZbGE4.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-8722813901544391808</id><published>2011-06-23T17:37:00.000+03:00</published><updated>2011-06-23T17:37:31.202+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-23T17:37:31.202+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django orm" /><title>Уменьшаем кол-во запросов, которые генерируются Django ORM</title><content type="html">&lt;p&gt;Думаю, для многих уже совсем не секрет, что делать, чтобы уменьшить кол-во запросов, которые генерируются при помощи Django ORM. Однако, столкнувшись в очередной раз с необходимостью сбавить обороты и ускорить генерацию страницы я захотел вынести все эти методы в один пост.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Лирическое отступление:&lt;/em&gt; Для того, чтоб лучше понять как эти методы работают, я задам легкую схему для моделей, упоминаемых в статье. Во-первых, это будет встроенная &lt;code&gt;auth.User&lt;/code&gt;, затем это будет модель статьи, которая будет версионироваться при помощи &lt;a href="https://github.com/etianen/django-reversion"&gt;&lt;code&gt;django-reversion&lt;/code&gt;&lt;/a&gt;. Иными словами, наш &lt;code&gt;models.py&lt;/code&gt; будет выглядеть как-то так:

&lt;pre name="code" class="python"&gt;&lt;code&gt;from django.db import models
from django.utils.translation import ugettext_lazy as _

import reversion


class Article(models.Model):

    STATE_NEW, STATE_SUBMITTED, STATE_REJECTED, STATE_ACCEPTED = range(1, 5)
    STATE_CHOICES = (
        (STATE_NEW, _('New')),
        (STATE_SUBMITTED, _('Submitted')),
        (STATE_REJECTED, _('Rejected)),
        (STATE_ACCEPTED, _('Accepted)),
    )

    title = models.CharField(_('title'), max_length=64)
    content = models.TextField(_('content'))
    state = models.PositiveIntegerField(_('state'), choices=STATE_CHOICES,
        default=STATE_NEW)

    writer = models.ForeignKey('auth.User', related_name='articles_writer',
        verbose_name=_('writer'))
    editor = models.ForeignKey('auth.User', blank=True, null=True,
        related_name='articles_editor', verbose_name=_('editor'))

reversion.register(Article)
&lt;/code&gt;&lt;/pre&gt;

&lt;h1&gt;Метод 1. Использование select_related вместо all&lt;/h1&gt;

&lt;p&gt;Здесь все очень просто. Если у моделей есть не-нулевые FK поля Вы явно захотите получать данные в них без дополнительных запросов к БД. Потому если писатель статьи должен быть явно указан на сайте, то считывайте статьи перед паджинацией как,&lt;/p&gt;

&lt;pre class="python" name="code"&gt;&lt;code&gt;articles = Article.objects.select_related()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;а не &lt;code&gt;.all()&lt;/code&gt;. На самом деле метод &lt;code&gt;.all()&lt;/code&gt; хорош как по мне только для "нишевых" (с малым кол-вом или вообще без связей) моделей. В остальных случаях надо смотреть по ситуации, может где-то &lt;code&gt;.defer()&lt;/code&gt; после &lt;code&gt;.select_related(*fields)&lt;/code&gt; пригодится, может где и &lt;code&gt;.annotate()&lt;/code&gt; будет использован.&lt;/p&gt;

&lt;p&gt;В целом с &lt;code&gt;.select_related()&lt;/code&gt; я думаю всем все давным давно ясно, не забывайте только, что для не-нулевых FK-полей этот метод ничего не даст. И следующий код сгенерит в худшем случае &lt;code&gt;Articles.objects.count() + 1&lt;/code&gt; запросов (если все поля &lt;code&gt;editor&lt;/code&gt; заполнены), вместо 1:&lt;/p&gt;

&lt;pre class="python" name="code"&gt;&lt;code&gt;articles = Article.objects.select_related()
for article in articles:
    print(article.title, article.state, article.writer, article.editor)
&lt;/code&gt;&lt;/pre&gt;

&lt;h1&gt;Метод 2. Использование model.fk_id вместо model.fk.id&lt;/h1&gt;

&lt;p&gt;На самом деле, с первого взгляда кажется, что метод несколько бесполезен. Ну что там можно узнать про объект только по его идентификатору? Однако, смею вас уверить, это кажется только на первый взгляд. На самом деле во всяких &lt;code&gt;filter&lt;/code&gt; или &lt;code&gt;map&lt;/code&gt; и прочих &lt;code&gt;lambda&lt;/code&gt;-функциях без него не обойтись.&lt;/p&gt;

&lt;p&gt;Простой пример, необходимо найти все версии, оставленные редактором статьи. Что сразу приходит в голову? Использовать простой запрос,&lt;/p&gt;

&lt;pre class="python" name="code"&gt;&lt;code&gt;article = Article.objects.get(pk=XXX)
versions = Version.objects.get_for_object(article).\
                           exclude(revision__user=None)
versions = article.editor and versions.filter(revision__user=article.editor) \
                          or versions.none()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;И вроде все просто и безоблачно. Но только до поры до времени, для начала это +1 запрос к БД, а потом код переместиться в цикл или надо будет расширить параметры поиска искать не только ревизии редактора, но и какого-то системного пользователя.&lt;/p&gt;

&lt;p&gt;Выход прост, изначально помнить, что редактор может принимать нулевое значение, а значит обращение к &lt;code&gt;.editor&lt;/code&gt; спровацирует +1 запрос к БД. А значит используем &lt;code&gt;.editor_id&lt;/code&gt; и точно знаем, что здесь искать кита мы не будем.&lt;/p&gt;

&lt;pre class="python" name="code"&gt;&lt;code&gt;article = Article.objects.get(pk=XXX)
versions = Version.objects.get_for_object(article).\
                           exclude(revision__user=None)
versions = \
    article.editor_id and versions.filter(revision__user__pk=article.editor_id) \
                      or versions.none()
&lt;/code&gt;&lt;/pre&gt;

&lt;h1&gt;Метод 3. Предопределение FK значения&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Сразу предупреждаю, пользуйтесь этим методом осторожно и только если уверены, что знаете что делаете :)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Помните пример из первого метода? Когда каждый запрос к полю редактора статьи стоил нам одного запроса. Нехорошо это, ведь и &lt;code&gt;.select_related()&lt;/code&gt; нам тут не помощник. Но, не все так печально, а иначе очень уж просто. Надо всего лишь считать всех пользователей, а потом подставить необходимое значение в аттрибут &lt;code&gt;._editor_cache&lt;/code&gt; (&lt;em&gt;примечание:&lt;/em&gt; на месте &lt;code&gt;editor&lt;/code&gt; может быть любое название вашего FK-поля). А теперь еще раз и в коде:&lt;/p&gt;

&lt;pre class="python" name="code"&gt;&lt;code&gt;articles = Article.objects.select_related('writer')
users = User.objects.all()
users = dict([(user.pk, user) for user in users])

for article in articles:
    article._editor_cache = users.get(article.editor_id)
    print(article.title, article.state, article.writer, article.editor)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;И мы в итоге получаем гарантировано два запроса к БД, вместо &lt;code&gt;Article.objects.сount() + 2&lt;/code&gt; в худшем случае (опять же при условии, что все поля &lt;code&gt;editor&lt;/code&gt; заполнены). Просто и изящно.&lt;/p&gt;

&lt;p&gt;Однако надо помнить, что установив ручками значение в &lt;code&gt;._{{ fk_field }}_cache&lt;/code&gt; именно вы в ответе за него, а никак не Django. И если вдруг редактором статьи вместо Васи Пупкина станет Джон Доу, то вы знаете что делать :)&lt;/p&gt;

&lt;h1&gt;Метод 4. Ручное исполнение запросов&lt;/h1&gt;

&lt;p&gt;Я думаю, многие обрадовались и подумали, ну наконец-то про &lt;code&gt;.raw()&lt;/code&gt;, ну наконец-то про SQL, к черту тот ORM, он и медленный и вообще сложно с ним. Но нет, поспешу разочаровать. Я лишь про то, что во многих случаях может пригодится явное преобразование &lt;code&gt;QuerySet&lt;/code&gt; объектов в что-то более питоновское, как-то &lt;code&gt;list&lt;/code&gt; и в дальнейшем использование именно питоновских функций для выборки данных, например &lt;code&gt;filter()&lt;/code&gt;, а не метода &lt;code&gt;.filter()&lt;/code&gt;. Взвесьте все за и против и помните, что &lt;code&gt;QuerySet&lt;/code&gt; преобразованный в &lt;code&gt;list&lt;/code&gt; &lt;strike&gt;есть не попросит&lt;/strike&gt; к базе уже не обратиться, а обыкновенный еще как сможет.&lt;/p&gt;

&lt;p&gt;Ну и про &lt;code&gt;.raw()&lt;/code&gt; и &lt;code&gt;.extra()&lt;/code&gt; не забывайте. Как говорится, есть моменты когда ваше SQL кунг-фу будет сильнее SQL кунг-фу Django ORM.&lt;/p&gt;

&lt;h1&gt;Метод 5. Или прочее&lt;/h1&gt;

&lt;p&gt;Сразу оговорюсь, что я не пытался упомянуть о всех методах уменьшения кол-ва запросов, а лишь хотел преподнести вам методы наиболее используемые мною при работе с Django. Для дальнейшего просветления я рекомендую вам прочитать &lt;a href="https://docs.djangoproject.com/en/dev/topics/db/optimization/"&gt;раздел про оптимизацию доступа к базе данных&lt;/a&gt; в документации Django (версия для &lt;a href="https://docs.djangoproject.com/en/1.3/topics/db/optimization/"&gt;1.3&lt;/a&gt; или &lt;a href="https://docs.djangoproject.com/en/1.2/topics/db/optimization/"&gt;1.2&lt;/a&gt;), ну или напрямую обращаться к &lt;a href="http://www.google.com/search?q=Django+database+optimization"&gt;Google&lt;/a&gt; или &lt;a href="http://stackoverflow.com/search?q=Django+database+optimization"&gt;StackOverflow&lt;/a&gt; с теми же ключевыми словами, Django database optimization.&lt;/p&gt;

&lt;p&gt;На сим прощаюсь и до новых встреч. Если появились вопросы или я где-то сглупил - милости прошу в комменты!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-8722813901544391808?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/8722813901544391808?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/8722813901544391808?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/S0AdjaAqf0E/django-orm.html" title="Уменьшаем кол-во запросов, которые генерируются Django ORM" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2011/06/django-orm.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cFSXs4fSp7ImA9Wx9TFk0.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-8422619417329206017</id><published>2010-11-24T15:21:00.001+02:00</published><updated>2010-11-24T15:23:38.535+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-24T15:23:38.535+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django 1.2" /><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><category scheme="http://www.blogger.com/atom/ns#" term="custom model fields" /><title>Заставляем кастомные поля для моделей работать под Django 1.2+</title><content type="html">&lt;p&gt;Некоторое время тому назад я разработал несколько кастомных полей для моделей, таких как &lt;a href="http://djangonaut.blogspot.com/2010/03/picklefield.html"&gt;&lt;code&gt;PickleField&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://djangonaut.blogspot.com/2009/02/jsonfield-django.html"&gt;&lt;code&gt;JSONField&lt;/code&gt;&lt;/a&gt; и все было в них хорошо. Но время ведь не стоит на месте и мне захотелось окончательно перейти в своих проектах на прекрасную Django 1.2+ и использовать ее бенефиты вместе с использованием моих старых кастомных полей.&lt;/p&gt;

&lt;p&gt;Для начала я просто попробовал запустить свой проект под Django 1.2 и сразу же получил вылетающие тесты при попытках считать любые данные из базы данных для моделей, содержащих кастомное поле. Ошибкой был обыкновенный &lt;code&gt;ObjectDoesNotExist&lt;/code&gt;, который сразу натолкнул меня на мысль что проблема где-то в &lt;code&gt;get_db_prep_value&lt;/code&gt; методе.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://docs.djangoproject.com/en/1.2/howto/custom-model-fields/#converting-python-objects-to-query-values"&gt;Открыв документацию&lt;/a&gt;, я понял, что был прав. Проблема была в том, что при переходе на поддержку многих БД одновременно был совершен рефакторинг &lt;code&gt;get_db_prep_value&lt;/code&gt; и подобных ему методов. Для них были добавлены аргументы &lt;code&gt;connection, prepared=False&lt;/code&gt; и пользоваться ими рекоммендовалось только в случае БД-зависимого преобразования. А БД-независимые преобразования советовали делать в &lt;code&gt;get_prep_value&lt;/code&gt; и подомных ему методов.&lt;/p&gt;

&lt;p&gt;Так как мои поля не требовали БД-зависимого преобразования, я решил просто переименовать свои &lt;code&gt;get_db_prep_value&lt;/code&gt; методы в &lt;code&gt;get_prep_value&lt;/code&gt;. Результат не заставил себя долго ждать и при следующем перезапуске, тесты моделей, содержащие кастомные поля, прошли без ошибок. Все бы хорошо, но не выбрасывать же поддержку кастомных полей для версий Django ниже 1.2.&lt;/p&gt;

&lt;p&gt;Посему я принялся думать как бы это реализовать. Первым в голову, почему-то пришел дурной вариант о дублировании функционала &lt;code&gt;get_prep_value&lt;/code&gt; в &lt;code&gt;get_db_prep_value&lt;/code&gt;, следующим образом:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;class PickleField(models.Field):
    ...
    def get_db_prep_value(self, value, **kwargs):
        return self.get_prep_value(value)

    def get_prep_value(self, value):
        return pickle.dumps(value)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Да, это решило проблему с прохождением тестов для Django &lt; 1.2, но теперь тесты стали валиться на Django 1.2+. Видно что-то было не так. Вооружившись дуростью, я начал копать в сторону неправильного генерирования SQL-запроса. И так бы продолжалось долго, если бы я не остановился и не подумал еще раз посмотреть на отрефакторенный код &lt;code&gt;get_db_prep_value&lt;/code&gt; метода. После этого проблема стала очевидной, мое предыдущее решение просто дублировало преобразование значения и потому Django не могло найти записи в базе данных и возвращало &lt;code&gt;ObjectDoesNotExist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Что ж после определении проблемы надо было немного подумать над ее решением, но я не стал себя заморачивать и просто для всех кастомных полей и версии Django меньше 1.2, я переименовал методы &lt;code&gt;get_prep_value&lt;/code&gt; в &lt;code&gt;get_db_prep_value&lt;/code&gt; при помощи следующего кода:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;from django import VERSION


if VERSION[0] == 1 and VERSION[1] &lt; 2:
    fields = (JSONField, PickleField)
    for field in fields:
        if not hasattr(field, 'get_prep_value'):
            continue
        field.get_db_prep_value = field.get_prep_value
        del field.get_prep_value
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Последующий перезапуск тестов на 1.0.4 и 1.1.2 завершился успехом, впрочем как и для 1.2.3 и версии из транка.&lt;/p&gt;

&lt;p&gt;Конечно, мне не особо нравится эта магия, но у разработчиков Django похоже не было другого выбора для реализации рефакторинга, посему имеем то, что имеем :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-8422619417329206017?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/8422619417329206017?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/8422619417329206017?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/39djJE-roiY/django-12.html" title="Заставляем кастомные поля для моделей работать под Django 1.2+" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2010/11/django-12.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEINSHw5eSp7ImA9WxFTEEs.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-4004894486610413158</id><published>2010-03-31T15:24:00.009+03:00</published><updated>2010-03-31T22:09:59.221+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-31T22:09:59.221+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><category scheme="http://www.blogger.com/atom/ns#" term="custom model fields" /><title>И опять про PickleField</title><content type="html">&lt;p&gt;&lt;a href="http://djangonaut.blogspot.com/2008/07/django-pickle-field.html"&gt;Давным-давно я написал&lt;/a&gt; более менее работающий пример &lt;code&gt;PickleField&lt;/code&gt;'а - поля для хранения любого питоновского объекта, поддерживающего сериализацию при помощи &lt;code&gt;pickle&lt;/code&gt; или &lt;code&gt;cPickle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;И все бы неплохо, только на днях мне понадобилось хранить в этих &lt;code&gt;PickleField&lt;/code&gt;'ах Django'вские модели, например экземпляры пользователей (&lt;code&gt;auth.User&lt;/code&gt;) или проектов (самописная модель). Да, возможно, это не совсем верно, но задача стояла именно такая.&lt;/p&gt;

&lt;p&gt;Не надеясь получить какой-то подвох я стал просто сохранять эти instance, и вроде бы все работало, пока я не написал смехотворный тест:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;project = choice(Project.objects.all())
user = choice(project.users.all())

PickleModel.objects.create(object_field=project)
obj = PickleModel.objects.get(object_field=project)
obj.object_field = user
obj.save()

obj = PickleModel.objects.get(object_field=user)
obj.delete()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;гдe &lt;code&gt;PickleModel&lt;/code&gt; простая тестовая модель:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;class PickleModel(models.Model):

    object_field = PickleField()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;И каково было мое удивление, когда тест завершился ошибкой &lt;code&gt;PickleModel.DoesNotExist&lt;/code&gt; на 9 строке вместо чистого прохождения. Интересно, сказал я и полез в код, смотреть почему &lt;code&gt;PickleField&lt;/code&gt; при чистом сохранении (&lt;code&gt;PickleModel.objects.create&lt;/code&gt;) правильно сохраняет в базу весь инстанс проекта, а при обновлении тестового объекта (&lt;code&gt;obj.object_field = ...&lt;/code&gt;) сохраняет что-то не то и не так.&lt;/p&gt;

&lt;p&gt;Сначала думал, что ошибка где-то в &lt;code&gt;PickleField&lt;/code&gt;, на секунду даже задумался о использовании &lt;a href="http://github.com/shrubberysoft/django-picklefield"&gt;стороннего решения&lt;/a&gt;. Но потом понял, что сам &lt;code&gt;PickleField&lt;/code&gt; не причем, и что при обновлении тестового объекта не вызывается &lt;code&gt;PickleField.get_db_prep_save&lt;/code&gt;. Интересно, решил я и пошел смотреть на то, как именно проходит процесс обновления объекта в &lt;code&gt;django.db.models.base.Models.save_base&lt;/code&gt;, а оттуда в &lt;code&gt;django.db.models.base.sql.UpdateQuery.add_update_fields&lt;/code&gt;, где собственно и происходит процесс генерирования &lt;code&gt;sql&lt;/code&gt;-запроса для обновления. Там я и нашел причину такого поведения Django.&lt;/p&gt;

&lt;p&gt;Причина оказалась весьма логична c одной стороны. Django для всех своих инстансов вызывает &lt;code&gt;prepare_database_save&lt;/code&gt;, чтобы хранить не инстанс, а его первичный ключ в базе данных. С другой стороны осталось не до конца понятно, почему такого же поведения нет при создании объектов. Впрочем, как именно и где происходит вызов &lt;code&gt;prepare_database_save&lt;/code&gt; для инстансов моделей при создании нового объекта я не проверял, а начал думать как обойти эту особенность Django.&lt;/p&gt;

&lt;p&gt;Для начала решил по манки-патчить по-тупому, добавив в &lt;code&gt;PickleField.to_python&lt;/code&gt;:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;        if hasattr(value, 'prepare_database_save'):
            delattr(value, 'prepare_database_save')
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;на что Питон сказал мне: Are you crazy, man? И добавил &lt;code&gt;AttributeError: prepare_database_save&lt;/code&gt;. Тогда я решил по манки-патчить более умно, в два захода :) Для начала подменил поведение &lt;code&gt;prepare_database_save&lt;/code&gt; в том же &lt;code&gt;PickleField.to_python&lt;/code&gt;:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;        if hasattr(value, 'prepare_database_save'):
            value.prepare_database_save = \
                lambda unused: PickleField().get_db_prep_save(value)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;а затем, в &lt;code&gt;PickleField.get_db_prep_value&lt;/code&gt; возвращал объекту первичное состояния, чтобы его возможно было сериализовать:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;        if hasattr(value, 'prepare_database_save') and \
           isinstance(getattr(value, 'prepare_database_save'),
                      types.LambdaType):
            delattr(value, 'prepare_database_save')
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;После всего этого, запустил исходный тест, порадовался чистому проходу и решил написать вот этот малость бессмысленный псот :)&lt;/p&gt;

&lt;p&gt;Ах да, сам код &lt;code&gt;PickleField&lt;/code&gt; доступен как всегда в &lt;a href="http://github.com/playpauseandstop/kikola/blob/master/kikola/db/fields.py"&gt;kikola.db.fields&lt;/a&gt;, ну или отдельным &lt;a href="http://gist.github.com/350281"&gt;гистом&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;И еще одна забавная возможность &lt;code&gt;PickleField&lt;/code&gt;'a. Благодаря модифицированию стандартного поведения &lt;code&gt;get_default&lt;/code&gt; вы можете указывать любой питоновский объект, как дефолтное значение для этого поля. Например:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;class PickleModel(models.Model):

    dict_field = PickleField(default={'a': 1, 'b': 2, 'c': 3})
    list_field = PickleField(default=[1, 2, 3])
    tuple_field = PickleField(default=(1, 2, 3))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Весьма полезная для меня возможность, подсмотренная в &lt;a href="http://github.com/django-extensions/django-extensions/issues#issue/27"&gt;issues к django-extensions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Апдейт&lt;/h3&gt;

&lt;p&gt;Ха, недолго музыка играла для моего предыдущего решения. И я тому весьма рад ибо оно было уж слишком уродливым. Вообщем, суть проблемы прошлого решения в следующем неработающем тесте:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;project = choice(Project.objects.all())

obj = PickleModel.objects.create(object_field=project)
obj.save()

obj = PickleModel.objects.get(object_field=project)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;а именно в том, что если поле &lt;code&gt;object_field&lt;/code&gt; осталось нетронутым при изменении модели, по при вызове &lt;code&gt;save&lt;/code&gt; все-таки вызовется метод &lt;code&gt;prepare_database_save&lt;/code&gt; и конвертирует модель в строку - первичный ключ.&lt;/p&gt;

&lt;p&gt;Но не беда, манки-патчим &lt;code&gt;django.db.models.Model.prepare_database_save&lt;/code&gt; и получаем работающий тест и код:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;# Make able to store Django model objects in ``PickleField``
def picklefield(func):
    def wrapper(obj, field):
        if isinstance(field, PickleField):
            return field.get_db_prep_save(obj)
        return func(obj, field)
    return wrapper


models.Model.prepare_database_save = \
    picklefield(models.Model.prepare_database_save)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Хотя, по правде говоря, и это решение мне не очень нравится. Dirty hack, не иначе.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-4004894486610413158?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/4004894486610413158?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/4004894486610413158?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/mP5-zBHxKnE/picklefield.html" title="И опять про PickleField" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2010/03/picklefield.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cBQXs9fCp7ImA9WxBXE0U.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-23577046976888302</id><published>2010-01-23T14:44:00.002+02:00</published><updated>2010-01-25T02:57:30.564+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-01-25T02:57:30.564+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="virtualenv" /><category scheme="http://www.blogger.com/atom/ns#" term="pip" /><category scheme="http://www.blogger.com/atom/ns#" term="bootstrap" /><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><title>Разворачиваем проект при помощи virtualenv и pip</title><content type="html">&lt;p&gt;Наверное, нет смысла подробно останавливаться на том, что такое &lt;a href="http://virtualenv.openplans.org/"&gt;virtualenv&lt;/a&gt; или &lt;a href="http://pip.openplans.org/"&gt;pip&lt;/a&gt;, про эти трендовые понятия питоньего мира написана уже не одна статья. Так что сегодня, я просто поделюсь способом разворачивания проекта основуясь на этих технологиях.&lt;/p&gt;

&lt;p&gt;Итак, на самом деле все просто. Для начала надо создать новое виртуальное окружение, а затем установить туда все зависимости. Также было бы неплохо получить &lt;code&gt;Makefile&lt;/code&gt; со всеми необходимыми целями, которые будут облегчать работу с проектом.&lt;/p&gt;

&lt;p&gt;Конечно, все это можно делать и руками для каждого следующего проекта. Благо запоминать там немного:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ virtualenv ENV&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;да:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ pip install -E ENV -r REQUIREMENTS.pip&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;где &lt;code&gt;REQUIREMENTS.pip&lt;/code&gt; - файл со всеми необходимыми зависимостями для проекта. Ну а потом прописать прямо в &lt;code&gt;Makefile&lt;/code&gt; или в &lt;code&gt;Makefile.def&lt;/code&gt; путь к "новому" Python'у, в нашем случае это может выглядеть как-то так:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PYTHON=ENV/bin/python&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Но, когда новые проекты начинают сыпаться как из рога изобилия хочется автоматизировать все эти действия. Для этого я на коленке написал &lt;a href="http://gist.github.com/285542"&gt;bootstrap скрипт&lt;/a&gt;. Разберемся с тем, что он делает.&lt;/p&gt;

&lt;p&gt;Во-первых, его нужно сохранить в директорию с проектом (это ограничение я думаю преодолеть по-позже, когда разберусь с созданием проекта с шаблона).&lt;/p&gt;

&lt;p&gt;Во-вторых, для настройки тех или иных параметров скрипта, например, названия файла с зависимостями или имени нового виртуального окружения, вы можете создать файл &lt;code&gt;bootstrap.cfg&lt;/code&gt; в директории проекта. Скрипт попытается считать настройки оттуда и обновит их.&lt;/p&gt;

&lt;p&gt;В-третьих, скрипт создат для Вас новое виртуальное окружение, если оно еще не создано. По умолчанию, это виртуальное окружение создатся с опциями &lt;code&gt;--no-site-packages --unzip-setuptools&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;В-четвертых, скрипт попытается создать &lt;code&gt;Makefile&lt;/code&gt;, используя шаблонный файл &lt;code&gt;Makefile.template&lt;/code&gt;, который должен быть ранее создан в директории проекта.&lt;/p&gt;

&lt;p&gt;В-пятых, скрипт установит все зависимости, находящиеся, по умолчанию, в файле &lt;code&gt;REQUIREMENTS.pip&lt;/code&gt; и сохранит все скачанные архивы с питоньими пакетами в &lt;code&gt;ENV/src&lt;/code&gt;. Формат файлов зависимостей описан в документации к pip.&lt;/p&gt;

&lt;p&gt;Собственно все. После обновления зависимостей или шаблона Makefile - просто выполните &lt;code&gt;bootstrap.py&lt;/code&gt; еще раз and have fun.&lt;/p&gt;

&lt;p&gt;Как уже говорил, в будущем хочется разобраться с созданием проекта с шаблона, чтобы тратить как можно меньше времени на начало каждого следующего проекта.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-23577046976888302?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/23577046976888302?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/23577046976888302?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/9bq_RsGFsDM/virtualenv-pip.html" title="Разворачиваем проект при помощи virtualenv и pip" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2010/01/virtualenv-pip.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0cGQX05eip7ImA9WxNaF00.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-6402180035339256717</id><published>2009-12-02T00:14:00.009+02:00</published><updated>2009-12-02T01:10:20.322+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-02T01:10:20.322+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python string methods" /><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><category scheme="http://www.blogger.com/atom/ns#" term="django template" /><title>Используем встроенные строковые методы Python'а в Django шаблонах</title><content type="html">&lt;h3&gt;Вместо предисловия&lt;/h3&gt;

&lt;p&gt;Привет! Давно здесь не отписывался. Почему? Наверное главная причина, что после выхода 1.1 версии уже не так активно слежу за развитием Django. Может быть в ближайшее время меня пробъет на творчество и я выдам пару-тройку новых постов, но не обещаю ;)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Но это я отвлекся от темы поста. А она заключается вот в чем. Надо было сегодня в шаблонах Django для некоторых урлов убрать конечные слеши, т.е. просто вызвать &lt;code&gt;url.rstrip('/')&lt;/code&gt;. Просмотрев в который раз список всторенных шаблонных фильтров и не обнаружив там нужного, я задумался: как быть? Создавать простой фильтр, типа:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;from django.template import Library
from django.template.defaultfilters import stringfilter


register = Library()


@register.filter
@stringfilter
def rstrip(text, chars=None):
    return text.rstrip(chars)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;совершенно не хотелось. Ибо вдруг мне в будущем захочется добавить поддержку &lt;code&gt;lstrip&lt;/code&gt; или &lt;code&gt;strip&lt;/code&gt; метода. Что надо будет морочиться с Ctrl+C, Ctrl+V и минимальными исправлениями отяжеляя эту темплейт библиотеку?&lt;/p&gt;

&lt;p&gt;Потому не долго думая и вроде бы не отыскав необходимого мне решения в гугле, я решил повелосипедить и добавить возможность вызова всех строковых методов Python в Django шаблоне.&lt;/p&gt;

&lt;p&gt;Первая моя идея была очень проста, перебрать все публичные методы пустой строки и зарегестрировать их как шаблонный фильтр, используя lambda. Переходя на код, это приобрело примерно следующий вид:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;from django.template import Library
from django.template.defaultfilters import stringfilter


register = Library()


for name in dir(u''):
    if name.startswith('_'): continue

    filter = lambda value, *args: getattr(unicode(value), name)(*args)
    filter = stringfilter(filter)

    register.filter(name, filter)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Написав простой тест-кейс я уже было приготовился доставать шампанское и переходить к следующей задаче, но не тут-то было :( &lt;code&gt;TemplateSyntaxError&lt;/code&gt; для &lt;code&gt;{{ url|strip:"/" }}&lt;/code&gt; заставила притормозить коней. Почему же так произошло? Я пошел к &lt;a href="http://code.djangoproject.com/browser/django/tags/releases/1.1/django/template/__init__.py#L608"&gt;месту ошибки&lt;/a&gt; и понял, что проблема в &lt;code&gt;*args&lt;/code&gt;, точнее в том, что &lt;code&gt;django.template.FilterExpression.args_check&lt;/code&gt; ожидает определенный набор аргументов, а не, наоборот, не определенный заранее.&lt;/p&gt;

&lt;p&gt;Что ж, пришлось усложнять код. Для начала я добавил поддержку только трех аргументов, ибо это максимальноя кол-во используемых аргументов для любого строкового метода, кроме &lt;code&gt;format&lt;/code&gt;. Затем переписал предыдущую безымянную функцию в:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;@stringfilter
def make_filter(name):
    def filter(value, first=None, second=None, third=None):
        args = [first, second, third]
        method = getattr(force_unicode(value), name)

        while True:
            try:
                return method(*args)
            except TypeError:
                args.pop(len(args) - 1)

    return filter
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;и заодно переписал регистрацию этого фильтра. Запустил тест-кейс, получил Ran OK! и на свою голову решил усложнить тест-кейс, проверив работу фильтра с двумя аргументами, например, &lt;code&gt;{{ "abcdef"|replace:"abc":"def" }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Каково же было мое удивление, когда Django сказала, нет много аргументов для фильтров - это не хорошо. И сгенерировала очередную &lt;code&gt;TemplateSyntaxError&lt;/code&gt;. Что ж, пришлось реализовывать это в виде отдельного простого тега &lt;code&gt;{% stringmethod %}&lt;/code&gt;. Принцип его работы простейший, как видно из кода:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&lt;code&gt;@register.simple_tag
def stringmethod(name, value, first=None, second=None, third=None):
    return make_filter(name)(value, first, second, third)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;В свою очередь это повлекло за собой обновление теста, и &lt;code&gt;{{ "abcdef"|replace:"abc":"def" }}&lt;/code&gt; превратилось в &lt;code&gt;{% stringmethod "replace" "abcdef" "abc" "def" %}&lt;/code&gt;. Монструозно, соглашусь, но что поделаешь. В итоге, получив Ran OK! я немного подправил документацию и выложил это все дело, как &lt;a href="http://gist.github.com/246727"&gt;отдельный гист на гитхаб&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Пользуйтесь, возможно вам это пригодится!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; На последок упомяну еще о пару особенностях &lt;code&gt;stringmethods&lt;/code&gt;. Во-первых, она не переписывает встроенный &lt;code&gt;join&lt;/code&gt; фильтр, во-вторых, она плохо справляется с &lt;code&gt;format&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зыы.&lt;/strong&gt; Если вы знаете reusable apps решающие похожие проблемы - не стесняйтесь писать в комменты :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-6402180035339256717?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6402180035339256717?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6402180035339256717?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/YZcWSrboUYE/python-django.html" title="Используем встроенные строковые методы Python'а в Django шаблонах" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/12/python-django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUYBRXczfyp7ImA9WxJREUw.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-3546187624452278144</id><published>2009-05-12T11:12:00.007+03:00</published><updated>2009-05-12T11:32:34.987+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-12T11:32:34.987+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tddspry" /><category scheme="http://www.blogger.com/atom/ns#" term="documentation" /><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><category scheme="http://www.blogger.com/atom/ns#" term="github" /><category scheme="http://www.blogger.com/atom/ns#" term="sphinx" /><title>Sphinx documentation + GitHub pages = &lt;3</title><content type="html">&lt;p&gt;Вообщем буду краток ;)&lt;/p&gt;

&lt;p&gt;Для документирования питоньих проектов очень удобно использовать &lt;a href="http://sphinx.pocoo.org/"&gt;Sphinx&lt;/a&gt;. Это всем известно. А если не известно, то и &lt;a href="http://docs.python.org/"&gt;Python&lt;/a&gt;, и &lt;a href="http://docs.djangoproject.com/"&gt;Django&lt;/a&gt; документированы именно им.&lt;/p&gt;

&lt;p&gt;На &lt;a href="http://github.com/"&gt;GitHub&lt;/a&gt;'е есть возможность размещения &lt;acronym title="HyperText Markup Language"&gt;HTML&lt;/acronym&gt; страниц проекта и последующего доступа к ним по адресу: &lt;code&gt;http://&lt;strong&gt;username&lt;/strong&gt;.github.com/&lt;strong&gt;repo_name&lt;/strong&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Само собой разумеется, было бы очень неплохо подружить их. И посему я на коленке написал следующую &lt;code&gt;Makefile&lt;/code&gt;-команду:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;project=YOUR_PROJECT_NAME
docs_dir=$(TMPDIR)/$(project)-docs

ghdocs:
    rm -rf $(docs_dir)
    $(MAKE) -C docs html
    cp -r docs/_build/html $(docs_dir)
    mv $(docs_dir)/_static $(docs_dir)/static
    mv $(docs_dir)/_sources $(docs_dir)/sources
    perl -pi -e "s/_sources/sources/g;" $(docs_dir)/*.html
    perl -pi -e "s/_static/static/g;" $(docs_dir)/*.html
    git checkout gh-pages
    rm -r sources static
    cp -rf $(docs_dir)/* .
    git add .
    git commit -a -m 'Updates $(project) documentation.'
    git checkout master
    rm -rf $(docs_dir)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Теперь по вызову &lt;code&gt;make ghdocs&lt;/code&gt; я обновляю документацию после каждого релиза &lt;a href="http://github.com/playpauseandstop/tddspry"&gt;&lt;strong&gt;tddspry&lt;/strong&gt;&lt;/a&gt;, которая теперь и впредь &lt;a href="http://playpauseandstop.github.com/tddspry/"&gt;доступна всем&lt;/a&gt; ;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; После коммита документацию можно было бы сразу пушить в репо, но иногда не бывает интернета под рукой, иногда очепятку найдешь уже после коммита, посему я потом вручную перехожу на &lt;code&gt;gh-pages&lt;/code&gt; и делаю &lt;code&gt;git push origin gh-pages&lt;/code&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-3546187624452278144?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3546187624452278144?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3546187624452278144?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/wQHCtWrg16A/sphinx-documentation-github-pages-3.html" title="Sphinx documentation + GitHub pages = &lt;3" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/05/sphinx-documentation-github-pages-3.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUUARng6fCp7ImA9WxJTFk0.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-6486982600405783456</id><published>2009-04-24T21:40:00.008+03:00</published><updated>2009-04-24T23:00:47.614+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-24T23:00:47.614+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tddspry" /><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><category scheme="http://www.blogger.com/atom/ns#" term="testing" /><title>Test or die!</title><content type="html">&lt;p&gt;Сегодня я расскажу вам о тестах. Нет, я не буду рассказывать, почему это важно или зачем это нужно делать. Об этом вы прочтете у других авторов. Я же расскажу вам при помощи чего я тестирую свои Django-проекты и приложения.&lt;/p&gt;

&lt;p&gt;Итак, знакомтесь, &lt;strong&gt;&lt;a href="http://github.com/playpauseandstop/tddspry"&gt;tddspry&lt;/a&gt;&lt;/strong&gt; - альтернатива стандартному подходу в тестировании Django приложений.&lt;/p&gt;

&lt;p&gt;Как мы знаем из &lt;a href="http://docs.djangoproject.com/en/dev/topics/testing/#topics-testing"&gt;документации&lt;/a&gt; и практики для тестирования приложения надо:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Собственно написать док или юнит-тест. Если это юнит-тест, то разместить его в &lt;code&gt;tests.py&lt;/code&gt; или &lt;code&gt;tests/&lt;/code&gt; приложения (при этом надо помнить, что в этом приложении должен присутствовать хотя бы пустой &lt;code&gt;models.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Выполнить тесты при помощи &lt;code&gt;./manage.py test app&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Вроде бы ничего сложного или религиозно неправильного нет. Но, я просто не привык к ним. Не привык и точка. Мне намного проще тестировать приложения при помощи &lt;a href="http://code.google.com/p/python-nose"&gt;python-nose&lt;/a&gt;, а вместо стандартного &lt;a href="http://docs.djangoproject.com/en/dev/topics/testing/#default-test-client"&gt;django.test.Сlient&lt;/a&gt; использовать &lt;a href="http://twill.idyll.org/"&gt;twill&lt;/a&gt;-овский браузер.&lt;/p&gt;

&lt;p&gt;Не буду вдаваться в подробности почему так произошло, но, поверьте, я рад этому до сих пор. Единственное, что меня не устраивало в последнее время это кое-какая монструозность нашей библиотеки для тестирования (которая tddspry и зовется ;) ). И посему я решил вспомнить и применить на практике значение слова рефакторинг. Прошел процесс быстро и практически безболезнено и за три неплоных дня мы имеем практически, да чего уж там таить, фактически новое лицо для нее.&lt;/p&gt;

&lt;p&gt;Итак, что-же сейчас умеет &lt;strong&gt;tddspry&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Есть три базовых тест-кейса:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tddspry.NoseTestCase&lt;/code&gt; - этот тест-кейс предназначается для любых тестов и по-большому счету является нашим ответом &lt;strike&gt;Чемберлену&lt;/strike&gt; &lt;code&gt;unittest.TestCase&lt;/code&gt;. Его особенности: он содержит все декораторы из &lt;a href="http://code.google.com/p/python-nose/wiki/TestingTools"&gt;&lt;code&gt;nose.tools&lt;/code&gt;&lt;/a&gt; как статические методы класса, а все прочие функции из того же &lt;code&gt;nose.tools&lt;/code&gt; как методы объекта.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tddspry.django.DatabaseTestCase&lt;/code&gt; - этот тест-кейс предназначается для тестов в которых необходимо использовать базу данных. Как и в стандартном поведении &lt;code&gt;unittest.TestCase&lt;/code&gt; он создает или просто настраивает для тестов: а) базу данных &lt;code&gt;sqlite3&lt;/code&gt; в оперативной памяти; б) текущую базу данных из &lt;code&gt;settings&lt;/code&gt; файла проекта; в) тестовую базу данных. Самым быстрым для тестов является вариант создания &lt;code&gt;sqlite3&lt;/code&gt; базы данных в оперативной памяти и именно он используется по умолчанию. Но никто не говорит, что вы не сможете протестировать любой другой адаптер. Для этого просто укажите &lt;code&gt;database_name&lt;/code&gt; аттрибут в вашем тест-кейсе, наследующем &lt;code&gt;DatabaseTestCase&lt;/code&gt;. Также, аналогично к джанговским тест-кейсам, &lt;code&gt;DatabaseTestCase&lt;/code&gt; умеет загружать фикстуры. И напоследок этот тест-кейс содержит три безумно простых метода для проверки создания, удаления и исправления модели: они же &lt;code&gt;check_create&lt;/code&gt;, &lt;code&gt;check_delete&lt;/code&gt;, &lt;code&gt;check_update&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tddspry.django.HttpTestCase&lt;/code&gt; - этот тест-кейс предназначается для тестов серверной части вашего Django проекта при помощи &lt;a href="http://twill.idyll.org/"&gt;twill&lt;/a&gt;-браузера. Почему twill? Потому что он простой и он на Python'e  ;) Единственным существенным его недостатком считаю отсутствие простой функции для кастомного (не &lt;code&gt;GET&lt;/code&gt;-запроса), посему в тестировании &lt;code&gt;POST&lt;/code&gt; или &lt;code&gt;PUT&lt;/code&gt; запросов вам прийдется дополнительно использовать или джанговский тест-клиент или просто напросто &lt;code&gt;urllib2&lt;/code&gt;. Так, но это мы отвлеклись. Что еще особенного в &lt;code&gt;HttpTestCase&lt;/code&gt;? Да то, что он содержит в себе все функции из &lt;code&gt;twill.commands&lt;/code&gt;, а также кое-какие стоящие хелперы, как то &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;login_to_admin&lt;/code&gt;, &lt;code&gt;logout&lt;/code&gt;, из названия которых становится ясно, что они делают. Также упрощен интерфейс перехода по урлам, в функцию &lt;code&gt;go&lt;/code&gt; можна передать как уже чистый урл, так и только имя его урлпаттерна, которое будет сконвертировано в чистый урл при помощи &lt;code&gt;django.core.urlresolvers.reverse&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Есть пару воспомогательных хелперов как-то:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tddspry.django.helpers.create_profile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tddspry.django.helpers.create_staff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tddspry.django.helpers.create_superuser&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tddspry.django.helpers.create_user&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
из названия которых вроде бы опять становится ясно, что они делают.&lt;/li&gt;
&lt;li&gt;Есть крутой воспомогательный хелпер для проверки работы регистрационного механизма &lt;a href="http://code.google.com/p/django-registration"&gt;django-registration&lt;/a&gt;. Он зовется &lt;code&gt;tddspry.django.helpers.registration.registration&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Есть прелесный декоратор &lt;code&gt;show_on_error&lt;/code&gt;, который автоматически включен для каждого тестового метода в &lt;code&gt;HttpTestCase&lt;/code&gt;. Его прелесть заключается в том, что при ошибке twill'a он показывает содержимое страницы на которой произошла ошибка или при наличии переменной окружения &lt;code&gt;TWILL_ERROR_DIR&lt;/code&gt; сохраняет в этой директории html-страницу с ошибкой.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Для установки библиотеки, достаточно просто выполнить &lt;code&gt;sudo easy_install tddspry&lt;/code&gt;. А для непосредственного тестирования вашего Джанго-проекта или приложения необходимо присвоить переменной окружения &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; значение актуальных настроек и сказать &lt;code&gt;nosetests&lt;/code&gt; :)&lt;/p&gt;

&lt;p&gt;Для окончательного примера покажу, какой командой тестируется сам &lt;code&gt;tddspry&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PYTHONPATH=/srv/projects/tddspry-github:. DJANGO_SETTINGS_MODULE=testproject.settings nosetests  -w .. --with-coverage --cover-package=tddspry --exe testproject&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Так что, надеюсь, что моя информация будет вам полезна и вы будете следить за дальнейшим развитием &lt;code&gt;tddspry&lt;/code&gt;. Ведь в планах у меня есть поддержка Django 1.1, использование &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt; или &lt;a href="http://www.getwindmill.com/"&gt;Windmill&lt;/a&gt; для клиентских тестов и куча прочих полезностей.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Совсем забыл ;) Примеры тестов, написанных при помощи &lt;code&gt;tddspry&lt;/code&gt;, вы можете найти &lt;a href="http://github.com/playpauseandstop/tddspry/tree/ae9f22c7700e9a215cd540d7369d3f2822738d4b/testproject/testapp/tests"&gt;где-то здесь&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;зыы.&lt;/strong&gt; Особенная благодарность Almad'у, автору &lt;a href="http://devel.almad.net/trac/django-sane-testing/"&gt;&lt;code&gt;django-sane-testing&lt;/code&gt;&lt;/a&gt;, за стимул к полному рефакторингу &lt;code&gt;tddspry&lt;/code&gt;. Краткое объяснение: он написал мне в личку на гитхабе, мол так и так, я уже сделал практически то же самое только с Селениумом и без Твилла, посмотри может тебе пригодится. Я посмотрел на его тесты и я окончательно понял, что надо улучшать &lt;code&gt;tddspry&lt;/code&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-6486982600405783456?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6486982600405783456?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6486982600405783456?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/dgYmU89w038/test-or-die.html" title="Test or die!" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/04/test-or-die.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8HRH4zeip7ImA9WxJTEEg.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-3983136631334507316</id><published>2009-04-18T15:44:00.003+03:00</published><updated>2009-04-18T15:47:15.082+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-18T15:47:15.082+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="kikola" /><category scheme="http://www.blogger.com/atom/ns#" term="search" /><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps" /><title>Давайте просто поищем!</title><content type="html">&lt;p&gt;Как Вы, наверное, знаете поиск и Django - понятия весьма и весьма
родные. Каких только приложений не написали для этих целей: и &lt;a
href="http://code.google.com/p/djapian/"&gt;djapian&lt;/a&gt;, и и &lt;a
href="http://code.google.com/p/django-solr-search/"&gt;solango&lt;/a&gt;, и
даже &lt;a href="http://github.com/toastdriven/django-haystack/tree"&gt;haystack&lt;/a&gt;.
И это все хорошо, и замечательно, но что делать если нужен простой,
совсем простой, поиск по сайту без использования дополнительных
поисковых движков? Ответ прост: писать свой очередной велосипед. Что я
с удовольствием и сделал!&lt;/p&gt;

&lt;p&gt;Итак, знакомтесь: &lt;a
href="http://github.com/playpauseandstop/kikola/tree/5e361cc67f359d0eac9ee963cac4a4bbdd747158/kikola/contrib/basicsearch"&gt;kikola.contrib.basicsearch&lt;/a&gt;
- приложение для легковесного поиска по любым моделям в вашем
проекте.&lt;/p&gt;

&lt;p&gt;Для того, чтобы быстро и ясно понять, что оно умеет и как оно
работает, предлогаю следующую задачу: в проекте используются
стандартная джанговская модель &lt;a
href="http://code.djangoproject.com/browser/django/trunk/django/contrib/flatpages/models.py"&gt;&lt;code&gt;FlatPage&lt;/code&gt;&lt;/a&gt;
и самописная &lt;a href="#samplemodel-code"&gt;&lt;code&gt;SampleModel&lt;/code&gt;&lt;/a&gt;.
Надо организовать поиск по им, причем так, чтобы результаты из
&lt;code&gt;SampleModel&lt;/code&gt; печатались раньше, чем результаты из
&lt;code&gt;FlatPage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Решение:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Добавляем &lt;code&gt;kikola.contrib.basicsearch&lt;/code&gt; в
&lt;code&gt;INSTALLED_APPS&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Настраиваем &lt;code&gt;SEARCH_MODELS&lt;/code&gt; там же, в &lt;code&gt;settings&lt;/code&gt;'ах:
&lt;pre name="code" class="py"&gt;SEARCH_MODELS = {
   'flatpages.FlatPage': {
       'description': '{{ obj.content|truncatewords_html:20 }}',
       'fields': ('title', 'content'),
       'priority': 0,
       'title': '{{ obj.title }}',
   },
   'sample.SampleModel': {
       'description': '{{ obj.overview|truncatewords_html:20 }}',
       'fields': ('name', 'slug', 'overview'),
       'priority': 100,
       'trigger': lambda obj: obj.is_active,
   },
}
&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Добавляем &lt;code&gt;kikola.contrib.basicsearch.urls&lt;/code&gt; в
&lt;code&gt;ROOT_URLCONF&lt;/code&gt;-модуль;&lt;/li&gt;
&lt;li&gt;???&lt;/li&gt;
&lt;li&gt;Profit!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Иными словами, сейчас запустив тестовый сервер проекта и зайдя на
127.0.0.1:8000/search/ перед нами будет страничка с поисковой формой в
которой мы сможем поискать и по всем &lt;code&gt;FlatPage&lt;/code&gt;, и по
только активным &lt;code&gt;SampleModel&lt;/code&gt;.&lt;/p&gt;

&lt;p id="samplemodel-code"&gt;&lt;strong&gt;зы.&lt;/strong&gt; Код &lt;code&gt;SampleModel&lt;/code&gt;:&lt;/p&gt;
&lt;pre name="code" class="py"&gt;from django.db import models
from django.db.models import permalink
from django.utils.translation import ugettext_lazy as _


class SampleModel(models.Model):

   name = models.CharField(_('name'), max_length=64)
   slug = models.CharField(_('slug'), max_length=64)
   overview = models.TextField(_('overview'), blank=True)
   is_active = models.BooleanField(_('actived'), blank=True, default=True)

   def __unicode__(self):
       return self.name

   def get_absolute_url(self):
       return ('sample_urlname', [self.slug])
   get_absolute_url = permalink(get_absolute_url)
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-3983136631334507316?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3983136631334507316?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3983136631334507316?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/IxXTdlKXkyY/blog-post.html" title="Давайте просто поищем!" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/04/blog-post.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEGR3g7eyp7ImA9WxVWFkQ.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-827164353767497539</id><published>2009-02-27T01:39:00.003+02:00</published><updated>2009-02-27T01:57:06.603+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-27T01:57:06.603+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="mediafiles" /><category scheme="http://www.blogger.com/atom/ns#" term="my projects" /><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps" /><title>django-mediafiles 0.2</title><content type="html">&lt;p&gt;Сегодня оффициально вышел первый публичный релиз моего проекта, под названием &lt;strong&gt;django-mediafiles&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://github.com/playpauseandstop/django-mediafiles"&gt;Страница проекта на GitHub (код)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/p/django-mediafiles/"&gt;Страница проекта на Google Code (багтрекер и вики)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://pypi.python.org/pypi/django-mediafiles/"&gt;Страница проекта на CheeseShop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;О чем этот проект уже практически понятно из его названия, а чтобы рассеять последние ваши сомнения, посвечу скриншотами того, что он умеет:&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;&lt;strong&gt;Главная страница&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/fz_ZztfwRGq2-D_whq8jFA?feat=directlink"&gt;&lt;img src="http://lh3.ggpht.com/_wPsSJz8bPyE/Sacc3-5QuXI/AAAAAAAAAjc/YWYLQr6YpfU/s800/mediafiles_index.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/dd&gt;

&lt;dt&gt;&lt;strong&gt;Создание новой директории или файла, загрузка файла на сервер&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/nI6hKrH6sAj51wIDjmTO2w?feat=directlink"&gt;&lt;img src="http://lh6.ggpht.com/_wPsSJz8bPyE/Sacc30xDMhI/AAAAAAAAAjk/Rizbq2xvhVw/s800/mediafiles_make_directory.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/IhvStWlUgE2yaeMLZQAvSg?feat=directlink"&gt;&lt;img src="http://lh3.ggpht.com/_wPsSJz8bPyE/Sacc3-XY15I/AAAAAAAAAjs/UNxGwDWH2AY/s800/mediafiles_make_file.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/WjIOL5uAlRU2IU6PJxwlQQ?feat=directlink"&gt;&lt;img src="http://lh4.ggpht.com/_wPsSJz8bPyE/SacdDFqIu7I/AAAAAAAAAkE/bS1295GUPqg/s800/mediafiles_upload_file.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;

&lt;dt&gt;&lt;strong&gt;Удаление директории&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/gFarZC6km3ea4xJKd_Ed1w?feat=directlink"&gt;&lt;img src="http://lh3.ggpht.com/_wPsSJz8bPyE/Sacc3kt5JzI/AAAAAAAAAjM/MwlDXNxoNmk/s800/mediafiles_delete_directory.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;

&lt;dt&gt;&lt;strong&gt;Предпросмотр рисунков, кода или редактирование текстовых файлов&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/j95VkVIvDUF1-ombqnbWoA?feat=directlink"&gt;&lt;img src="http://lh3.ggpht.com/_wPsSJz8bPyE/SacdC8AwY_I/AAAAAAAAAj8/5N5L4aLTxlU/s800/mediafiles_preview_image.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/YE9GHTyPS47SHK3D2_0LDA?feat=directlink"&gt;&lt;img src="http://lh5.ggpht.com/_wPsSJz8bPyE/SacdCx0e-AI/AAAAAAAAAj0/RPID3JYeUsU/s800/mediafiles_preview_file.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://picasaweb.google.com/lh/photo/d-Vf5emPAkLtKX2VDui2bw?feat=directlink"&gt;&lt;img src="http://lh5.ggpht.com/_wPsSJz8bPyE/Sacc3nWHXcI/AAAAAAAAAjU/0hTbl_4Z6ws/s800/mediafiles_edit_file.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Надеюсь, что кому-то этот проект пригодится. &lt;strong&gt;Enjoy!&lt;/strong&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-827164353767497539?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/827164353767497539?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/827164353767497539?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/nUn2y071Olo/django-mediafiles-02.html" title="django-mediafiles 0.2" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh3.ggpht.com/_wPsSJz8bPyE/Sacc3-5QuXI/AAAAAAAAAjc/YWYLQr6YpfU/s72-c/mediafiles_index.png" height="72" width="72" /><feedburner:origLink>http://djangonaut.blogspot.com/2009/02/django-mediafiles-02.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQMQXc8fip7ImA9Wx9RGEs.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-4145515560492064628</id><published>2009-02-22T01:43:00.007+02:00</published><updated>2010-12-20T20:06:20.976+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-20T20:06:20.976+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django orm" /><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><title>Джанго дб моделс кью - ай лав ю!</title><content type="html">&lt;p&gt;Значится, &lt;strong&gt;дано&lt;/strong&gt; следующий фрагмент &lt;code&gt;models.py&lt;/code&gt;:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;class Attribute(models.Model):
    ATTRIBUTE_PERMISSIONS = (
        ('public', _('Public')),
        ('semi-private', _('Semi-Private')),
        ('private', _('Private'))
    )

    property_ = models.ForeignKey('Property')
    type_ = models.ForeignKey('AttributeType')
    value = models.CharField(max_length=255)
    permission = models.PositiveIntegerField(choices=ATTRIBUTE_PERMISSIONS,
        default=ATTRIBUTE_PUBLIC)
    granted_users = models.ManyToManyField('auth.User', blank=True, null=True)

class AttributeType(models.Model):
    slug = models.SlugField(max_length=32)
    """
    Other implementation of AttributeType class was stripped
    """

class Property(models.Model):
    owner = models.ForeignKey('auth.User')
    """
    Other implementation of Property class was stripped
    """
&lt;/pre&gt;

&lt;p&gt;Теперь, внимание, &lt;strong&gt;задание&lt;/strong&gt;: надо найти все объекты &lt;code&gt;Property&lt;/code&gt;, у которых:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;значение публичного аттрибута &lt;code&gt;attr1&lt;/code&gt; равно &lt;code&gt;"Public"&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;значение полу-приватного аттрибута &lt;code&gt;attr2&lt;/code&gt; равно &lt;code&gt;"Semi-Private"&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;значение приватного аттрибута &lt;code&gt;attr2&lt;/code&gt; равно &lt;code&gt;"Private"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Казалось бы, как сложно и тут будет много запросо к базе данных, ведь это ж &lt;code&gt;Django&lt;/code&gt;, это ж никому не нужный &lt;acronym title="Object-relational mapping"&gt;ORM&lt;/acronym&gt;. Вот если бы настрочить большой и сложный &lt;acronym title="Structured Query Language"&gt;SQL&lt;/acronym&gt;, эх. Но, вот именно тут на арену и выходит &lt;a href="http://code.djangoproject.com/browser/django/trunk/django/db/models/query_utils.py#L23"&gt;&lt;code&gt;django.db.models.Q&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Судите сами,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;для того чтобы отфильтровать проперти по значению публичного аттрибута, нам надо просто отфильтровать проперти по &lt;code&gt;.filter(attribute_type___slug='attr1', attribute__permission='public', attribute__value='Public')&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;для фильтра по значению полу-приватного аттрибута, надо дополнительно ввести фильтр проверки на вхождение пользователя в список разрешенных пользователей или этот пользователь является автором проперти: &lt;code&gt;.filter(attribute_type___slug='attr2', attribute__permission='semi-private', attribute__value='Semi-Private', attribute__granted_users=USER).filter(attribute_type___slug='attr2', attribute__permission='semi-private', attribute__value='Semi-Private', owner=USER)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;для фильтра же по значению приватного аттрибута, мы просто оставляем вторую часть предыдущего фильтра: &lt;code&gt;.filter(attribute_type___slug='attr2', attribute__permission='private', attribute__value='Private', owner=USER)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Как-то слишком монструозно выходит, просто фильтровать. Давайте, лучше создадим хелпер, который будет учитывать особенности всех этих фильтров, проще говоря склеим все фильтры воедино при помощи &lt;code&gt;Q&lt;/code&gt;. А затем просто будем фильтровать проперти по названию аттрибута и его значению:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;def filter_by_attribute(queryset, **kwargs):
    # Public attributes filter
    basequery = Q(attribute__permission='public')

    if 'user' in kwargs:
        user = kwargs.pop('user')

        # Semi-private attributes filter
        basequery |= Q(attribute__permission='semi-private') &amp; \
                     Q(Q(attribute__granted_users=user) | \
                       Q(owner=user))

        # Private attributes filter
        basequery |= Q(attribute__permission='private') &amp; Q(owner=user)

    for slug, value in kwargs.items():
        query = basequery &amp; \
                Q(attribute__type___slug=slug) &amp; \
                Q(attribute__value=value)

        queryset = queryset.filter(query)

    return queryset

queryset = Property.objects.all()
queryset = filter_by_attribute(queryset, attr1='Public', attr2='Semi-Private', user=USER)
queryset = filter_by_attribute(queryset, attr2='Private', user=USER)&lt;/pre&gt;

&lt;p&gt;Вот и все, мы отфильтровали все проперти по необходимым значениям аттрибутов, в то же время сохранив для этих аттрибутов сменные права доступа. И да, выборку всех этих проперти выполнил один большой запрос, сгенерированный джанговским &lt;acronym title="Object-relational mapping"&gt;ORM&lt;/acronym&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Я знаю, что &lt;code&gt;filter_by_attribute&lt;/code&gt; лучше было бы прицепить к кастомному &lt;code&gt;PropertyManager&lt;/code&gt;'у, но это выходит за рамки моей сегодняшней темы ;)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-4145515560492064628?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/4145515560492064628?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/4145515560492064628?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/poHWM_ippRs/blog-post.html" title="Джанго дб моделс кью - ай лав ю!" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/02/blog-post.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYFSXo5eSp7ImA9WxVWEEQ.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-6353685307889571629</id><published>2009-02-20T04:22:00.004+02:00</published><updated>2009-02-20T04:48:38.421+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-20T04:48:38.421+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><title>Перевод строки в Boolean-тип в Python</title><content type="html">&lt;p&gt;Все гениальное просто, а премудростями, описанными в сабже, в Python занимается &lt;a href="http://docs.python.org/distutils/apiref.html#distutils.util.strtobool"&gt;&lt;code&gt;distutils.util.strtobool&lt;/code&gt;&lt;/a&gt;. Примеры использования данной функции настолько тривиальны, что я ограничусь уже вышесказанным ;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы&lt;/strong&gt;. Давно уже присматривался к &lt;a href="http://posterous.com/"&gt;Posterous&lt;/a&gt; и вот наконец-то созрел для того, чтобы попробовать. Отныне &lt;a href="http://djangonaut.posterous.com/"&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/8645505283412677599-6353685307889571629?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6353685307889571629?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6353685307889571629?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/9LDv8ZVPDO4/boolean-python.html" title="Перевод строки в Boolean-тип в Python" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/02/boolean-python.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEENQncyeip7ImA9WxVXF0k.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-3923181045051805599</id><published>2009-02-16T00:57:00.007+02:00</published><updated>2009-02-16T02:04:53.992+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-16T02:04:53.992+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks" /><title>Полезные функции в Django</title><content type="html">&lt;p&gt;В &lt;strong&gt;Django&lt;/strong&gt; существует куча полезных функций и классов, которые помогают выполнять простые и полезные действия раз от разу. Предлогаю Вам свое видение этого списка:&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/datastructures.py#L53"&gt;&lt;strong&gt;django.utils.datastructures.SortedDict&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/datastructures.py#L170"&gt;&lt;strong&gt;django.utils.datastructures.MultiValueDict&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;По умолчанию в Python объект &lt;code&gt;dict&lt;/code&gt; не поддерживает сортировку ключей и несколько значений для одного ключа. Именно поддержку этих возможностей дают перечисленные выше классы. Например, поддержка &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;REQUEST&lt;/code&gt; массивов в объекте &lt;code&gt;request&lt;/code&gt; реализована в виде &lt;code&gt;MultiValueDict&lt;/code&gt; объектов.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/dates.py"&gt;&lt;strong&gt;django.utils.dates&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Этот модуль содержит разнообразные массивы, которые используются для печати &lt;a href="http://djangonaut.blogspot.com/2008/05/django.html"&gt;SelectDateWidget&lt;/a&gt;'а.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/encoding.py#L44"&gt;&lt;strong&gt;django.utils.encoding.force_unicode&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Переводит любой Python объект в &lt;a href="http://docs.python.org/library/functions.html#unicode"&gt;&lt;code&gt;unicode&lt;/code&gt;&lt;/a&gt;. Так же переводит в &lt;code&gt;unicode&lt;/code&gt; любую модель Django у которой есть метод &lt;code&gt;__unicode__&lt;/code&gt;.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/html.py#L133"&gt;&lt;strong&gt;django.utils.html.clean_html&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Очищает переданный в функцию HTML строку, а именно:
&lt;ul&gt;
&lt;li&gt;Конвертирует &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; и &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; в &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; и &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Правильно кодирует все амперсанды.&lt;/li&gt;
&lt;li&gt;Удаляет все &lt;code&gt;"target"&lt;/code&gt; аттрибуты с тегов &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Конвертирует явно заданные баллетсы (bullets) в неупорядоченные HTML списки.&lt;/li&gt;
&lt;li&gt;Удаляет из текста фрагменты &lt;code&gt;"&amp;lt;p&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;lt;/p&amp;gt;"&lt;/code&gt;, но только если они находятся в конце текста.&lt;/li&gt;
&lt;/ul&gt;&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/html.py#L75"&gt;&lt;strong&gt;django.utils.html.urlize&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Переводит все ссылки в тексте в &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; тэги.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/safestring.py#L89"&gt;&lt;strong&gt;django.utils.safestring.mark_safe&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Обозначает любой строковой объект, как безопасный для того, чтобы он мог был беспрепятственно распечатан в шаблоне без эскейпинга символов.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="http://code.djangoproject.com/browser/django/trunk/django/utils/text.py#L128"&gt;&lt;strong&gt;django.utils.text.get_text_list&lt;/strong&gt;&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Для описания работы этой функции и &lt;code&gt;doctest&lt;/code&gt;'а хватит:
&lt;pre name="code" class="python"&gt;&gt;&gt;&gt; get_text_list(['a', 'b', 'c', 'd'])
u'a, b, c or d'
&gt;&gt;&gt; get_text_list(['a', 'b', 'c'], 'and')
u'a, b and c'
&gt;&gt;&gt; get_text_list(['a', 'b'], 'and')
u'a and b'
&gt;&gt;&gt; get_text_list(['a'])
u'a'
&gt;&gt;&gt; get_text_list([])
u''&lt;/pre&gt;&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Надеюсь, что Вам пригодится что-то из моего списка.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-3923181045051805599?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3923181045051805599?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3923181045051805599?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/MFyzEJ316e4/django.html" title="Полезные функции в Django" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/02/django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE4CQX06eCp7ImA9WxVXFEU.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-6582986911197488135</id><published>2009-02-13T02:56:00.004+02:00</published><updated>2009-02-13T03:02:40.310+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-13T03:02:40.310+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="widgets" /><category scheme="http://www.blogger.com/atom/ns#" term="custom model fields" /><title>Простой и работающий JSONField для Django</title><content type="html">&lt;p&gt;Не успел, я &lt;a href="http://djangonaut.blogspot.com/2008/07/django-pickle-field.html"&gt;починить&lt;/a&gt; &lt;code&gt;PickleField&lt;/code&gt; для Django, мне понадобилось создать &lt;code&gt;JSONField&lt;/code&gt;. Задача оказалась решенной на раз/два, плюс ко всему добавился маленький и полезный виджет для &lt;code&gt;JSONField&lt;/code&gt;'а, который показывает красиво отформатированный JSON в &lt;code&gt;textarea&lt;/code&gt;. Сразу скажу, что &lt;code&gt;contribute_to_class&lt;/code&gt; метод чуть менне, чем полностью, скопипастен с &lt;a href="http://www.djangosnippets.org/snippets/377/"&gt;снипетта 377&lt;/a&gt;.&lt;/p&gt;

&lt;pre name="code" class="python"&gt;from django.conf import settings
from django.forms.widgets import Textarea
from django.db.models import SubfieldBase, TextField
from django.utils import simplejson


class JSONField(TextField):
    __metaclass__ = SubfieldBase

    def contribute_to_class(self, cls, name):
        super(JSONField, self).contribute_to_class(cls, name)

        def get_json(model):
            return self.get_db_prep_value(getattr(model, self.attname))
        setattr(cls, 'get_%s_json' % self.name, get_json)

        def set_json(model, json):
            setattr(model, self.attname, self.to_python(json))
        setattr(cls, 'set_%s_json' % self.name, set_json)

    def formfield(self, **kwargs):
        kwargs['widget'] = JSONWidget(attrs={'class': 'vLargeTextField'})
        return super(JSONField, self).formfield(**kwargs)

    def get_db_prep_value(self, value):
        return simplejson.dumps(value)

    def to_python(self, value):
        if not isinstance(value, basestring):
            return value

        try:
            return simplejson.loads(value, encoding=settings.DEFAULT_CHARSET)
        except ValueError, e:
            # If string could not parse as JSON it's means that it's Python
            # string saved to JSONField.
            return value

class JSONWidget(Textarea):
    """
    Prettify dumps of all non-string JSON data.
    """
    def render(self, name, value, attrs=None):
        if not isinstance(value, basestring) and value is not None:
            value = simplejson.dumps(value, indent=4, sort_keys=True)
        return super(JSONWidget, self).render(name, value, attrs)&lt;/pre&gt;

&lt;p&gt;Ну и пару примеров использования, напоследок:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;&amp;gt;&amp;gt;&amp;gt; # Пусть у нас есть простая модель
&amp;gt;&amp;gt;&amp;gt; class Sample(models.Model):
...    name = models.CharField(max_length=16)
...    data = JSONField()

&amp;gt;&amp;gt;&amp;gt; # Создадим модель и сохраним в data поле настройки для ShadowBox
&amp;gt;&amp;gt;&amp;gt; sb = Sample.objects.create(name='ShadowBox',
...                            data={'autoDimensions': True,
...                                  'overlayOpacity': 0.5,
...                                  'skipSetup': True})

&amp;gt;&amp;gt;&amp;gt; # Теперь мы хотим напечатать эти настройки где-то в шаблоне
&amp;gt;&amp;gt;&amp;gt; sb.get_data_json()
'{"overlayOpacity": 0.5, "autoDimensions": true, "skipSetup": true}'

&amp;gt;&amp;gt;&amp;gt; # Вспомним, что нам вообщем-то не зачем кастомное значение для overlayOpacity
&amp;gt;&amp;gt;&amp;gt; del sb.data['overlayOpacity']
&amp;gt;&amp;gt;&amp;gt; sb.save()

&amp;gt;&amp;gt;&amp;gt; # И опять напечатаем настройки в шаблоне
&amp;gt;&amp;gt;&amp;gt; sb.get_data_json()
'{"autoDimensions": true, "skipSetup": true}'&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; &lt;a href="http://mjijackson.com/shadowbox/"&gt;&lt;strong&gt;ShadowBox&lt;/strong&gt;&lt;/a&gt; - это лучший лайтбокс для любого JavaScript фреймворка, имо.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-6582986911197488135?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6582986911197488135?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6582986911197488135?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/IU80TZ9J1jA/jsonfield-django.html" title="Простой и работающий JSONField для Django" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><feedburner:origLink>http://djangonaut.blogspot.com/2009/02/jsonfield-django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08CRHg8fyp7ImA9WxFTEEk.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-7927836287907673149</id><published>2009-02-13T02:32:00.001+02:00</published><updated>2010-03-31T16:24:25.677+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-31T16:24:25.677+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="custom model fields" /><title>По-настоящему, рабочий Django PickleField</title><content type="html">&lt;p&gt;&lt;strong&gt;Важнее некуда&lt;/strong&gt;: &lt;a href="http://djangonaut.blogspot.com/2010/03/picklefield.html"&gt;В PickleField добавлена возможность полноценного хранения объектов моделей и поддержка питоновских значений по умолчанию.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Важно&lt;/strong&gt;: предыдущая версия &lt;code&gt;PickleField&lt;/code&gt;'а была практически неработающей в большинстве случаев. Теперь же появилось время в связи с ангиной, и я быстро исправил эту оплошность.&lt;/p&gt;

&lt;p&gt;Иногда возникает необходимость хранить какие-то данные, "сериализированные" с помощью &lt;a href="http://docs.python.org/lib/module-pickle.html"&gt;pickle&lt;/a&gt; или &lt;a href="http://docs.python.org/lib/module-cPickle.html"&gt;cPickle&lt;/a&gt;, в базе данных. Для этих случаев весьма кстати будет &lt;code&gt;PickleField&lt;/code&gt;. Вообщем, может быть кому-то пригодится.&lt;/p&gt;

&lt;pre name="code" class="python"&gt;from django.conf import settings
from django.db import models

if getattr(settings, 'USE_CPICKLE', False):
    import cPickle as pickle
else:
    import pickle

class PickleField(models.TextField):
    __metaclass__ = models.SubfieldBase

    editable = False
    serialize = False

    def get_db_prep_value(self, value):
        return pickle.dumps(value)

    def to_python(self, value):
        if not isinstance(value, basestring):
            return value

        # Tries to convert unicode objects to string, cause loads pickle from
        # unicode excepts ugly ``KeyError: '\x00'``.
        #
        # If not possible, return this value, cause it's not pickled yet.
        if isinstance(value, unicode):
            try:
                str_value = str(value)
            except UnicodeEncodeError:
                return value
        else:
            str_value = value

        try:
            return pickle.loads(str_value)
        except ValueError:
            # If pickle could not loads from string it's means that it's Python
            # string saved to PickleField.
            return value&lt;/pre&gt;

&lt;p&gt;Также добавил простейший тест для показания работы &lt;code&gt;PickleField&lt;/code&gt;'a:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;import unittest

from django.db import models

from fields import PickleField

class PickleObject(models.Model):
    name = models.CharField(max_length=16)
    data = PickleField()

class TestPickleField(unittest.TestCase):
    def setUp(self):
        PickleObject.objects.all().delete()

    def test_not_string_data(self):
        items = [
            'Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'
        ]

        obj = PickleObject.objects.create(name='default', data=items)
        self.assertEqual(PickleObject.objects.count(), 1)

        self.assertEqual(obj.data, items)

        obj = PickleObject.objects.get(name='default')
        self.assertEqual(obj.data, items)

    def test_string_and_unicode_data(self):
        DATA = (
            ('string', 'Simple string'),
            ('unicode', u'Simple unicode string'),
        )

        for name, data in DATA:
            obj = PickleObject.objects.create(name=name, data=data)
            self.assertEqual(obj.data, data)

        self.assertEqual(PickleObject.objects.count(), 2)

        for name, data in DATA:
            obj = PickleObject.objects.get(name=name)
            self.assertEqual(obj.data, data)&lt;/pre&gt;

&lt;p&gt;Надеюсь, что в скором времени эти изменения будут добавлены в &lt;a href="http://github.com/svetlyak40wt/django-fields/tree"&gt;django-fields&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/8645505283412677599-7927836287907673149?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/7927836287907673149/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=7927836287907673149" title="Комментарии: 4" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/7927836287907673149?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/7927836287907673149?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/I2vjFUDVVro/django-pickle-field.html" title="По-настоящему, рабочий Django PickleField" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>4</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/07/django-pickle-field.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cGQns4fip7ImA9WxVSEUk.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-820796870747184621</id><published>2008-10-09T00:44:00.006+03:00</published><updated>2009-01-05T09:37:03.536+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-05T09:37:03.536+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="полезные советы" /><category scheme="http://www.blogger.com/atom/ns#" term="системы управления версиями" /><title>Быстрое обновление всех клонированных репозиториев</title><content type="html">&lt;p&gt;Не совсем в тему блога, но все же, надеюсь, что кому-то предоставленный ниже скрипт и принцип работы будут полезными.&lt;/p&gt;

&lt;p&gt;Итак, работая с любым Django проектом я использую целую тучу разнообразнейших &lt;code&gt;reusable apps&lt;/code&gt;. Установка и добавления любого &lt;code&gt;reusable app&lt;/code&gt;'а в свой Django-проект проста и не тривиальна: клонирование репозитория, обновление &lt;code&gt;sys.path&lt;/code&gt;, добавление &lt;code&gt;appname&lt;/code&gt; в &lt;code&gt;INSTALLED_APPS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Намного интересней становится, когда приходит время пробежаться по всем склонированным локально репозиториям и проверить наличие обновление в них (сейчас и далее актуально только для тех, кто на передовой). Согласитесь, имея в наличии под 100 svn репозиториев c googlecode, да 20-30 git репозиториев с github'а, а также по паре тройке разнообразных bzr с hg репозиториев, их обновление посредством ручного набора поочередно:&lt;/p&gt;

&lt;pre&gt;$ svn up /path/to/django
$ cd /path/to/werkezeug &amp;&amp; hg fetch
$ cd /path/to/django-debug-toolbar &amp;&amp; git pull&lt;/pre&gt;

&lt;p&gt;будет очень и очень надоедливо. И именно по-этому, я быстро на коленке написал простой скрипт для обновления всех склонированных репозиториев в текущем или указанном каталоге. Код скрипта, ниже. Сразу предупреждаю, за именование переменных, а также использование os.system, не ругать, так как в час ночи не до жиру ;)&lt;/p&gt;

&lt;pre name="code" class="py"&gt;#!/usr/bin/env python

import os, sys

def run():
    if len(sys.argv) == 2:
        dirname = os.path.expanduser(sys.argv[1])
    else:
        dirname = os.getcwd()

    if not os.path.isdir(dirname):
        sys.stderr.write('Directory "%s" was not exists or you ' % dirname + \
                         'have not permissions to read from it.\n')
        sys.exit(1)

    dirdata = os.listdir(dirname)

    not_scms = []
    scms = (
        ('bzr', []),
        ('git', []),
        ('git-svn', []),
        ('hg', []),
        ('svn', []),
    )

    dirs_len = len(dirdata)
    scms_len = 0

    for name in dirdata:
        subdirname = os.path.join(dirname, name)

        if not os.path.isdir(subdirname):
            continue

        scms_len += 1
        subdirdata = os.listdir(subdirname)

        if '.bzr' in subdirdata:
            scms[0][1].append(subdirname)
        elif '.git' in subdirdata:
            f = open(os.path.join(subdirname, '.git/config'), 'r')
            if not 'svn-remote' in f.read():
                scms[1][1].append(subdirname)
            else:
                scms[2][1].append(subdirname)
            f.close()
        elif '.hg' in subdirdata:
            scms[3][1].append(subdirname)
        elif '.svn' in subdirdata:
            scms[4][1].append(subdirname)
        else:
            scms_len -= 1
            not_scms.append(subdirname)

    print 'Work directory:', dirname
    print 'Number of found subdirs:', dirs_len
    print 'Number of found SCM dirs:', scms_len

    if dirs_len != scms_len:
        print 'Not SCM dirs:', not_scms

    print

    if not scms_len:
        sys.exit(0)

    for protocol, dirs in scms:
        if protocol == 'bzr':
            cmd = "cd '%s' &amp;&amp; bzr pull"
        elif protocol == 'git':
            cmd = "cd '%s' &amp;&amp; git pull"
        elif protocol == 'git-svn':
            cmd = "cd '%s' &amp;&amp; git svn fetch"
        elif protocol == 'hg':
            cmd = "cd '%s' &amp;&amp; hg fetch"
        elif protocol == 'svn':
            cmd = "svn update '%s'"

        dirs.sort()

        for d in dirs:
            print '$', cmd % d
            os.system(cmd % d)
            print

if __name__ == '__main__':
    run()
&lt;/pre&gt;

&lt;p&gt;Теперь вы можете сохранить этот код в любой файл (я, например, использую название &lt;code&gt;scm-up-all.py&lt;/code&gt;) и поместить этот файл в &lt;code&gt;PATH&lt;/code&gt;. Таким образом обновление всех репозиториев в &lt;code&gt;/srv/shared&lt;/code&gt; стало плевым делом ;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPD:&lt;/strong&gt; На днях появилось желание чуточку проапдейтить скрипт, потому я решил завести для него &lt;a href="http://github.com/playpauseandstop/scm-up-all/tree"&gt;проект на GitHub&lt;/a&gt;. В &lt;strong&gt;ToDo&lt;/strong&gt;: поддержка CVS, Darcs; вывод информации о последнем коммите (последней ревизии) в репозитории; простой GUI (PyQt4) для управления обновлением.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-820796870747184621?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/820796870747184621/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=820796870747184621" title="Комментарии: 2" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/820796870747184621?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/820796870747184621?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/ROFB43AN478/blog-post.html" title="Быстрое обновление всех клонированных репозиториев" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/10/blog-post.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUUNRnk8cCp7ImA9WxRTFU0.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-3322818588472040047</id><published>2008-09-04T08:36:00.004+03:00</published><updated>2008-09-04T08:48:17.778+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-04T08:48:17.778+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django weblog" /><category scheme="http://www.blogger.com/atom/ns#" term="новости" /><category scheme="http://www.blogger.com/atom/ns#" term="django 1.0" /><title>Django 1.0 was released</title><content type="html">&lt;p&gt;Примите мои самые искренние поздравления, друзья, в связи с &lt;a href="http://www.djangoproject.com/weblog/2008/sep/03/1/"&gt;этой отличнейшей новостью&lt;/a&gt; :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.djangoproject.com/download/1.0/tarball/"&gt;Скачать &lt;strong&gt;Django 1.0&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.djangoproject.com/en/dev/releases/1.0/"&gt;Читать &lt;strong&gt;Release notes&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.djangoproject.com/en/dev/releases/1.0-porting-guide/"&gt;Читать &lt;strong&gt;Краткий курс перехода с Django 0.96 на 1.0&lt;/strong&gt;&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/8645505283412677599-3322818588472040047?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/3322818588472040047/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=3322818588472040047" title="Комментарии: 0" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3322818588472040047?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3322818588472040047?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/XrisUNf3EPc/django-10-released.html" title="Django 1.0 was released" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/09/django-10-released.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAFQ3w8fip7ImA9WxdVF0w.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-7156584833934539985</id><published>2008-07-19T10:38:00.004+03:00</published><updated>2008-07-22T11:51:52.276+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-22T11:51:52.276+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="newforms" /><category scheme="http://www.blogger.com/atom/ns#" term="django trunk" /><category scheme="http://www.blogger.com/atom/ns#" term="newforms-admin" /><title>Ну это просто праздник какой-то!</title><content type="html">&lt;p&gt;&lt;strong&gt;&lt;a href="http://code.djangoproject.com/changeset/7967"&gt;Django's newforms-admin branch merged into trunk&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Лирическое отступление: Знаете, меня сейчас переполняют настолько положительные эмоции, что их просто невозможно как-то точно сформулировать и/или описать.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Но если по сути, то бранч &lt;a href="http://code.djangoproject.com/wiki/NewformsAdminBranch"&gt;&lt;strong&gt;newforms-admin&lt;/strong&gt;&lt;/a&gt; уже давно заслужил быть слитым в транк. Почему?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Потому что, &lt;a href="http://www.djangoproject.com/documentation/admin/"&gt;кастомизация административной панели Django&lt;/a&gt; переходит на совершенно новый уровень.&lt;/li&gt;
&lt;li&gt;Потому что, &lt;a href="http://www.djangoproject.com/documentation/admin/#multiple-admin-sites-in-the-same-urlconf"&gt;один сайт &lt;strong&gt;!=&lt;/strong&gt; одна административная панель Джанго.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Потому что, &lt;a href="http://www.djangoproject.com/documentation/forms/#media"&gt;&lt;code&gt;class Media&lt;/code&gt; для форм&lt;/a&gt;! И теперь для добавления кастомного CSS или JavaScript не надо реализовывать свой велосипед.&lt;/li&gt;
&lt;li&gt;Потому что, &lt;a href="http://www.djangoproject.com/documentation/forms/#formsets"&gt;формсеты.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Да и просто потому что, &lt;code&gt;oldforms&lt;/code&gt; &amp;ndash; это уже история.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;И что самое главное, каких-то экстра трудностей по переходу на &lt;code&gt;newforms-admin&lt;/code&gt; наблюдаться не должно. А если еще не использовал в проектах встроенную админку &amp;ndash; то их вообще не будет ;)&lt;/p&gt;

&lt;strong&gt;upd.&lt;/strong&gt; Не так много времени прошло после мерджинга &lt;code&gt;newforms-admin&lt;/code&gt; в &lt;code&gt;trunk&lt;/code&gt;, как была выпущена &lt;a href="http://www.djangoproject.com/weblog/2008/jul/21/10-alpha/"&gt;первая альфа 1.0 релиза Django&lt;/a&gt;, и вместе с тем библиотека &lt;code&gt;django.newforms&lt;/code&gt; тоже стала историей ;) Отныне есть только &lt;a href="http://www.djangoproject.com/documentation/forms/"&gt;&lt;strong&gt;The forms library&lt;/strong&gt;&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-7156584833934539985?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/7156584833934539985/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=7156584833934539985" title="Комментарии: 2" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/7156584833934539985?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/7156584833934539985?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/PdhQBZ-y0xI/blog-post.html" title="Ну это просто праздник какой-то!" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/07/blog-post.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcBQ3oyfyp7ImA9WxdXGU4.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-6255833046466636051</id><published>2008-07-01T19:56:00.005+03:00</published><updated>2008-07-01T20:07:32.497+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-01T20:07:32.497+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django trunk" /><category scheme="http://www.blogger.com/atom/ns#" term="file uploads" /><title>Fixed #2070: refactored Django's file upload capabilities.</title><content type="html">&lt;p&gt;Да, да, да! Вы не ошиблись, прочитав заголовок сего поста, ибо то, о чем так долго говорили большевики - свершилось! Django теперь умеет по-взрослому загружать файлы на сервер.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.djangoproject.com/documentation/upload_handling/"&gt;Опубликованная по этому случаю документация&lt;/a&gt;, имо, должна заменить книгу, с которой ты обычно засыпаешь на ближайших пару дней. Ибо &lt;a href="http://code.djangoproject.com/changeset/7814"&gt;изменений&lt;/a&gt; не просто, а ОЧЕНЬ, много!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; И да, &lt;strong&gt;newforms-admin&lt;/strong&gt; уже &lt;a href="http://code.djangoproject.com/changeset/7815"&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/8645505283412677599-6255833046466636051?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/6255833046466636051/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=6255833046466636051" title="Комментарии: 0" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6255833046466636051?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/6255833046466636051?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/q8I2mM8ahvU/fixed-2070-refactored-djangos-file.html" title="Fixed #2070: refactored Django's file upload capabilities." /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/07/fixed-2070-refactored-djangos-file.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkUARno-eyp7ImA9WxdQEEg.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-3397264697474348815</id><published>2008-06-10T01:06:00.006+03:00</published><updated>2008-06-10T01:24:07.453+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-10T01:24:07.453+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django trunk" /><category scheme="http://www.blogger.com/atom/ns#" term="sessions" /><title>Поддержка поля "Запомнить меня" в формах авторизации в Django</title><content type="html">&lt;p&gt;&lt;a href="http://code.djangoproject.com/changeset/7586"&gt;Замечательнейший changeset номер 7586&lt;/a&gt; теперь позволяет реализовать описанный в сабже функционал прямо в методе save() формы авторизации:&lt;/p&gt;

&lt;pre name="code" class="python"&gt;from django import newforms as forms
from django.contrib import auth

class LoginForm(forms.Form):
    username = forms.CharField(...)
    password = forms.CharField(...)
    remember_me = forms.BooleanField(...)

    user_cache = None

    ...

    def save(self, request):
        cd = self.cleaned_data
        user = self.user_cache

        auth.login(request, user)

        if not 'remember_me' in cd or not cd['remember_me']:
            &lt;strong&gt;request.session.set_expire(0)&lt;/strong&gt;

        return user&lt;/pre&gt;

&lt;p&gt;Вот и не используй после этого Django из транка.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; &lt;a href="http://www.djangoproject.com/documentation/sessions/"&gt;Документация по сессиям в Django&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-3397264697474348815?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/3397264697474348815/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=3397264697474348815" title="Комментарии: 0" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3397264697474348815?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3397264697474348815?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/c-Bjsc9eOI8/django.html" title="Поддержка поля &quot;Запомнить меня&quot; в формах авторизации в Django" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/06/django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMNSXk_eip7ImA9WxRVFUk.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-5587004881105060130</id><published>2008-05-30T13:47:00.007+03:00</published><updated>2008-11-13T04:48:18.742+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-13T04:48:18.742+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps" /><category scheme="http://www.blogger.com/atom/ns#" term="django forum" /><title>Django Forum - это реально</title><content type="html">&lt;p&gt;Конечно, я знаю, что для Django есть десятки прототипов решений форума, начиная от &lt;a href="http://softwaremaniacs.org/cicero/"&gt;Cicero&lt;/a&gt; и заканчивая &lt;a href="http://code.google.com/p/snapboard"&gt;Snapboard&lt;/a&gt; и &lt;a href="http://code.google.com/p/diamanda"&gt;Mighty Board&lt;/a&gt;. Но не с одним из этих прототипов мне не удалось подружиться быстро и безболезненно. Всегда надо было предпринимать какие-то лишние телодвижения, связанные с пониманием идеологии того или иного форума.&lt;/p&gt;

&lt;p&gt;И вот вчера, когда я еще раз просматривал списки доступных решений я наткнулся на статью &lt;a href="http://code.djangoproject.com/wiki/ForumAppsComparison"&gt;ForumAppsComparsion&lt;/a&gt; в Django Code Wiki. Присмотрелся я к табличке и понял, что не имел дело всего лишь с &lt;a href="http://code.google.com/p/django-threadedcomments"&gt;django-threadedcomments&lt;/a&gt; и &lt;a href="http://www.jonathanbuchanan.plus.com/repos/forum"&gt;Django Forum&lt;/a&gt;. Благо и одно и второе приложение содержало демо-версии и я решил остановится на них подробней.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;django-threadedcomments&lt;/strong&gt; я сразу отбросил из-за названия и области применения (и даже &lt;a href="http://www.eflorenzano.com/threadexample/blog/"&gt;прикольная демка&lt;/a&gt; не спасла его :)).&lt;/p&gt;

&lt;p&gt;А вот &lt;strong&gt;Django Forum&lt;/strong&gt; оказался практически сразу же именно тем, что мне и надо было. Простой и не загруженный разными ненужными фишками форум, который быстро настраивается и запускается. Единственным же минусом стало то, что автор решил не утомлять себя интернационализацией движка и именно ей я сейчас и занимаюсь.&lt;/p&gt;

&lt;p&gt;И даже то, что этот форум хранится в &lt;a href="http://darcs.net/"&gt;Darcs&lt;/a&gt; репозиторие - это не помешало мне уже практически полюбить его, как когда-то давно я полюбил с первого взгляда маленький и удаленький &lt;a href="http://usebb.net/"&gt;UseBB&lt;/a&gt; форум.&lt;/p&gt;

&lt;p&gt;Ну а напоследок, как всегда пачка скриншотов, в этот раз для тех кому лень &lt;a href="http://incremental.homedns.org:81/" title="Логин/Пароль: guest/guest"&gt;идти на демонстрцию&lt;/a&gt; :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Главная страница моего форума (уже частично локализированного)&lt;/strong&gt;&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD_hVCB3R1I/AAAAAAAAAFU/HGhYySqzFFk/s1600-h/forum_index.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD_hVCB3R1I/AAAAAAAAAFU/HGhYySqzFFk/s400/forum_index.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5206127445701773138" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Список тем в форуме&lt;/strong&gt;&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_wPsSJz8bPyE/SD_hUiB3R0I/AAAAAAAAAFM/y4fGsuRKkB8/s1600-h/forum_forum.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_wPsSJz8bPyE/SD_hUiB3R0I/AAAAAAAAAFM/y4fGsuRKkB8/s400/forum_forum.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5206127437111838530" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Отображение темы&lt;/strong&gt;&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_wPsSJz8bPyE/SD_hViB3R4I/AAAAAAAAAFs/SVUB2U9u87I/s1600-h/forum_topic.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_wPsSJz8bPyE/SD_hViB3R4I/AAAAAAAAAFs/SVUB2U9u87I/s400/forum_topic.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5206127454291707778" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Добавить новое сообщение в тему&lt;/strong&gt;&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD_hVCB3R2I/AAAAAAAAAFc/4CZUoP_2n-Q/s1600-h/forum_post.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD_hVCB3R2I/AAAAAAAAAFc/4CZUoP_2n-Q/s400/forum_post.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5206127445701773154" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Отображение профиля&lt;/strong&gt;&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_wPsSJz8bPyE/SD_hVSB3R3I/AAAAAAAAAFk/avWpl6fE2SU/s1600-h/forum_profile.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SD_hVSB3R3I/AAAAAAAAAFk/avWpl6fE2SU/s400/forum_profile.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5206127449996740466" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Позволю себе еще одно замечание. Если вы хотите использовать этот форум в STANDALONE режиме - не забудьте папки css, img и js в media/ перенести в media/forum/. А то после django-manage.py runserver Вы вполне можете не увидеть ни стилей, ни рисунков, ни смайлов. А только текст, один текст.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-5587004881105060130?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/5587004881105060130/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=5587004881105060130" title="Комментарии: 3" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/5587004881105060130?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/5587004881105060130?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/TQtgIDuWTFI/django-forum.html" title="Django Forum - это реально" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD_hVCB3R1I/AAAAAAAAAFU/HGhYySqzFFk/s72-c/forum_index.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/05/django-forum.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8HQn04cCp7ImA9WxVWE0Q.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-3743743660304660639</id><published>2008-05-29T20:14:00.020+03:00</published><updated>2009-02-23T14:07:13.338+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-23T14:07:13.338+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django forms" /><category scheme="http://www.blogger.com/atom/ns#" term="widgets" /><category scheme="http://www.blogger.com/atom/ns#" term="jquery" /><category scheme="http://www.blogger.com/atom/ns#" term="autocomplete" /><title>jQuery AutocompleteWidget для Django</title><content type="html">&lt;p&gt;По моему скромному мнению, сейчас найти правильный сниппет, показывающий как реализовать автозаполнение на том или ином языке программирования с использованием того или иного JavaScript фреймворка - пустяковое дело.&lt;/p&gt;

&lt;p&gt;Так что будем считать, что сегодня просто сама по себе настала очередь связки &lt;a href="http://www.jquery.com/"&gt;jQuery&lt;/a&gt; + &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;. Реализация виджета автозаполнения в этой связке - проста до ужаса. Особенно, когда используется великолепный &lt;a href="http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/"&gt;jQuery Autocomplete plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Вся идея реализации состоит в следующем:&lt;p&gt;
&lt;ol&gt;
&lt;li&gt;Определяем какие данные будут использоваться в автозаполнении (локальные или удаленные)&lt;/li&gt;
&lt;li&gt;Если данные удаленные, пишем view и добавляем этот view в urlpatterns&lt;/li&gt;
&lt;li&gt;Получаем автозаполняемое поле&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Но чтобы потом в дальнейшем по сто раз не дописывать этот виджет я чуточку его модифицировал. И посему Вы можете указать какие поля являются зависимыми от текущего (это например, очень полезно в связке страна - город, ведь незачем выбрав страну Украина, автодополнять город Амстердам). Это раз, а два - Вы можете указать какие именно опции плагина вам необходимы для этого поля.&lt;/p&gt;

&lt;p&gt;Ну что ж, перейдем к программному коду. Для начала сам виджет:
&lt;pre name="code" class="python"&gt;from django.core.urlresolvers import reverse, NoReverseMatch
from django.newforms import HiddenInput, TextInput
from django.utils import simplejson
from django.utils.safestring import mark_safe

class AutocompleteWidget(TextInput):
    """
    Autocomplete widget to use with jquery-autocomplete plugin.

    Widget can use for static and dynamic (AJAX-liked) data. Also
    you can relate some fields and it's values'll posted to autocomplete
    view.

    Widget support all jquery-autocomplete options that dumped to JavaScript
    via django.utils.simplejson.

    **Note** You must init one of ``choices`` or ``choices_url`` attribute.
    Else widget raises TypeError when rendering.
    """
    def __init__(self, attrs=None, choices=None, choices_url=None, options=None, related_fields=None):
        """
        Optional arguments:
        -------------------

            * ``choices`` - Static autocomplete choices (similar to choices
            used in Select widget).

            * ``choices_url`` - Path to autocomplete view or autocomplete
            url name.

            * ``options`` - jQuery autocomplete plugin options. Auto dumped
            to JavaScript via SimpleJSON

            * ``related_fields`` - Fields that relates to current (value
            of this field will sended to autocomplete view via POST)
        """
        self.attrs = attrs or {}
        self.choice, self.choices, self.choices_url = None, choices, choices_url
        self.options = options or {}

        if related_fields:
            extra = {}
            if isinstance(related_fields, str):
                related_fields = list(related_fields)

            for field in related_fields:
                extra[field] = "%s_value" % field

            self.extra = extra
        else:
            self.extra = {}

    def render(self, name, value=None, attrs=None):
        if not self.choices and not self.choices_url:
            raise TypeError('One of "choices" or "choices_url" keyword argument must be supplied obligatory.')

        if self.choices and self.choices_url:
            raise TypeError('Only one of "choices" or "choices_url" keyword argument can be supplied.')

        choices = ''

        if self.choices:
            self.set_current_choice(value)
            choices = simplejson.dumps([unicode(v) for k, v in self.choices], ensure_ascii=False)
            html_code = HiddenInput().render(name, value=value)
            name += '_autocomplete'
        else:
            html_code = ''

        if self.choices_url:
            try:
                choices = simplejson.dumps(reverse(str(self.choices_url)))
            except NoReverseMatch:
                choices = simplejson.dumps(self.choices_url)

        if self.options or self.extra:
            if 'extraParams' in self.options:
                self.options['extraParams'].update(self.extra)
            else:
                self.options['extraParams'] = self.extra

            options = ', ' + simplejson.dumps(self.options, indent=4, sort_keys=True)
            extra = []

            for k, v in self.extra.items():
                options = options.replace(simplejson.dumps(v), v)
                extra.append(u"function %s() { return $('#id_%s').val(); }\n" % (v, k))

            extra = u''.join(extra)
        else:
            extra, options = '', ''

        final_attrs = self.build_attrs(attrs)
        html_code += super(AutocompleteWidget, self).render(name, self.choice or value, attrs)

        html_code += u"""
&amp;lt;script type="text/javascript"&amp;gt;&amp;lt;!--
    %s$('#%s').autocomplete(%s%s);
--&amp;gt;&amp;lt;/script&amp;gt;
""" % (extra, final_attrs['id'], choices, options)

        return mark_safe(html_code)

    def set_current_choice(self, data):
        if not self.choices:
            raise ValueError('"choices" attribute was not defined yet.')

        for k, v in self.choices:
            if k == data:
                self.choice = v
                break

    def value_from_datadict(self, data, files, name):
        if not self.choices:
            return super(AutocompleteWidget, self).value_from_datadict(data, files, name)

        autocomplete_name = name + '_autocomplete'

        if not autocomplete_name in data:
            self.set_current_choice(data[name])
            return data[name]

        for k, v in self.choices:
            if v == data[autocomplete_name]:
                self.set_current_choice(k)
                return k&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Теперь примеры его использования:
&lt;pre name="code" class="python"&gt;&lt;strong&gt;forms.py&lt;/strong&gt;
from django import newforms as forms
from django.utils.translation import ugettext as _

from myproject.widgets import AutocompleteWidget

SPORTS_CHOICES = (
    ('basketball', _('Basketball')),
    ('football', _('Football')),
    ('hockey', _('Hockey')),
)

class SampleForm(forms.Form):
    name = forms.CharField(label=_('Name'))
    country = forms.CharField(label=_('Country'),
        widget=AutocompleteWidget(choices_url='autocomplete_countries', related_fields=('city',)))
    city = forms.CharField(label=_('City),
        widget=AutocompleteWidget(choices_url='autocomplete_cities', related_fields=('country',)))
    sports = forms.ChoiceField(label=_('Sports'), choices=SPORTS_CHOICES,
        widget=AutocompleteWidget(options={'minChars': 0, 'autoFill': True, 'mustMatch': True}))

&lt;strong&gt;urls.py&lt;/strong&gt;
from django.conf.urls.defaults import *

urlpatterns = patterns('',
    url('/path/to/cities/autocomplete/', 'views.autocomplete_cities', name='autocomplete_cities'),
    url('/path/to/countries/autocomplete/', 'views.autocomplete_countries', name='autocomplete_countries'),
)

&lt;strong&gt;views.py&lt;/strong&gt;
from myproject.models import City

def autocomplete_cities(request):
    def results_to_string(results):
        if results:
            for r in results:
                yield '%s|%s\n' % (r.name, r.pk)

    city = request.REQUEST.get('q', None)
    country = request.REQUEST.get('country', None)

    if city:
        cities = City.objects.filter(city__istartswith=city)
    else:
        cities = City.objects.all()

    if country:
        cities = cities.filter(country__name=country)

    cities = cities[:int(request.REQUEST.get('limit', 15))]
    return results_to_string(cities, mimetype='text/plain')
&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;А вот как это выглядит &lt;acronym title="В Реальной Жизни"&gt;ВРЛ&lt;/acronym&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Автозаполнение поля "City"&lt;/strong&gt; (удаленные данные)&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD77lSB3RwI/AAAAAAAAAEo/UqE2ZfrWYqI/s1600-h/autocomplete_city.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD77lSB3RwI/AAAAAAAAAEo/UqE2ZfrWYqI/s400/autocomplete_city.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5205874837200258818" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Автозаполнение поля "Country"&lt;/strong&gt; (удаленные данные)&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_wPsSJz8bPyE/SD77liB3RxI/AAAAAAAAAEw/6efkJUMM_dg/s1600-h/autocomplete_country.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SD77liB3RxI/AAAAAAAAAEw/6efkJUMM_dg/s400/autocomplete_country.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5205874841495226130" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Связь при автозаполнении между полями "Country" и "Сity"&lt;/strong&gt;&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_wPsSJz8bPyE/SD77liB3RyI/AAAAAAAAAE4/eVL-fhcC8c4/s1600-h/autocomplete_related.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SD77liB3RyI/AAAAAAAAAE4/eVL-fhcC8c4/s400/autocomplete_related.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5205874841495226146" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Автозаполнение поля "Sports"&lt;/strong&gt; (локальные данные)&lt;br /&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_wPsSJz8bPyE/SD77lyB3RzI/AAAAAAAAAFA/dssbgoe79mU/s1600-h/autocomplete_sports.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_wPsSJz8bPyE/SD77lyB3RzI/AAAAAAAAAFA/dssbgoe79mU/s400/autocomplete_sports.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5205874845790193458" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;За сим прощаюсь и поздравляю всех с наступающим долгожданным летом!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-3743743660304660639?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/3743743660304660639/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=3743743660304660639" title="Комментарии: 7" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3743743660304660639?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/3743743660304660639?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/46fghISpAP8/jquery-autocompletewidget-django.html" title="jQuery AutocompleteWidget для Django" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_wPsSJz8bPyE/SD77lSB3RwI/AAAAAAAAAEo/UqE2ZfrWYqI/s72-c/autocomplete_city.png" height="72" width="72" /><thr:total>7</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/05/jquery-autocompletewidget-django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIHRn07cSp7ImA9WxdREEQ.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-8482523047281112775</id><published>2008-05-29T20:14:00.017+03:00</published><updated>2008-05-29T22:48:57.309+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-29T22:48:57.309+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="администрирование django" /><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps" /><title>Расширение функционала django-admin.py и django-manage.py с помощью django-command-extensions</title><content type="html">&lt;p&gt;Вообще, утилиты командной строки &lt;a href="http://www.djangoproject.com/documentation/django-admin/"&gt;django-admin.py&lt;/a&gt; и &lt;a href="http://djangonaut.blogspot.com/2008/05/django-managepy-django-adminpy.html"&gt;django-manage.py&lt;/a&gt; обладают кучей полезных и крайне юзабельных функций (dbshell, runserver, shell и тп), но иногда и их бывает недостаточно.&lt;/p&gt;

&lt;p&gt;И именно тогда на помощь приходит &lt;strong&gt;django-command-extensions&lt;/strong&gt;! Это приложение расширяет стандартные возможности утилит Django и теперь вы можете:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Создавать скелет приложения по своему шаблону (create_app)&lt;/li&gt;
&lt;li&gt;Создавать скелет для своей комманды (create_command)&lt;/li&gt;
&lt;li&gt;Быстро добавлять суперпользователя в базу данных (create_superuser)&lt;/li&gt;
&lt;li&gt;Быстро создать форму для необходимой модели (describe_form)&lt;/li&gt;
&lt;li&gt;Создать базу контактов пользователей вашего проекта (export_email)&lt;/li&gt;
&lt;li&gt;Сгенерировать SECRET_KEY для настроек проекта (generate_secret_key)&lt;/li&gt;
&lt;li&gt;Создать граф, показывающий связь между моделями (graphviz)&lt;/li&gt;
&lt;li&gt;Сбросить пользовательский пароль (passwd)&lt;/li&gt;
&lt;li&gt;Использовать python shell с автоматически загруженными классами всех моделей проекта (shell_plus)&lt;/li&gt;
&lt;li&gt;Просмотреть все существующие в проекте urlpatterns (show_urls)&lt;/li&gt;
&lt;li&gt;Показать разницу между определением модели и тем, что присутствует в базе данных (sqldiff)&lt;/li&gt;
&lt;li&gt;Создать и запустить определенное задание (create_job, run_job, run_jobs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Остается только поблагадорить разработчиков приложения и перейти к его скачиванию/установке/использованию.&lt;/p&gt;

&lt;p&gt;Ссылки:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://code.google.com/p/django-command-extensions/"&gt;django-command-extensions @ Google Code&lt;/a&gt;
&lt;li&gt;&lt;a href="http://blog.michaeltrier.com/2008/5/29/django-command-extensions-update"&gt;Пост "Django Command Extensions Update" в блоге Михаэля Триера&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/8645505283412677599-8482523047281112775?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/8482523047281112775/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=8482523047281112775" title="Комментарии: 0" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/8482523047281112775?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/8482523047281112775?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/AyFz8eQy3UQ/django-adminpy-django-managepy-django.html" title="Расширение функционала django-admin.py и django-manage.py с помощью django-command-extensions" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/05/django-adminpy-django-managepy-django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0IAR3c4fip7ImA9WxdWEE8.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-2398682768102285035</id><published>2008-05-14T19:19:00.008+03:00</published><updated>2008-07-02T22:05:46.936+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-02T22:05:46.936+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="архитектура проектов" /><category scheme="http://www.blogger.com/atom/ns#" term="администрирование django" /><title>django-manage.py или не единым django-admin.py сыты будем</title><content type="html">&lt;p&gt;В разработке своих проектов на Django я придерживаюсь архитектуры, близкой к той, которую описал Мальколм Трединник в своей статье &lt;a href="http://www.pointy-stick.com/blog/2007/11/09/django-tip-developing-without-projects/"&gt;"Django Tip: Developing Without Projects"&lt;/a&gt;. Странно звучит, да, разработка проектов используя методику разработки без проектов.&lt;/p&gt;

&lt;p&gt;Но это только на первый взгляд. В действительности я не использую терминологию Django, в которой проект - это продукт действия &lt;code&gt;django-admin.py startproject&lt;/code&gt;, порождающий дополнительную ветвь в иерархии и приводящий к повсеместному использованию projectname. в питон коде. Для меня проект - это скорее объединение приложений, как reusable (как-то &lt;a href="http://code.google.com/p/django-tagging/"&gt;django-tagging&lt;/a&gt;, &lt;a href="http://code.google.com/p/django-mptt/"&gt;django-mptt&lt;/a&gt; и многие многие другие) с одной стороны, так и тех, которые разрабатываются исключительно для текущего веб-сайта, под одной общей крышей (settings.py, urls.py).&lt;/p&gt;

&lt;p&gt;В идеале архитектура любого моего проекта выглядит как:
&lt;pre&gt;project/
    application/
    another_application/
    locale/
    templates/
    settings.py
    urls.py&lt;/pre&gt;
Т.е. по-большому счету ничего лишнего. Управление и деплоймент проекта ведется при помощи универсальной утилиты &lt;a href="http://www.djangoproject.com/documentation/django-admin/"&gt;django-admin.py&lt;/a&gt;, которой передаются переменные DJANGO_SETTINGS_MODULE (settings) и PYTHONPATH (абсолютный путь к project/) (опять же в идеале создаются алиасы для каждого из проекта вида django-project и это в дальнейшем облегчает их использование в коммандной строке).&lt;/p&gt;

&lt;p&gt;И казалось бы жизнь прекрасна и чудесна. Но так было до сегодняшнего дня, а сегодня я решил установить &lt;a href="http://code.google.com/p/django-compress/"&gt;django-compress&lt;/a&gt; - приложение для сжатие CSS и JavaScript файлов. И одним из условий &lt;a href="http://code.google.com/p/django-compress/wiki/Usage"&gt;его использования&lt;/a&gt; было выполнение комманды
&lt;pre name="code" class="bash"&gt;$ ./manage.py synccompress&lt;/pre&gt;
Я, конечно, ничуть не смутился и попытался было:
&lt;pre name="code" class="bash"&gt;$ django-project synccompress&lt;/pre&gt;
но эта попытка закончилась
&lt;pre name="code" class="bash"&gt;Unknown command: 'synccompress'
Type 'django-admin.py help' for usage.&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Причина оказалось простой и, наверное, вполне логичной. django-admin.py вызывает &lt;a href="http://code.djangoproject.com/browser/django/trunk/django/core/management/__init__.py#L258"&gt;django.core.management.execute_from_command_line&lt;/a&gt;, manage.py же, лежащий в директории проекта, после его создания (startproject), вызывает &lt;a href="http://code.djangoproject.com/browser/django/trunk/django/core/management/__init__.py#L265"&gt;django.core.management.execute_manager&lt;/a&gt;. Вся же разница состоит в том, какая утилита управления (ManagementUtility) инициализируется и выполняется в этих функциях. И в итоге, ProjectManagementUtility считывает дополнительные команды, которые находятся в директории &lt;code&gt;commands&lt;/code&gt; всех приложений, перечисленных в &lt;a href="http://www.djangoproject.com/documentation/settings/#installed-apps"&gt;INSTALLED_APPS&lt;/a&gt; проекта. Именно это и дает возможность &lt;a href="http://webnewage.org/post/2008/2/5/komandovat-paradom-budet-django/"&gt;выполнения кастомных команд&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Собственно все это и навело на мысль создания "магичной" (новый тренд в джанго-среде) утилиты django-manage.py. Программный код этого велосипеда:
&lt;pre name="code" class="python"&gt;#!/usr/bin/env python

import os, sys

if __name__ == '__main__':
    try:
        dirname = os.environ['PWD']
    except KeyError:
        import subprocess
        dirname = subprocess.Popen('cd', shell=True, stdout=subprocess.PIPE).communicate()[0].strip()
    sys.path.append(dirname)

    try:
        settings = 'settings'
        try:
            __import__(settings)
        except ImportError, e:
            settings = os.path.basename(dirname) + '.settings'
            __import__(settings)
    except ImportError, e:
        sys.stderr.write("Error: Can't find the file 'settings.py' in the current work directory %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % dirname)
        sys.exit(1)

    os.environ['DJANGO_SETTINGS_MODULE'] = settings
    from django.core import management
    utility = management.ProjectManagementUtility(None, os.path.basename(dirname))
    utility.execute()&lt;/pre&gt;
Установка выполняется копированием сохраненного кода в PATH и выдачей ему прав на исполнение (последнее только для Unix'ов).&lt;/p&gt;

&lt;p&gt;Примеры использования тоже вполне очевидны:
&lt;pre name="code" class="bash"&gt;$ cd /path/to/project
$ django-manage.py help
django-manage.py &lt;subcommand&gt; [options] [args]
Django command line tool, version 0.97-pre-SVN-unknown
Type 'django-manage.py help &lt;subcommand&gt;' for help on a specific subcommand.
Available subcommands:
  adminindex
  createcachetable
  dbshell
  diffsettings
  dumpdata
  flush
  inspectdb
  loaddata
  reset
  runfcgi
  runserver
  shell
  sql
  sqlall
  sqlclear
  sqlcustom
  sqlflush
  sqlindexes
  sqlinitialdata
  sqlreset
  sqlsequencereset
  startapp
  &lt;strong title="Кастомная команда"&gt;synccompress&lt;/strong&gt;
  syncdb
  test
  testserver
  validate&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Ну и подитоживая могу лишь порадоваться, что процесс расширения Django под свои нужды проходит так просто и безболезненно.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Важно!&lt;/strong&gt; Сей метод подходит лишь для архитектуры, описанной мной в начале поста, если вы используете архитектуру Django-проектов используйте для этих же целей manage.py, расположенный в корне директории проекта.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPD 1&lt;/strong&gt; Добавлена проверка текущей рабочей директории для Windows, так как в нем нет ключа PWD в словаре os.environ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPD 2&lt;/strong&gt; Добавлена поддержка settings, project.settings (стандартной архитектуры проектов Django).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8645505283412677599-2398682768102285035?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/2398682768102285035/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=2398682768102285035" title="Комментарии: 0" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/2398682768102285035?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/2398682768102285035?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/IkhDif-9fSw/django-managepy-django-adminpy.html" title="django-manage.py или не единым django-admin.py сыты будем" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/05/django-managepy-django-adminpy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MAQXo7fyp7ImA9WxdTF0s.&quot;"><id>tag:blogger.com,1999:blog-8645505283412677599.post-4029375630927337232</id><published>2008-05-14T14:35:00.005+03:00</published><updated>2008-05-14T14:44:00.407+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-14T14:44:00.407+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django weblog" /><category scheme="http://www.blogger.com/atom/ns#" term="security fix" /><title>Django security fix released</title><content type="html">&lt;p&gt;Как сегодня сообщил &lt;a href="http://www.djangoproject.com/weblog/"&gt;оффициальный блог разработчиков Django&lt;/a&gt;, до недавнего времени в форме входа в админ-панель существовала потенциальная дыра безопасности. И именно поэтому они предоставили обновления для транка (ревизия 7521), версий 0.96 (0.96.2), 0.95 (0.95.3) и 0.91 (0.91.2) и попросили быстрейшим образом обновиться ;)&lt;/p&gt;

&lt;p&gt;Больше об этом security fix'е вы можете прочитать на &lt;a href="http://www.djangoproject.com/weblog/2008/may/14/security/"&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/8645505283412677599-4029375630927337232?l=djangonaut.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://djangonaut.blogspot.com/feeds/4029375630927337232/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8645505283412677599&amp;postID=4029375630927337232" title="Комментарии: 0" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/4029375630927337232?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8645505283412677599/posts/default/4029375630927337232?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/djangonaut/~3/pcjMfPcS4OA/django-security-fix-released.html" title="Django security fix released" /><author><name>playpauseandstop</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_wPsSJz8bPyE/SPghyPaDxqI/AAAAAAAAAXU/_68fnX-ebus/S220/dsc00053_1.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://djangonaut.blogspot.com/2008/05/django-security-fix-released.html</feedburner:origLink></entry></feed>

