четверг, 13 декабря 2007 г.

Ещё один форум на Django или на перегонки с самим собой

Много людей пишут в своих блогах о Django. Что я рыжый? К тому же, есть что сказать. ;)

Началось всё с того, что я согласился на разработку сайта магазина товаров для скейтеров. Однако требовался не веб-магазин, а портрал сообщества скейтеров, роллеров и bmxеров нашего города, дабы создать некоторый интерес вокруг этого магазина. Основными составляющими которого должны были стать форум и галерея. В общем у меня появилась возможность опробовать на реальном проекте знания о Django, которые я почерпнул из документации и блогов.

Однако это был не первый мой проект на Django. http://bulldozer-nk.com - был сделан ещё год назад (после чего я надолго забросил Django), в процессе первого знакомства с этим фреймворком, и он врядли страдает от идеальности дизайна и реализации.

Времени был месяц, почему объяснять не буду. =) Тут я сам себе злобный буратина.

Форумы на Django пытаются писать все кому не лень. Ведь видимая простота и элегантность реализации завораживает. Но тем не менее реально опробованного и проверенного временем решения до сих пор не видно.

Ничего особенно от форума не требовалось, всё должно было быть просто и без излишеств.

Главное было желаение проверить, а реально ли в столь короткие сроки разработать весь этот функционал: форум, галерея, новости, каталог ссылок и др. И не просто написать, а и развернуть в боевых условиях.

Дык вот: http://skatehouse.ru.

Был соствлен список требований. Впрочем вполне стандартный для подобных приложений. И понеслась.

А зачем писать просто форум когда весомая часть функционала, может быть задействована и в других приложениях. Таким образом родилась идея CMS, что вылилось в следующее, и сократило сроки для всего проекта:


cmscore <-
forum
gallery
links
....


В смысле зависимости слоёв, а не структуры пакетов.

Каждое приложение имеет свои настройки, которые храняться в единственной записи модели myapp.Preferences, специфичный для приложения профиль пользователя (который так и не был заюзан), и набор действий (о действиях будет сказано ниже), который можно производить над ним и его моделями и др.

Базывый функционал, основные атрибуты для профилей пользоватей и множество утилит ушло в cmscore. Опредилился интерфейс модулей. Простой до опупения, в __init__.py джанговского аппа прописывается следующее:


get_preferences() -> экземпляр Preferences
get_statisticis() -> словарь со статистическими данными


и


actions = ...


Как бы то ни было, к этому можно добавлять что угодно. А cmscore всё это собирает и показывает где нужно. Вообще приложение не обязано это определять, оно будет проигнорировано если будет ImportError, т.е. можно подключать стандатные джанговские аппы.

Большинство форм, по крайней мере на начальной стадии, было создано так:


{% include "generic_form.html" %}


где generic_form.html


{% if form.is_multipart %}
<form enctype="multipart/form-data" method="post" action=".">
{% else %}
<form method="post" action=".">
{% endif %}

<table class="form">
{{ form }}
</table>

<div class="buttons">
<input type="submit" value="{% trans "Save" %}">
</div>

</form>


Через процессор контекста в шаблоны передаётся список аппов, чтобы забрать у них экшены, и преференсы. Хотя преференсы также добавляются в словарь pref из контекст процессоров, как и статистика и даже ссылка модуль settings (в основном чтобы иметь доступ к MEDIA_URL).


'cmscore.context_processors.apps',
'cmscore.context_processors.preferences',
'cmscore.context_processors.statistics',
'cmscore.context_processors.settings'


И доступ к ним такой.


{{ stat.cmscore.last_user }}
{{ stat.forum.post_number }}


или


{{ pref.gallery.images_on_page }}


Портлеты. Терминологически появились под влиянием Plone. :) В шаблоны предаются тоже процессорами контекста.


'cmscore.context_processors.portlet_navigator',
'cmscore.context_processors.portlet_login',
'cmscore.context_processors.portlet_counter'


... и используеются так.


{{ portlet_cmscore_login }}


Хотя нужно их наверно собирать в список portlets приложения. ;) И передавать в шаблон именно этот список.

Примитивный счётчик посещений был реализован через middleware, который выдёргивает из базы объект UrlCounter по pk, который равен request.path или создаёт его. Ну и считает отдельно для всего, отдельно для месяца, отдельно для дня и сбрасывает эти отдельные счётчики отдельно.

Действия. Вообще отдельная концепция, хорошая идея и плохая реализация. ;(

Действия для аппа:


actions = (Action(_('Add a group'),
'/forum/group_add/',
'forum.add_group'),
Action(_('Preferences'),
'/forum/preferences/',
'forum.change_preferences'))


... или для модели:


actions = (Action(_('Add a forum'), '/forum/groups/%s/add_forum/', 'forum.add_forum'),
Action(_('Change'), '/forum/groups/%s/change/', 'forum.change_group'),
ActionWithPrompt(_('Delete'), '/forum/groups/%s/delete/', 'forum.delete_group', None,
_('Do you realy want to delete the group?')),
Action(_('Up'), '/forum/groups/%s/up/', 'forum.change_forum'),
Action(_('Down'), '/forum/groups/%s/down/', 'forum.change_forum'),
)


Различия в том, что в URL действий для модели предаётя параметр id объекта. Да, на данный момент поддерживаются урлы только с одной переменной (первый крестик к неудачной реализации), что в моём проекте не было проблемой. Остальное заголовок, условия появления (пермишены и/или функции, принимающии объект пользователя) и всё. ActionWithPrompt через JavaScript показывает диалог перед тем как выполнить переход по нужному урлу, удобно для запроса подтверждения удаления.

Рендерится это всё так, специальным тегом (второй крестик к неудачной реализации).


{% actions apps.forum %}


или


{% actions image %}
{% actions post %}


Потом до меня допёрло, что лучше сделать объект коллектор экшенов, типа ActionGroup и пусть он сам себя рендерит, это более универсально и позволит даже определять контексты, т.е. где и как действия должны рисоваться, можно исключать некоторые действия из некоторых контекстов, где они не имёют смысла, например. Об этом позже, в другой заметке. ;)

Итог. Работа была начата 10-го ноября, а 3-го декабря всё уже работало и радовалo заказчика. Благо он был не особо придирчив к дизайну, т.к. всё было выполнено мной в одиночку, и будь я даже хорошим дизайнером, у меня бы просто не было времени заниматься оформлением. Вообще фреймворк Django оправдал мои надежды и был успешно применён для разработки нескольких веб-приложений (не особо сложных и по минимому, но всё-таки) в весьма сжатые сроки.

Тестирование проводилось только на уровне юзкейсов, методом тыка, т.е. никакого автоматического тестирования не применялось.

После этого было выявлено несколько багов, критичных и не очень, которые были быстро исправлены, ну а пока всё пучком, 10 дней полёт нормальный. :)

Всё пока. Тут получилось несколько сумбурно в виду попытки захватить все важные аспекты хоть маленько, в следующих заметках попробую изложить более детально отдельные части и решения.

Комментариев нет: