<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2694977231610590835</id><updated>2024-09-07T01:25:36.919+03:00</updated><category term="django"/><category term="git"/><category term="github"/><category term="hg"/><category term="python"/><category term="web.py"/><title type='text'>Parse the world</title><subtitle type='html'>Блог про Python, Linux и прочие технические штучки с которым сталкиваюсь каждый день.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://getopts.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://getopts.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>redbaron</name><uri>http://www.blogger.com/profile/13540996795534025793</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZVNWLzgWYRknQu_I6yu3VpL4LPappX_haaBFOPpHoUcf2nGVdRl9eqArN0nyAs_T4kNf4z6uDlNvCuFKGPDWDbS87TOZao1C_I7fxR4JXbQR_vdAxgEMUmwudZUIOmw/s220/x_03aa5a6a.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>5</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2694977231610590835.post-1369506388734754699</id><published>2009-04-26T20:49:00.006+04:00</published><updated>2009-05-07T12:01:03.172+04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="django"/><category scheme="http://www.blogger.com/atom/ns#" term="git"/><category scheme="http://www.blogger.com/atom/ns#" term="github"/><title type='text'>Git на практике на примере django</title><content type='html'>&lt;h2&gt;Как пропатчить django под себя и не сломать себе жизнь.&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;django&lt;/strong&gt; хороший проект, но очень уж тормозной в плане мелких доделок. Да и не мелких тоже - баги висят по полгода-год, даже с приложенными патчами. Ждать пока великие хранители django снизойдут до рассмотрения ваших низменных потребностей не очень хочется, благо прогресс не стоит на месте и сделать это проще простого.&lt;/p&gt;
&lt;p&gt;План таков:&lt;ol&gt;
&lt;li&gt;Настроить git&lt;/li&gt;
&lt;li&gt;Склонировать django репозиторий&lt;/li&gt;
&lt;li&gt;Создать свой branch (ветку), где мы будем прикладывать нужные нам патчи&lt;/li&gt;
&lt;li&gt;Собственное прикладывание патчей, вытягивание изменений из других веток, тыренье кода у друзей и соседей&lt;/li&gt;
&lt;li&gt;Поддержание всего этого добра в актуальном состоянии&lt;/li&gt;
&lt;/ol&gt;
Приступим.&lt;/p&gt;
&lt;h2&gt;Настройка и клонирование git.&lt;/h2&gt;
&lt;p&gt;Тут всё просто.
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ git config --global user.email &quot;your@email.ru&quot; 
$ git config --global user.name &quot;Your Name&quot; 
$ git clone git://github.com/django/django.git 
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Сначала настраиваем на текушей машинке наши учетные данные, так удобнее потом будет разгребать что мы напатчили, а что из далека приплыло с обновлениями. Потом клонируем django репозиторий  с github (мега ресурс, долгих лет жизни ему), при этом в текущей директории будет создана папка django, а в ней появятся файлы из trunk. По состоянию на сегодняшнее число вся история всех веток от начала времен съест у вас &lt;strong&gt;27 MiB&lt;/strong&gt; трафика.&lt;/p&gt;
&lt;h2&gt;Создаём свою песочницу.&lt;/h2&gt;
&lt;p&gt;Теперь построим свою версию django, с блекджеком и шлюхами. Для этого надо всего ничего:
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ git checkout -b dj-patched origin/master
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Таким образом мы создали ветку dj-patched из удаленной ветки origin/master (соотвествует svn trunk). Если вы захотите взять за основу какую-то другую ветку то их список можете посмотреть так:
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ git branch -a            
* dj-patched
  master
  origin/0.90-bugfixes
  origin/0.91-bugfixes
  origin/0.95-bugfixes
  origin/0.96-bugfixes
  origin/HEAD
  origin/admin_changelist_refactor
  origin/admin_generic_relations
  origin/attic
  origin/boulder-oracle-sprint
  origin/connection_pooling
  origin/formset_unique_data
  origin/formset_unique_together
  origin/full-history
  origin/generic-auth
  origin/gis
  origin/i18n
  origin/inline_mi
  origin/magic-removal
  origin/master
  origin/modelform_unique_validation
  origin/multi-auth
  origin/multiple-db-support
  origin/multiprocess_tests
  origin/new-admin
  origin/newforms-admin
  origin/onetoone_as_pk
  origin/per-object-permissions
  origin/queryset-refactor
  origin/releases/1.0.X
  origin/schema-evolution
  origin/schema-evolution-ng
  origin/search-api
  origin/sqlalchemy
  origin/unicode
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Большинство этих веток либо заброшенное старье, либо эксперименты с кодом. Если вы тёртый калач, который доверяет только стабильным релизам, то советую обратить внимание на ветку &lt;em&gt;origin/releases/1.0.X&lt;/em&gt;, мне же, как ярому красноглазику веселее сидеть на trunk.&lt;br/&gt;
Звездочкой в листинге выше помечена ветка, на которой мы сейчас находимся (т.е. файлы, которые мы видим по ls).
&lt;/p&gt;

