С запуска текущей версии сайта радио было 3 попытки переделать движок. Первую я уже толком и не помню, там изменения больше касались дизайна. В итоге красивый дизайн было нечем забивать (там предполагалось больше контентных блоков, а копирайтера у нас не было и нету) и на него забили. Второй вариант был на джанге. Я повторил на джанге свой старый движок, на на котором работает где-то по миру десятка два сайтов. В итоге меня самого это заебало и я снова забил. Третья попытка началась вчера, когда я окончательно понял, что фиксить поломанный RSS на старом сайте не поднимется рука, а модные фишки, использующиеся в движке этого блога, можно заюзать и на сайте радио. В итоге, к исходу шаббата второго дня кодирования, у меня есть практически весь функционал старого сайта, плюс моднявые фишки типа аттачей к новостям с рендерингом их же в тексте поста.

Но важно не это, важно другое - делать третью версию сайта - сплошное удовольствие. Те же аттачи к новостям делались через три пизды крайне неудобно в оригинальной джанге (моделька для новостей, моделька для аттачей, файлы на диске с именами типа image________.jpg) и крайне красиво и удобно с использованием кауча - выдернул картинку, аттачнул к документу - счастье есть.

Календарик для новостей - непрерывный оргазм. Зацените вот:

CouchDB-view, помогающая строить архив:

function(doc) {
  if (doc.type == 'news') {
    emit([doc.date.slice(0, 4), doc.date.slice(5, 7), doc.date.slice(8, 10)], doc.title);
  }
}

Фрагмент urls.py:

    url(r'^radio/archive/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>.*)/', 'main.views.article'),
    url(r'^radio/archive/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/', 'main.views.article'),
    url(r'^radio/archive/(?P<year>\d{4})/(?P<month>\d{2})/', 'main.views.article'),
    url(r'^radio/archive/(?P<year>\d{4})/', 'main.views.article'),
    url(r'^radio/archive/', 'main.views.article'),

Django-view, отвечающая за календарик

@render_to()
def article(request, year = None, month = None, day = None, slug = None):

    startkey = {}
    endkey = None
    
    if year:
        if month:
            if day:
                if slug:
                    doc = request.db['news_%s-%s-%s_%s' % (year, month, day, slug)]
                    return {'TEMPLATE': 'main/archive/entry.html', 'entry': doc}
                else:
                    startkey = [year, month, day]
                    endkey = startkey
            else:
                startkey = [year, month, {}]
                endkey = [year, month]
        else:
            startkey = [year, {}]
            endkey = [year]
            
    entries = request.db.view('news/archive', startkey = startkey, endkey = endkey, descending = True)
    
    return {'TEMPLATE': 'main/archive.html', 'entries': entries}

Шаблон main/archive.html

<ul>
{%for entry in entries %}
{% ifchanged %}</ul><h3><a href="/radio/archive/{{ entry.key.0 }}/">{{ entry.key.0 }}</a></h3><ul>{% endifchanged %}
{% ifchanged %}</ul><h4><a href="/radio/archive/{{ entry.key.0 }}/{{ entry.key.1 }}/">{{ entry.key.1|month_textual }}</a></h4><ul>{% endifchanged %}
    <li><span class="date"><a href="/radio/archive/{{ entry.key.0 }}/{{ entry.key.1 }}/{{ entry.key.2 }}/">{{ entry.key.2 }}/{{ entry.key.1 }}/{{ entry.key.0 }}</a></span> - <span class="title"><a href="/radio/archive/{{ entry.id|permalink }}/">{{ entry.value }}</a></span></li>
{% endfor %}
</ul>

В php-версии кода было в стопиццот раз больше, он был кривой и страшный и работал не полностью. Тут же всё чисто и прозрачно, кода - с гулькин нос, а выборки из базы - найтривиальнейшие.

По сравнению с предыдущим постом мы научились:

  • Создавать документы с уникальными (но такими как нам надо id-шками.
  • Пинговать (запрашивать, для обновления кэша) все view-функции.

Брать, как обычно, с github'а - http://github.com/angry-elf/django-couch-utils.

Под этим тэгом я буду собирать мои наблюдения относительно сабжа.

Религиозных отличий не просто много, а очень много, поэтому я буду заострять внимание на том, что мне лично кажется очень важным.

В couchdb невозможно быстро сделать delete from ...

Если вдруг появляется такая задача, приходится писать скрипт, делающий это. В процессе написания скрипта действие будет много раз переосмыслено и, возможно, изменится в корне. Легкость delete from в mysql позволяет делать быстрые и ненужные поступки (/me в этом месте сыплет пепел на лысину).

В отлчии от mysql, в couchdb очень сложно делать неиндексированные выборки.

Как это выглядит? Любая выборка сложнее перечня документов с n по m требует написания view-функции. Если нужно, пишется reduce-функция. Причем движок не даст мне написать reduce-функцию, которая порождает слишком большой результат (отключабельно).

Дальше ты можешь дернуть эту view-шку и получит какие-то ряды. Дергать ты можешь только одним способом. Внутренних сортировок нету, join'ов нету, есть только группировка по ключу (да, разумеется, работает с сумасшедшей скоростью). Итог - очень быстрая выборка данных. Не важно, какой был исходный набор данных - 100 рядов или 100 миллионов.

Еще один неявный плюс. В процессе разработки с MySQL накапливается большое количество индексов. Часто они становятся ненужными до продакшен-стадии, часто нужных не хватает. Лишний индекс в большой таблице тормозит insert'ы. Недостающий - тормозит select'ы. Поиск недостающих предусмотрен движком базы. А вот поиск лишних - головная боль. В couchdb же лишние view-функции (т.е. лишние индексы) во-первых, не мешают ни выборкам, ни вставкам, во вторых, отсеиваются обычным grep'ом по исходникам. Красота!

Потратил несколько дней (с перерывом "на подумать" в две недели) на то что б понять, что django-admin хоть и можно допилить для поддержки couchdb, но занятие это:

  • А. Абсолютно неблагодарное. Django-admin испрещён query-set'ами, id-шки подразумевает только числовые, пытается сортировать, фильтровать и пейджить абсолютно несовместимым с couchdb пейджером и прочая, и прочая.
  • Бэ. Чуть более, чем полностью, ненунжное. Django-admin предоставляет классную возможность для стартующего проекта. Вместо редактирования базы данных через PMA и ему подобное, django-admin даёт удобный интерфейс для порождения сущностей, а главное - связанных сущностей, кое является большой головной болью в SQL-ориентированных базах. Глупо и бессмысленно - вначале мы денормализуем наши данные по максимуму, а потом имеем головную боль, как их собирать обратно. Сами же себе ставим палки в колёса, вместо того что бы поменять парадигму.

После нескольких дней активного исследования, в голову пришла мысль - а что, собственно, сэкономит мне django-admin по сравнению с уже существующим удобным механизмом - futon'ом?

  • Создание предопределённых сущностей. Спасибо, приплыли. CouchDB у нас, всё что у нас может быть предопределено - правило генерации id-шек и пара полей (например, type, определяющий сущность). Причем половина полей будет сложных типов, которые django-admin, как и django-forms не в состоянии будут ни отобразить, ни обработать. Значит придётся делать свои классы полей "JSONField" с соответствующей валидацией ("this is not valid json object"). Очень напоминает futon :)
  • Разделение сущностей вместо навигации по длиннющему списку объектов. Секунду, а как это сделать? Написать view-функцию под каждую сущность (entity1/list, entity2/list) и скормить её в Admin-класс, что б нужный раздел админки выводил только нужные документы. Стоп, то же самое сделает и futon.
  • Пейджинг? Хаха. Ничего альтернативного futon'у не получится при всём желании. Ну нету у нас страниц, да и впринципе они не нужны. Назад-вперёд - наше всё
  • Сортировки? Фильтры? Поиск? К сожалению, от всего это пришлось отказаться, сделав ставку на CouchDB.

