6 ноября 2010 г.

musicmans.ru | Как сделать сайт на Django | GWT

Посмотрел я на дерево жанров и оно мне не понравилось. Страшное, неудобное. И решил сразу заняться клиентской стороной. Тем более у нас есть отличнейший повод!

Итак, настроим gwt. Скачиваем eclipse 3.6 для java.
Далле переходим на страницы с загрузками GWT. Ставим gwt плагин для eclipse.

Создаем проект File > New > Web Application Project.

Название: genre
package: ru.musicmans

Запуск - Debug As > Web Application.

Переходим по адресу, устанавливаем плагин. Все работает.

Устанавливаем GWT Designer. Читаем quick start.

Далее можно конвертировать, созданный проект из gwt plugin (gwt plugin мы поставили потому, что в нем все равно находиться сам gwt) в gwt java project, который идет с дизайнером (правой кнопкой на проекте - convert to) или создать новый:



Сразу создадим модуль ru.musicmans.genre.GenreTree:



Итак, проект создан. Открываем genre.java и кликаем на вкладку Design.



Что нам надо для организации передачи данных? Мы не можем использовать rpc call gwt, так как у нас на стороне сервера django. Что делать в данном случае? Я рассматривал данный вопрос около года назад. Итог такой: возможен вывод данных в темплейте и преобразование их в javascript object, но это не очень оптимальный путь, тем более, что приложению обычно нужны данные в процессе работы (в том числе, обновленные). Поэтому лучшим решением мне предоставляется REST (с помощью этого подхода не надо проходить новый путь создания интерфейсов сервисов). Я решил не использовать SmartGWT, слишком он навороченный. В чистом GWT нет поддержки REST, поэтому воспользуемся Restlet Framework, ну а со стороны django - django-piston.

Скачаем Restlet Framework, Edition for Google Web Toolkit. Установим (укажем java build path в свойствах проекта).

Django-Piston

Теперь перейдем к серверной стороне. django-piston я уже как-то упоминал. Так вот, устанавливаем:

>c:\Python26\Scripts\pip.exe install hg+http://bitbucket.org/jespern/django-piston@c4b2d21db51a#egg=piston

Читаем документацию. Создадим приложение api, пропишем (r'^api/', include('api.urls')), в главном urls.py. Создадим в приложении файлы urls.py и handlers.py. Остальные файлы, кроме __init__.py можно удалить.
handlers.py:

from django.core.urlresolvers import reverse

from piston.handler import BaseHandler#@UnresolvedImport

from genre.models import GenreDirStyle#@UnresolvedImport

class GenreHandler(BaseHandler):
allowed_methods = ('GET', )
fields = ('name', 'type', 'url' )
model = GenreDirStyle

@classmethod
def url(self, genre):
return reverse('genre_genre', args=[genre.id])

def read(self, request, genre_id):
genre = GenreDirStyle.objects.get(id=int(genre_id))
return genre

urls.py:

from django.conf.urls.defaults import *

from piston.resource import Resource#@UnresolvedImport

from api.handlers import GenreHandler#@UnresolvedImport

genre_resource = Resource(handler=GenreHandler)

urlpatterns = patterns('',
url(r'^genre/(?P[^/]+)/$', genre_resource, name='api_genre_id'),
)


Переходим по адресу, например http://localhost:8000/api/genre/3/ :

{
"url": "/genre/id/4/",
"type": 3,
"name": "Prog-Rock"
}

Чтобы открывать application/json в firefox, установите дополнение, а еще лучше используйте RESTClient для Firefox.

Вернемся к клиентской части.

Добавим widget дерево в gwt приложение tree:






Запускаем отладку, кстати, сразу рекомендую изменить параметр logLevel в конфигурации отладки.

С помощью firebug видим, что ответа на запрос по адресу http://localhost:8000/api/genre/3 не увенчались успехом, поэтому вспоминаем про проблему SOP.

В процессе поиска решений наткнулся на django-crossdomainxhr-middleware.py, позволяющее использовать кроссдоменные запросы (требуется firefox > 3.5).



Но мы пока его использовать не будем.

Итак, проблему SOP при разработке решим следующим образом. Отключим Jetty сервер, запускаемый в отладке, а также укажем порт 8000.


Сделаем символическую ссылку с директории war проекта на www\media\static\gwt\genre, под windows, например, так:

www\media\static\gwt>mklink /d genre d:\path\to\gwt\war\

Далее, можно отлаживать приложение по адресу примерно такому (предварительно запустив веб-сервер отладки django) http://localhost:8000/media/static/gwt/genre/GenreTree.html?gwt.codesvr=127.0.0.1:9997 адресу. Параметр gwt.codesvr обязателен при отладке gwt приложения.

А еще лучше создать темплейт django, подключив к нему приложение gwt примерно следующим образом. Создаем div с id='genreTreeEntryPointId' в темплейте, а в gwt root panel определяем следующим образом:
RootPanel rootPanel = RootPanel.get("genreTreeEntryPointId");

Теперь мы можем отлаживать gwt приложение прямо в "окружении" django-проекта, например, по адресу такому http://localhost:8000/genre/tree/?gwt.codesvr=127.0.0.1:9997&genre_id=2 .

Серверный и клиентский код выкладывать слишком много, остановимся на нюансах:

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

treeqs = GenreDirStyle.objects.raw("""SELECT t2.*
FROM genre_genredirstyle AS t1
LEFT JOIN genre_genredirstyle AS t2
ON t2.lft BETWEEN t1.lft AND t1.rgt
WHERE t1.lft < %s AND t1.rgt > %s AND t1.tree_id = 1 AND t2.depth-1 = t1.depth AND t2.tree_id = %s
ORDER BY t2.lft;""", (genre.lft, genre.rgt, genre.tree_id))

Построение дерева решил возложить на плечи клиентов.
- При загрузке приложения выводим div с изображением, который заменяется самим приложением, после его загрузки, а также выполнения всех ajax запросов.
- Настройки приложения выводим как JSON объект в javascript, получая значения которого в gwt приложении:

private final Dictionary paramsDict = Dictionary.getDictionary("gwtGenreParameters");
String paramsDict.get("API_GENRES_MAIN_URL");

- Автоматический разбор JSON ответа. Используем данное приложение.
Оно зависит от: google-gin и totoe (погуглите и подключайте в проект).
- Для обозначения состояния элемента дерева используем своей объект класса (сокращенный вид):

private class GenreTreeItemData
{
private int id;
private Boolean alreadyLoaded = false;
private String description;
}

используя функцию setUserObject(Object).
- При создании проекта, в настройках gwt приложения наследуется стиль по умолчанию gwt standart. Так вот, проблема в том, что в нем есть правила css, переопределяющие наши (в том числе body). Решить это можно двумя способами, вот первый, а можно просто удалить нежелательные строки из standard.css файла в директории gwt\standard\ (их там немного и они вначале).
- Для генерации документации по API используем вид from piston.doc import documentation_view.

После работы все как обычно и результат:



ps. На стороне сервера попробовал использовать Aptana 3.0, там действительно отменная поддержка Django темплейтов в PyDev (но наткнулся на баг, ctrl+space вешает IDE, может только у меня так?).