&lt;h2&gt;Делаем всё, что захотим.&lt;/h2&gt;
&lt;p&gt;Самое время добавить немного блекджека. Для примера возьмем два супер мелких бага, которые будут висеть вечно: &lt;a href=&quot;http://code.djangoproject.com/ticket/9631&quot;&gt;#9637&lt;/a&gt; и &lt;a href=&quot;http://code.djangoproject.com/ticket/6877&quot;&gt;#6877&lt;/a&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ ... накладывам патч, правим код ...
$ git commit -a -m &quot;Fix #9637, la la la&quot;
$ ... накладываем другой патч, еще правим код ...
$ git commit -a -m &quot;Fix #6877, la la la&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Идея, думаю, понятна. Поправили проблему, проверили что работает, сделали commit, т.е. зафиксировали это состояние на века. Взялись за следующую проблему и так далее по кругу. Не стоит смешивать в одном комите несколько патчей, так другим будет проще разобраться в ваших наработках, да и вам через некоторое время тоже.&lt;/p&gt;
&lt;p&gt; Перед commit можно сделать &lt;em&gt;&lt;strong&gt;git status&lt;/strong&gt;&lt;/em&gt; и &lt;em&gt;&lt;strong&gt;git diff&lt;/strong&gt;&lt;/em&gt;, чтобы увидеть, что же мы наделали.  Если в процессе работы были добавлены новые файлы, то их надо занести в реп командой &lt;em&gt;&lt;strong&gt;git add&lt;/strong&gt;&lt;/em&gt; (можно прямо весь каталог указать, если уверены, что все новые файлы которые там есть, вам нужны в репе).
&lt;/p&gt;
&lt;h3&gt;Одним блекджеком сыт не будешь.&lt;/h3&gt;
&lt;p&gt;Вы думаете вы одни такие, пилите что-то под себя? Таких много, и есть даже те, которым не жалко поделиться. Идем на github и смотрим, кто что выпилил взяв за основу django репозиторий. Для этого у каждого репа есть страничка &lt;strong&gt;&lt;a href=&quot;http://github.com/django/django/network&quot;&gt;Networks&lt;/a&gt;&lt;/strong&gt;. Буквально сразу видим ветку wsgi_translators у пользователя teepark. Кликаем, копаемся, и понимаем, что он написал decorator, который любое wsgi приложение превращает в джанговский view. Глаза вспыхивают красным, капает слюна, и нам во что бы то ни стало надо заполучить этот код. Естественно наши тщательно подобранные и ухоженные патчи тоже должны остаться. Поехали.
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ git remote add gh-teepark git://github.com/teepark/django.git (1)
$ git pull gh-teepark wsgi_translators (2)
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Быстро да? &lt;ol&gt;
&lt;li&gt;Подключили удаленные git репозиторий с адресом git://github.com/teepark/django.git под именем gh-teepark.&lt;/li&gt;
&lt;li&gt;Получить обновления из репозитория gh-teerpark, взять его ветку wsgi_translators и влить её в текущую ветку&lt;/li&gt;
&lt;/ol&gt;
Замечу, что хотя реп gh-teepark и содержит в себе всю историю django от начала времен, нам не придется закачивать её снова, т.к. git умный и понимает, что она у нас уже есть (это работает благодаря сравнению историй нашей и удаленной, а не сравнению файлов как в rsync) и качает только то, что нам действительно нужно. В итоге эта операция заняла &lt;strong&gt;31.91 KiB&lt;/strong&gt; трафика. Владельцы GPRS модемов уже любят git :) Таким образом вы можете подключить неограниченное число git веток, при этом не путаясь в патчах и не проклиная всё на свете.
&lt;/p&gt;
&lt;h2&gt;Ухаживаем за нашим детищем.&lt;/h2&gt;
&lt;p&gt;Предположим, что период накопления безудержного втягивания патчей и чужих наработок прошел, нас всё устраивает и мы хотим просто жить в ногу со временем, т.е. иметь актуальную версию django и втянутых наработок. Новые версии патчей из багзиллы придется всё же накладывать руками, чудес не бывает.
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ git checkout dj-patched (1)
$ git pull (2)
$ git pull gh-teepark wsgi_translators (3)
$ git pull another_repo another_branch
$ ...
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Ну тут пояснений много не требуется. &lt;ol&gt;
&lt;li&gt;Переключаемся на ветку dj-patched (мало ли, где нас носило)&lt;/li&gt;
&lt;li&gt;Получаем обновления из основной django веткой (у нас это origin/master)&lt;/li&gt;
&lt;li&gt;Получаем обновления из других втянутых к нам веток, они же тоже не стоят на месте&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;git&lt;/strong&gt; сам разрулит большенство проблем с объединением изменений. Если в каком-то коммите django владыки будут трогать код в тех же файлах и в тех же строках, которые затрагивают ваши патчи (например баг наконец-то пофиксили или просто проводят свои ремонтные работы, но по другому поводу), то тогда возникнет конфликт. Вам надо будет заглянуть в файл и самим привести конфликтные места в порядок, обычно это не доставляет больших сложностей, да и случается совсем редко.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://getopts.blogspot.com/feeds/1369506388734754699/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2694977231610590835/1369506388734754699' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/1369506388734754699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/1369506388734754699'/><link rel='alternate' type='text/html' href='http://getopts.blogspot.com/2009/04/git-django.html' title='Git на практике на примере django'/><author><name>redbaron</name><uri>http://www.blogger.com/profile/13540996795534025793</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZVNWLzgWYRknQu_I6yu3VpL4LPappX_haaBFOPpHoUcf2nGVdRl9eqArN0nyAs_T4kNf4z6uDlNvCuFKGPDWDbS87TOZao1C_I7fxR4JXbQR_vdAxgEMUmwudZUIOmw/s220/x_03aa5a6a.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2694977231610590835.post-7643509010186066216</id><published>2009-01-21T15:04:00.003+03:00</published><updated>2009-05-07T12:03:53.752+04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="django"/><category scheme="http://www.blogger.com/atom/ns#" term="git"/><category scheme="http://www.blogger.com/atom/ns#" term="hg"/><title type='text'>Django из Git</title><content type='html'>&lt;h2&gt;Пора переходить на лучшее.&lt;/h2&gt;
&lt;p&gt;
Если кому-то надоел ублюдочный SVN, то можно брать Django из ежечасно обновляемого Git:
&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;git clone git://github.com/django/django.git django.git&lt;/code&gt;&lt;/pre&gt;
Или Mercurial:
&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;hg clone http://bitbucket.org/mirror/django/ django.hg&lt;/code&gt;&lt;/pre&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://getopts.blogspot.com/feeds/7643509010186066216/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2694977231610590835/7643509010186066216' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/7643509010186066216'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/7643509010186066216'/><link rel='alternate' type='text/html' href='http://getopts.blogspot.com/2009/01/django-git.html' title='Django из Git'/><author><name>redbaron</name><uri>http://www.blogger.com/profile/13540996795534025793</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZVNWLzgWYRknQu_I6yu3VpL4LPappX_haaBFOPpHoUcf2nGVdRl9eqArN0nyAs_T4kNf4z6uDlNvCuFKGPDWDbS87TOZao1C_I7fxR4JXbQR_vdAxgEMUmwudZUIOmw/s220/x_03aa5a6a.png'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2694977231610590835.post-5679602766772902059</id><published>2008-12-23T12:19:00.011+03:00</published><updated>2008-12-23T17:09:58.210+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="django"/><title type='text'>Хакаем django админку</title><content type='html'>&lt;h3&gt;Как можно прогнуть django админку под себя?&lt;/h3&gt;
&lt;p&gt;Админка в &lt;strong&gt;django&lt;/strong&gt; одна из любимых фич многих её поклонников и один из основных аргументов в холиварах django-vs-все_остальные. На самом деле админка это не сама django, а обычное приложение, просто идущее в комплекте и возможности у нее те же самые, т.е. никакой особой core магии в ней нет. Я хочу сказать, что не надо её бояться, думать как там всё запутано и фиг туда влезешь, чтобы что-то подправить под себя. Это ерунда, ею можно вертеть и в хвост и в гриву, было бы желание и немного знания &lt;strong&gt;Python&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Лишившись страха кидаемся в бой и первым делом определим наши тактические цели.&lt;/p&gt;
&lt;h4&gt;Чего мы добиваемся?&lt;/h4&gt;
&lt;p&gt;У &lt;strong&gt;django&lt;/strong&gt; довольно приличная документация на оф. сайте, по-этому тут мы займемся вопросами не освещенными в ней. Возьмем три примера из реальной жизни, необходимо в зависимости от группы пользователя одни и те же элементы показывать по-разному, а именно:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Перечень объектов в форме списка&lt;/li&gt;
&lt;li&gt;Фильтры в списках объектов&lt;/li&gt;
&lt;li&gt;Отображение формы редактирования новости&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Примеры интересны тем, что во-первых не займут много кода, а во-вторых могут быть полезными кому-то еще, ну и может натолкнут Вас на решение ваших собственных задач :)&lt;/p&gt;