Итого, моя реализация django-admin будет, в лучшем случае, подобием futon'а. В более вероятном случае - будет плохим подобием. Так как django-admin с большим трудом может заменить обычную нормальную среднестатистическую админку (из десятка написанных с использованием django проектов, ни один не использовал django-admin в качестве интерфейса администратора, везде было нужно гораздо большее, чем редактирование связанных сущностей).

Вывод: не стоит тратить время. При старте проекта - futon и только futon. Впоследствии - нормальная, полноценная админка, ничего общего с django-admin не имеющая.

Уже с год пользуюсь сабжевой связкой. В основном, рад неимоверно. Быстро, надёжно, параллельно. В процессе, наработалось немножко тулзов. Простейший поиск по кейвордам говорит мне, что таких тулзов за год появилось вагон и маленькая тележка, но свой всегда ближе к телу, конечно.

Что мы умеем?

  • Удобно работать с couchdb из django (request.db[document_id] и т.п.)
  • Хранить аутентификационные данные (то бишь, аккаунты юзеров) в couchdb. Ну и, разумеется, использовать из для аутентификации. ToDo - найти-таки, зачем django всё-равно пытается лезть в обычную базу данных и обрубить ей это.
  • Импортировать-экспортировать design-документы. Очень удобно - писать код map/reduce-функций в привычном редакторе, вместо futon'а и синхронизировать их одной командой.
  • Перебирать результаты очень длинных выборок с помощью множества мелких запросов (ну т.е. пейджер для view-шек).
  • Удобно делать bulk-update
  • Генерировать уникальные и красивые id-шки документов

Те, кто знают меня хоть немного, сразу спросят - откуда я взял такой чудесный дизайн? Неужели Эльф на старость лет научился чему-то большему, чем чёрный текст на сером фоне и с чёрными рамочками? Ответ - нет, так и не научился.

Изначальное авторство как движка, так и дизайна, принадлежит моему однополчанину и коллеге (к сожалению, бывшему) по работе, Шемсу Иван Петровичу, в миру, Дмитирию.

Я примерно стопиццот лет и 13 месяцев собирался сделать движок для блога, а вот Шемсу пришёл, увидел и победил. Спасибо ему за это большое. Я посмотрел, как оно хорошо, подпилил чутка (аутентификация, аттачи картинок с ресайзами, пейджер), поломал немножко (вертикальный ритм поломалсо. Шемсу, помогай-спасай!), и гнусно заюзал.

В отличии от примерно триставосемьсот блогодвижков, этот написан с использованием фреймворка django. В качестве базы данных - couchdb. Сочетание получилось очень вкусным. Django я использую в проектах уже полтора года. Около года - couchdb. И то, и другое показало себя только с лучшей стороны. В следущих постах постараюсь описать более детально.

Долго думал, как сделать отдачу аттачей в этом блогодвижке. Я типа могу картинки вставлять (с ресайзами), они хранятся как аттачи к документам каучевским... Вот додумал.

По умолчанию, завожу view-функцию, отдающую аттачи просто как есть, в лоб, по аналогии с django.static.serve, а на продакшне сделал так (внутри virtualhost'а нужного домена, разумеется):

<LocationMatch "^/diary/attachment(/.*\.jpg)$"> - условие достаточно тупое, но мне пока хватает
    <Limit POST PUT DELETE>
        Deny from all                  
    </Limit>
    ProxyPassMatch http://localhost:5984/stereoblog/$1
</LocationMatch>

В итоге, на продакшне картинки отдаются каучем, т.е. у них сразу есть content-length, mime-type, etag (не знаю зачем) и он корректно обрабатывает if-modified-since...