Сделал setup.py для couchdb-python-curl и django-couch-utils. Теперь их можно установить через easy_install. Еще б документацию написать...
С запуска текущей версии сайта радио было 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...