&lt;h4&gt;Заряжаем орудия.&lt;/h4&gt;
&lt;p&gt;Как и сказано в документации, стандартный способ поднастроить админку это унаследоваться от &lt;span style=&quot;font-style:italic;&quot;&gt;admin.ModelAdmin&lt;/span&gt; в файле &lt;span style=&quot;font-style:italic;&quot;&gt;admin.py&lt;/span&gt;. Простейший случай будет выглядеть так:
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
from django.contrib import admin
from mysite.myapp.models import News

class NewsAdmin(admin.ModelAdmin):
    list_display = (&#39;title&#39;, &#39;status&#39;, &#39;pub_date&#39;, &#39;author&#39;)
    list_filter = (&#39;author&#39;,&#39;status&#39;,&#39;category&#39;)
   
admin.site.register(News,NewsAdmin)
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Чтобы разобраться как и что работает, лучше всего держать перед глазами родительский класс &lt;span style=&quot;font-style:italic;&quot;&gt;ModelAdmin&lt;/span&gt; из файла &lt;span style=&quot;font-style:italic;&quot;&gt;django/contrib/admin/options.py&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Как видно ничего нестандартного. Есть модель &lt;span style=&quot;font-style:italic;&quot;&gt;News&lt;/span&gt;, для которой в списке будем выводить поля из &lt;span style=&quot;font-style:italic;&quot;&gt;list_display&lt;/span&gt;, а фильтровать по полям из &lt;span style=&quot;font-style:italic;&quot;&gt;list_filter&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Для достижения наших целей будем наворачивать наш унаследованный класс, пока не будем полностью удовлетворены :) Первым делом подготовимся. Все наши задачи зависят от группы пользователя. Принадлежит ли пользователь группе редакторов (id которой хранится в consts.EDITORS_ID) или нет можно определить так:
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
bool(request.user.groups.filter(pk=consts.EDITORS_ID))
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;Как видно, такую громоздкую хрень писать каждый раз не очень радостно, а менее громоздкого метода в документации к &lt;strong&gt;django&lt;/strong&gt; я не нашёл. Вот для пермишеннов есть &lt;span style=&quot;font-style:italic;&quot;&gt;.has_perm&lt;/span&gt;, а для групп нет =(. Представляю своё наколенное решение вопроса (тут и далее буду писать только новые методы класса NewsAdmin):
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
    def _prepare(self,request):
        &quot;&quot;&quot; 
        Подготовка нужных нам данных
        &quot;&quot;&quot;
        user = request.user
        user.is_editor = bool(user.groups.filter(pk=consts.EDITORS_ID))\
                                or user.is_superuser
        user.is_author = not request.user.is_editor

    def __call__(self,request,url):
        self._prepare(request)
        return super(NewsAdmin,self).__call__(request,url)
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Если посмотреть на код ModelAdmin, то видно, что определение какой url чем обрабатывать идёт в методе &lt;span style=&quot;font-style:italic;&quot;&gt;__call__&lt;/span&gt;, поскольку нам группа нужна во всех типах видов (добавление, изменение, список), то мы оборачиваем родительский &lt;span style=&quot;font-style:italic;&quot;&gt;__call__&lt;/span&gt; в свой, где проводим нужную нам подготовку. В моём случае, теперь всегда будет доступна информация является ли пользователь членом группы редакторов или нет.
&lt;/p&gt;
&lt;p&gt;
В дальнейшем большинство решений будем основывать на подобной технике: переопределить метод, сделать то, что нужно, вызвать родительский метод, доделать то, что нужно. Какой метод переопределять выясняем медитируя над исходниками ModelAdmin.
&lt;/p&gt;
&lt;h4&gt;Перечень объектов в форме списка&lt;/h4&gt;
&lt;p&gt;Задача стоит предельно просто, авторы новостей должны видеть только свои новости, а редактор все.&lt;/p&gt;
&lt;p&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
    def queryset(self, request):
        &quot;&quot;&quot;
        Формируем queryset  для list-view

        Фильтруем статьи по авторству, только если мы не
        супер юзер или редактор
        &quot;&quot;&quot;
        qs = super(NewsAdmin, self).queryset(request) #1
        if request.user.is_editor: 
            return qs                                 #2
        else:
            return qs.filter(author = request.user)   #3
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Я искренне не понимаю, почему про метод queryset не сказано в документации. Этот метод вызывается из &lt;span style=&quot;font-style:italic;&quot;&gt;changelist_view&lt;/span&gt; для получения &lt;span style=&quot;font-style:italic;&quot;&gt;QuerySet&lt;/span&gt; со списком объектов. Тут мы воспользовались замечательным свойством QuerySet&#39;ов, что получив их в одном месте, мы можем продолжить навешивать на них фильтры и отдавать дальше. Так и поступаем:
&lt;ol&gt;
&lt;li&gt;Получаем QuerySet, как оно изначально было задумано авторами django-admin&lt;/li&gt;
&lt;li&gt;Супер пользователям и редакторам отдаём в неизменном виде&lt;/li&gt;
&lt;li&gt;А всем остальным, фильтруем по полю &lt;span style=&quot;font-style:italic;&quot;&gt;author&lt;/span&gt;, оставляя только записи, где автором является текущий залогиненный пользователь.&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;&lt;p&gt;
Вуаля, админка стала чуть более дружественной и удобной.&lt;/p&gt;
&lt;p&gt;
Раз уж мы ограничили видимый список новостей для авторов, то надо идти до конца и заблокировать возможность открывать новость в форме редактирования простым подбором id в URL. Сделаем это так:&lt;ol&gt;
&lt;li&gt;Через админку в разделе удалим право Change News у группы авторов.&lt;/li&gt;
&lt;li&gt;Индивидуально даем разрешение на редактирование статьи, если пользователь совпадает с автором&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;&lt;p&gt;
Т.е. сначала закрутим гайки, потом немного ослабим.&lt;/p&gt;
&lt;p&gt;С первым пунктом проблем возникнуть не должно, админку наверное уже всю истыкали :). Переходим ко второму. Снова покурим исходники ModelAdmin и найдем метод 
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;def has_change_permission(self,request,obj=None):&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Переопределим его в нашем NewsAdmin:
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
    def has_change_permission(self, request, obj=None):
        &quot;&quot;&quot;
        Может ли пользователь заходить на страницу редактирования новости
        &quot;&quot;&quot;
        if not obj:                                                     #1
            return True

        orig = super(NewsAdmin,self).has_change_permission(request,obj) #2
        return orig or (obj.author == request.user 
                          or request.user.is_editor)                    #3
    has_delete_permission = has_change_permission                       #4
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
&lt;ol&gt;
&lt;li&gt;Случай когда &lt;span style=&quot;font-style:italic;&quot;&gt;obj=None&lt;/span&gt; вызывается когда вы еще ходите в районе /admin/ и списка объектов, т.е. когда еще неизвестен конкретный объект для редактирования. Я выбрал просто возвращать True для всех, иначе авторы не смогут получить даже список своих статей, т.к. право Change News мы у них отобрали.&lt;/li&gt;
&lt;li&gt;Получаем права согласно разрешениям на группы (именно так действует оригинальный метод, который мы переопределяем)&lt;/li&gt;
&lt;li&gt;Если права позволяют, то мы ничего не делаем, если вернулся запрет, то в некоторых случаях его ослабляем.&lt;/li&gt;
&lt;li&gt;Приравнял право на изменение к праву на удаление, вы можете поступить по-другому :)&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;
&lt;div class=&quot;warning&quot;&gt;
Обратите внимание, что в третьей строке нельзя использовать &lt;strong&gt;is&lt;/strong&gt; вместо &lt;strong&gt;==&lt;/strong&gt;, т.к. в &lt;strong&gt;Django ORM&lt;/strong&gt;, в отличие от замечательного &lt;strong&gt;SQLAlchemy&lt;/strong&gt; в пределах одной сессии объекты одного типа с одинаковым pk могут быть представлены в виде &lt;span style=&quot;font-weight:bold;&quot;&gt;разных instance&lt;/span&gt;, что лично мне совсем не нравится.
&lt;/div&gt;
&lt;p&gt;В общем получился у нас эдакий row-level-permission&lt;/p&gt;
&lt;h4&gt;Настройка фильтров&lt;/h4&gt;
&lt;p&gt;Теперь редакторы видят все новости  и могут фильтровать по авторам (помните атрибут &lt;span style=&quot;font-style:italic;&quot;&gt;list_filter&lt;/span&gt; в самом начале?). Но авторы-то уже видят только свои новости, а бесполезный фильтр справа у них все равно мозолит им глаза, заствляя их тыкать туда и задавать вопросы &quot;а что это такое и для чего это нужно&quot;?. Надо его убрать, оставив авторам только &lt;span style=&quot;font-style:italic;&quot;&gt;(&#39;status&#39;,&#39;category&#39;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Сначала я бодро решил выдавать list_filter через @property, примерно так
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
    @property
    def list_filter(self):
        if not self.request.user.is_editor:
            return &#39;auth&#39;,&#39;status&#39;,&#39;category&#39;
        else:
            return &#39;status&#39;,&#39;category&#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
но Джанга не поддалась, т.к. в процессе регистрации Админки (admin.site.register(News,NewsAdmin)) она проверяет тип атрибутов и ей всенепременно нужен был list или tuple. Тогда решил зайти сбоку. Удалим из определения в класса &lt;span style=&quot;font-style:italic;&quot;&gt;&quot;list_filter&quot;&lt;/span&gt;, а в &lt;span style=&quot;font-style:italic;&quot;&gt;_prepare(self, request)&lt;/span&gt; сформируем необходимый нам перечень фильтров. Вот что получилось:
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
    def _prepare(self,request):
        &quot;&quot;&quot;
        Подготовка нужных нам данных
        &quot;&quot;&quot;
        user = request.user
        user.is_editor = bool(user.groups.filter(pk=consts.EDITORS_ID))\
                                 or user.is_superuser
        user.is_author = not request.user.is_editor

        #настраивем фильтры под группу
        if user.is_author:
            self.list_filter = (&#39;status&#39;,&#39;category&#39;)
        else:
            self.list_filter = (&#39;author&#39;,&#39;status&#39;,&#39;category&#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Из недостатков: если поле из &lt;span style=&quot;font-style:italic;&quot;&gt;list_filter&lt;/span&gt; будет отстутсвовать в модели, то вы получите более невнятное сообщение об ошибке чем прежде, т.к. при валидации  NewsAdmin класса во время регистрации &lt;strong&gt;django&lt;/strong&gt; пропустит &lt;span style=&quot;font-style:italic;&quot;&gt;list_filter&lt;/span&gt;, как отсутствующий атрибут.&lt;/p&gt;

&lt;h4&gt;Настраиваем форму редактирования&lt;/h4&gt;
&lt;p&gt;У меня в модели News имеется поле &quot;editor_comment&quot;, куда редактор вбивает свои замечания к новости, если отправляет её на доработку автору. Что бы хотелось сделать с этим полем:
&lt;ul&gt;
&lt;li&gt;Не показывать его при добавлении новости&lt;/li&gt;
&lt;li&gt;Не показывать его авторам, если оно пустое&lt;/li&gt;
&lt;li&gt;Показывать его авторам, как обычный текст, если оно не пустое&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;Еще есть поле &#39;category&#39;, заводить категории могут только редакторы, у авторов права на добавление отобраны, но знак &quot;+&quot; все равно виден, что несколько нелогично. Попробуем убрать и его.&lt;/p&gt;
&lt;p&gt;У ModelAdmin есть замечательный метод &lt;pre&gt;&lt;code class=&quot;python&quot;&gt;def get_form(self,request,obj=None,**kwargs):&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;который возвращает настроенный и готовый у употреблению класс формы. Воспользуемся им. Уберем для начала плюсик:
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;
    def get_form(self,request,obj=None,**kwargs):
        &quot;&quot;&quot;
        Динамически создаем форму в зависимости от условий
        &quot;&quot;&quot;
        form = super(NewsAdmin,self).get_form(request,obj,**kwargs)
        if not request.user.has_perm(self.opts.app_label+&#39;.&#39;
                                 +Category._meta.get_add_permission()):
            #у пользователя нет права добавлят группы, убираем отрисовку плюсика
            #делаем это заменой на стандартный виджет
            tmp = form.base_fields[&#39;category&#39;].widget
            form.base_fields[&#39;category&#39;].widget = forms.widgets.SelectMultiple(
                                                            choices=tmp.choices)

        return form
&lt;/code&gt;&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Немного поясню. Сначала мы добываем у родительского класса форму, которая бы отобразилась, если бы мы не вмешались. У класса в атрибуте &lt;span style=&quot;font-style:italic;&quot;&gt;base_fields&lt;/span&gt; хранятся поля будущей формы, а к каждому полю уже прикреплен widget. ModelAdmin, когда создавал класс формы везде прикрутил свои собственные виджеты,в нашем случае там был виджет с плюсиком, от которого мы пытаемся избавиться. Сначала проверяем, есть ли у пользователя право добавлять категорию, и если есть начинаем операцию по удалению плюсика :) Из существующего виджета выдираем список доступных элементов для выбора и создаем новый стандартный &lt;strong&gt;SelectMultiple&lt;/strong&gt;, куда подсовываем список элементов, после чего прописываем новый виджет в поле &lt;span style=&quot;font-style:italic;&quot;&gt;category&lt;/span&gt;.&lt;/p&gt;
&lt;div class=&quot;info&quot;&gt;Особо хочу подчеркнуть, что &lt;strong&gt;form&lt;/strong&gt; это всегда разные экземпляры классов, т.е. ModelAdmin формирует их заново при каждом обращении, а не хранит их у себя где-то жёстко зашитыми, по-этому мы с  этим классом можем делать абсолютно все, и другой пользователь (скажем редактор), зашедший по точно тому же URL одновременно с нами получит свой экземпляр класса, а значит наши изменения его не коснутся. Если хотите узнать подробнее про динамическое создание классов, то почитайте про &lt;strong&gt;__metaclass__&lt;/strong&gt;.&lt;/div&gt;
&lt;p&gt;По похожей схеме разберемся с полем комментариев редактора:
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;  
class FixedTextWidget(forms.Textarea):
    def render(self, name, value, attrs=None):
        if value is None: value = &#39;&#39;
        final_attrs = self.build_attrs(attrs, name=name)
        return mark_safe(u&#39;&amp;lt;div%s&amp;gt;%s&amp;lt;/div&amp;gt;&#39; % (flatatt(final_attrs),
                conditional_escape(force_unicode(value))))

class NewsAdmin(admin.ModelAdmin):
    ..........
    def get_form(self,request,obj=None,**kwargs):
        &quot;&quot;&quot;
        Динамически создаем форму в зависимости от условий
        &quot;&quot;&quot;
        form = super(NewsAdmin,self).get_form(request,obj,*args,**kwargs)
        if not request.user.has_perm(self.opts.app_label+&#39;.&#39;
                                 +Category._meta.get_add_permission()):
            #у пользователя нет права добавлят группы, убираем отрисовку плюсика
            #делаем это заменой на стандартный виджет
            tmp = form.base_fields[&#39;category&#39;].widget
            form.base_fields[&#39;category&#39;].widget =\
                         forms.widgets.SelectMultiple(choices=tmp.choices)

        if not request.user.is_editor:
            if obj and not obj.red_comment:
                #не показывать пустые комментарий не редакторам
                del form.base_fields[&#39;red_comment&#39;]
            elif obj:
                tmp = form.base_fields[&#39;red_comment&#39;].widget =\
                                                      FixedTextWidget()

            #даже если choices были генератором, внутри они стали уже списком
            #оставляем из списка только первые 2 пункта(черновик и редактору)
            tmp = form.base_fields[&#39;status&#39;].widget
            tmp.choices = tmp.choices[:2]
&lt;/code&gt;&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Здесь мы либо вообще удаляем поле, и его в форме больше не будет, либо опять же модифицируем widget, заменяя его на свой, слизанный с &lt;span style=&quot;font-style:italic;&quot;&gt;Textarea&lt;/span&gt;, но вместо тега &lt;span style=&quot;font-style:italic;&quot;&gt;textarea&lt;/span&gt; выводится &lt;span style=&quot;font-style:italic;&quot;&gt;div&lt;/span&gt; и у пользователя уже не будет соблазна что-то в это поле вбивать. Наверняка там будет небольшой косяк с атрибутами от &lt;span style=&quot;font-style:italic;&quot;&gt;textarea&lt;/span&gt; пересаженными на &lt;span style=&quot;font-style:italic;&quot;&gt;div&lt;/span&gt;, я не силен в html вёрстке, отображается нормально и ладно :). Шаманство с &lt;span style=&quot;font-style:italic;&quot;&gt;mark_safe, conditional_escape&lt;/span&gt; и прочие заклинания указанные после &lt;span style=&quot;font-style:italic;&quot;&gt;return&lt;/span&gt; я не трогал от греха подальше, т.к. такими плясками выводятся все стандартные виджеты. :)&lt;/p&gt;
&lt;div class=&quot;warning&quot;&gt;не забудьте поставить  в модели в описаниие этого поля &lt;strong&gt;blank=True&lt;/strong&gt;! Иначе Django ругнется,что вы пытаетесь сохранить объект в базе со слишком пустым полем.&lt;/div&gt;

&lt;h4&gt;Конец&lt;/h4&gt;
&lt;p&gt;Вот и причесали админку на свой лад. А ведь еще можно переопределять её шаблоны и резвиться в save_model. Про это сказано в документации, очень советую почитать.&lt;/p&gt;
&lt;p&gt;Очень сильно мне помогли (особенно в последнем забеге, где игрались с виджетами и формой) связка &lt;strong&gt;python manage.py shell&lt;/strong&gt;+&lt;strong&gt;ipython&lt;/strong&gt;(http://ipython.scipy.org/)+&lt;strong&gt;функция dir&lt;/strong&gt; - очень удобно играться с произвольными объектами, смотреть их поля, править заменять, вызывать и пр. Если надо покопаться в неизвестной области, очень рекомендую!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://getopts.blogspot.com/feeds/5679602766772902059/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2694977231610590835/5679602766772902059' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/5679602766772902059'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/5679602766772902059'/><link rel='alternate' type='text/html' href='http://getopts.blogspot.com/2008/12/django.html' title='Хакаем django админку'/><author><name>redbaron</name><uri>http://www.blogger.com/profile/13540996795534025793</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZVNWLzgWYRknQu_I6yu3VpL4LPappX_haaBFOPpHoUcf2nGVdRl9eqArN0nyAs_T4kNf4z6uDlNvCuFKGPDWDbS87TOZao1C_I7fxR4JXbQR_vdAxgEMUmwudZUIOmw/s220/x_03aa5a6a.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2694977231610590835.post-2346017444698513095</id><published>2008-06-04T15:44:00.005+04:00</published><updated>2008-06-05T12:50:59.926+04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="git"/><title type='text'>Пара  Git  трюков</title><content type='html'>&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; - яркий представитель модных ныне распределенных систем контроля версий. Проще говоря то, что хранит все ваши исходники, формирует патчи, показывает вам, что вы делали неделю назад, и кто тот засранец, который так криво написал вот этот кусок (частенько оказывается, что это вы сами :) ). Иными словами если вас достали бесконечные папки project.old, project.old2, project.work и т.д. то подобные игрушки как раз для вас, рекомендую Git, хотя другие (bzr, hg, darcs) даже не пробовал.&lt;/p&gt;

&lt;p&gt;Не буду тут разводить туториал, как-нибудь в другой раз, просто расскажу про пару приемчиков.&lt;/p&gt;

&lt;h3&gt;Отложить текущие наработки&lt;/h3&gt;
&lt;p&gt;Вы делаете какую-то мега классную фичу, процесс в самом разгаре и тут бац, срочно надо исправить баг. Как вы обычно поступаете?
&lt;pre&gt;&lt;code class=&quot; bash&quot;&gt;
$ git checkout -b undone_work (1)
$ git commit -a -m &quot;незаконченная фича&quot; (2)
$ git checkout master (3)
... правим баг ...
$ git commit -a -m &quot;Исправлен баг&quot;
$ git checkout undone_work (4)
$ git reset --soft HEAD^ (5)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;&lt;li&gt;Создаете временную ветку&lt;/li&gt;
&lt;li&gt;комитите туда то что наработали&lt;/li&gt;
&lt;li&gt;переходите на ветку, где надо исправить баг, исправляем, коммитим&lt;/li&gt;
&lt;li&gt;возвращаемся на временную ветку с нашей недоделаной работой&lt;/li&gt;
&lt;li&gt;отменяем последний коммит, т.к. он безсмысленен сам по себе&lt;/li&gt;
&lt;/ol&gt;
Работать оно, конечно, работает, но у Git есть более удобное средство. &lt;strong&gt;git-stash&lt;/strong&gt;.
В уже описанном выше случае все действия сведутся к:
&lt;pre&gt;&lt;code class=&quot; bash&quot;&gt;
$ git stash (1)
... правим баг ...
$ git commit -a -m &quot;Исправлен баг&quot; (2)
$ git stash pop (3)
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;откладываем текущие наработки. Файлы вернуться в состояние HEAD (т.е. последний коммит текущей ветке)&lt;/li&gt;&lt;li&gt;исправляем баг&lt;/li&gt;&lt;li&gt;возвращаем наши отложенные изменения и продолжаем работу&lt;/li&gt;&lt;/ol&gt;
Как видно, всё намного проще, а главное понятнее :) При этом если исправление бага затронет те же файлы, что мы хачили делая новую супер фичу, то Git сначала попытается разрулить конфликт сам, а если не выйдет предложит это пользователю (в общем как и при git merge)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;git-stash&lt;/strong&gt; умеет класть на полку и доставать оттуда несколько недоделаных работ.
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
$ git-stash (1)
$ git-checkout another_branch
$ git-stash
$ git-stash list (2)
$ git-stash pop stash@{_номер_} (3)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;&lt;li&gt;несколько раз в разных местах вызываем &lt;strong&gt;git-stash&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;просматриваем все что наоткладывали&lt;/li&gt;&lt;li&gt;вернуть недоделку и продолжить над ней работу можно при помощи &lt;strong&gt;git-stash apply stash@{_номер_недоделки_}&lt;/strong&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;

&lt;h3&gt;Возврат потерянных данных&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt;, наверно, можно познавать вечно :). Недавно узнал, как пользоваться reflog. Оказывается &lt;strong&gt;Git&lt;/strong&gt; записывает у себя все ваши действия, даже переходы между ветками! Доступ  прошлым состояним можно получить через HEAD@{}. HEAD в &lt;strong&gt;Git&lt;/strong&gt; всегда указывает на ваше текущее дерево. Переключаясь между ветками вы переносите HEAD (ну можно представить что HEAD  - это считывающая головка ползающая по диску). То, где находились на прошлом шаге (например до переключения на другую ветку) можно получить через HEAD@{1}, на позапрошлом - HEAD@{2} и т.д. Т.е. в &lt;strong&gt;Git&lt;/strong&gt; можно добраться не только до истории комитов!&lt;/p&gt;

&lt;/p&gt;Например, чтобы показать мощь этого инструмента - жёстко удалим коммит, так что в истории по git-log его уже не будет, но всё же попробуем его вытащить обратно:
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;
... правим файл...
$ git-commit -a -m &quot;Исправлен файл А&quot; (1)
$ git-reset --hard HEAD^  (2)
$ git-log (3)
$ git-show HEAD@{1} (4)
$ git-reset --hard HEAD@{1} (5)
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;Сделали необдуманый комит&lt;/li&gt;
&lt;li&gt;Поняли что всё сдали не так и отменили все изменения. Опция &lt;b&gt;--hard&lt;/b&gt; откатывает назад не только текущее дерево, но и историю комитов. &lt;b&gt;HEAD^&lt;/b&gt; ссылается на предыдущий коммит в текущей ветке (не путать с HEAD@{}, который является ссылка на историю самого HEAD).&lt;/li&gt;
&lt;li&gt;Убеждаемся, что в истории комитов отмененного комита нет&lt;/li&gt;
&lt;li&gt;И тут все-таки решаем, что доля рационального в нашем комите все же была. Как его вытащить? На прошлом шаге HEAD как раз указывал на то, что мы сейчас пытаемся достать. Посмотрим так ли это? Комманда git-show просто показывает сообщение комита и его diff, т.е. убеждаемся что мы нашли наш потерянный коммит.&lt;/li&gt;
&lt;li&gt;Тут для примера мы просто назад восстанавливаем дерево на то состояние, которое нам бы хотелось.&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;

&lt;p&gt;Т.е. иными словами всё, что когда-то было зафиксировано в &lt;strong&gt;Git&lt;/strong&gt; можно достать, правда до тех пор, пока вы не сделаете &lt;strong&gt;git-&lt;span class=&quot;blsp-spelling-error&quot; id=&quot;SPELLING_ERROR_4&quot;&gt;gc&lt;/span&gt; --prune&lt;/strong&gt;, который убивает всю ненужную историю изменений, оставляя только историю коммитов. Также можно ссылаться на прошлое состояние не только HEAD, но и конкретной ветки, для ветки master это будет &lt;em&gt;master@{}&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Вот собственно и все, но Git бесконечен, как только открою для себя еще что-то новое - обязательно напишу.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://getopts.blogspot.com/feeds/2346017444698513095/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2694977231610590835/2346017444698513095' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/2346017444698513095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/2346017444698513095'/><link rel='alternate' type='text/html' href='http://getopts.blogspot.com/2008/06/git.html' title='Пара  Git  трюков'/><author><name>redbaron</name><uri>http://www.blogger.com/profile/13540996795534025793</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZVNWLzgWYRknQu_I6yu3VpL4LPappX_haaBFOPpHoUcf2nGVdRl9eqArN0nyAs_T4kNf4z6uDlNvCuFKGPDWDbS87TOZao1C_I7fxR4JXbQR_vdAxgEMUmwudZUIOmw/s220/x_03aa5a6a.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2694977231610590835.post-6988653327200824386</id><published>2008-05-25T21:22:00.002+04:00</published><updated>2009-05-07T12:01:39.558+04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="python"/><category scheme="http://www.blogger.com/atom/ns#" term="web.py"/><title type='text'>Введение и web.py</title><content type='html'>В сети появился еще один блог :). Надеюсь будет кому-то полезным. Писать буду про то, с чем сталкиваюсь ковыряя очередную свою поделку, но в основном всё будет крутиться вокруг Python, Linux и web-штучек в целом.

В последнее время взялся за свой первый в жизни сайт =) Это морда к сервру Ragnarok для моего любительского игрового проекта &lt;a href=&quot;http://simhost.org&quot;&gt;simhost.org&lt;/a&gt;. В качестве средства решения задачи выбрал web.py, т.к. по заверениям авторов он простой, как три копейки и ни в чем не ограничивает свободу выбора. В итоге два дня убил на лазание по инету, ознакомление с туториалами и немногочисленными примерами.  Время провел с пользой: пощупал вживую CSS, вдоволь начитался споров про HTML vs XHTML, начал осиливать vim (это моя, наверное, уже пятая попытка, но на этот раз зашел дальше всего) и вообще нагрузил себя новой информацией по самую макушку. В итоге web.py оказался действительно простым и действительно гибким.

В web.py все данные от пользователя можно получить через &lt;strong&gt;web.input()&lt;/strong&gt;, который возвращает словарь (если быть точнее, то ThreadedDict), но возникла небольшая заминка: как разделить переменные полученные в теле POST запроса от тех, что переданы через URL. Если это сделать, то можно более четко определить, что ты хочешь и чего ты не хочешь от программы. В частности при валидации форм мне видется это полезным. 

Чтобы наглядно показать смешение переменных приведу такой синтетический пример (все подписи к полям выкинул для наглядности).

&lt;PRE&gt;&lt;CODE class=&quot; html&quot;&gt;&lt;SPAN class=&quot;tag&quot;&gt;&amp;lt;&lt;SPAN class=&quot;keyword&quot;&gt;form&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; name=&lt;SPAN class=&quot;value&quot;&gt;&quot;login&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; method=&lt;SPAN class=&quot;value&quot;&gt;&quot;post&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; action=&lt;SPAN class=&quot;value&quot;&gt;&quot;/test?a=555&amp;amp;amp;b=666&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
&lt;SPAN class=&quot;tag&quot;&gt;&amp;lt;&lt;SPAN class=&quot;keyword&quot;&gt;input&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; type=&lt;SPAN class=&quot;value&quot;&gt;&quot;text&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; name=&lt;SPAN class=&quot;value&quot;&gt;&quot;login&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; id=&lt;SPAN class=&quot;value&quot;&gt;&quot;login&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
&lt;SPAN class=&quot;tag&quot;&gt;&amp;lt;&lt;SPAN class=&quot;keyword&quot;&gt;input&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; type=&lt;SPAN class=&quot;value&quot;&gt;&quot;password&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; id=&lt;SPAN class=&quot;value&quot;&gt;&quot;password&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; name=&lt;SPAN class=&quot;value&quot;&gt;&quot;password&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
&lt;SPAN class=&quot;tag&quot;&gt;&amp;lt;&lt;SPAN class=&quot;keyword&quot;&gt;input&lt;/SPAN&gt;&lt;SPAN class=&quot;attribute&quot;&gt; type=&lt;SPAN class=&quot;value&quot;&gt;&quot;submit&quot;&lt;/SPAN&gt;&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
&lt;SPAN class=&quot;tag&quot;&gt;&amp;lt;/&lt;SPAN class=&quot;keyword&quot;&gt;form&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/PRE&gt;Как видите, данные с формы отправляются методом &lt;em&gt;POST&lt;/em&gt; по URL &lt;em&gt;/test?a=555&amp;ampb=666&lt;/em&gt;. В итоге в обработчике /test, выполнив &lt;em&gt;web.input()&lt;/em&gt; мы получим словарь вида:
&lt;PRE&gt;&lt;CODE class=&quot;python&quot;&gt;{
&#39;name&#39;:&#39;testname&#39;,
&#39;password&#39;:&#39;testpass&#39;,
&#39;a&#39;:&#39;555&#39;
&#39;b&#39;:&#39;666&#39;
}&lt;/CODE&gt;&lt;/PRE&gt; Конечно, в данном случае мы сами смешали переменные, но согласитесь, что обрабатывая результат заполнения формы, мы ожидаем только переменные из &lt;em&gt;POST&lt;/em&gt;, так давайте только их и обрабатывать, просто на всякий случай, чтобы спать спокойно.

Как часто бывает в мире OpenSource лучшая документация это код. В моём недолгом освоении web.py это правило сработывало неоднократно, так получилось и на этот раз. Код &lt;em&gt;web.input()&lt;/em&gt; находится в &lt;em&gt;web/webapi.py&lt;/em&gt;, при желании можете посмотреть сами, но результатом моих поисков стало следующее знание: &lt;em&gt;web.input(_method=&#39;post&#39;)&lt;/em&gt; и &lt;em&gt;web.input(_method=&#39;get&#39;)&lt;/em&gt; выдадут только переменные переданные методом POST и GET соответственно.

На этом всё. В следующем сообщении расскажу про валидатор форм, выполненный ввиде декоратора цельнопизженного из Pylons. Правда эта конструкция в моих руках может и не взлететь, тогда расскажу о чем-нибудь другом.</content><link rel='replies' type='application/atom+xml' href='http://getopts.blogspot.com/feeds/6988653327200824386/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2694977231610590835/6988653327200824386' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/6988653327200824386'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2694977231610590835/posts/default/6988653327200824386'/><link rel='alternate' type='text/html' href='http://getopts.blogspot.com/2008/05/inithighlightingonload.html' title='Введение и web.py'/><author><name>redbaron</name><uri>http://www.blogger.com/profile/13540996795534025793</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZVNWLzgWYRknQu_I6yu3VpL4LPappX_haaBFOPpHoUcf2nGVdRl9eqArN0nyAs_T4kNf4z6uDlNvCuFKGPDWDbS87TOZao1C_I7fxR4JXbQR_vdAxgEMUmwudZUIOmw/s220/x_03aa5a6a.png'/></author><thr:total>1</thr:total></entry></feed>