web.py part #2
Попробуем написать блог, полностью писать груз (пока что) , сделаем простое добавление записей с поддержкой markdown'a.
Будут использованы такие модули:
В рамках данной статьи получим "блог" (наверно рано обзывать "это" подобным словом =) ) с возможностью добавения записей, просмотра единичных записей... и пожалуй хватит))
В качестве БД будет использоваться SQlite; markdown - для форматирования текста в постах, вместо всяких bb-code и raw html'я; pytils - для slug'a (для ссылок на посты, чтоб можно было обращаться к записям не по их ID, а по понятному slug'y полученному из заголовка поста), wsgi сервер - дефолтный.
Структура файлов будет примерно такой:
blog/
blod.db - база
code.py - само WSGI-приложение
config.py - конфиг блога для всяких переменных
model.py - описание модели и ф-ций для работы с БД
view.py - описание форм
tmpl/ -темплейты
add_entry.html
index.html
Будут использованы такие модули:
В рамках данной статьи получим "блог" (наверно рано обзывать "это" подобным словом =) ) с возможностью добавения записей, просмотра единичных записей... и пожалуй хватит))
В качестве БД будет использоваться SQlite; markdown - для форматирования текста в постах, вместо всяких bb-code и raw html'я; pytils - для slug'a (для ссылок на посты, чтоб можно было обращаться к записям не по их ID, а по понятному slug'y полученному из заголовка поста), wsgi сервер - дефолтный.
Структура файлов будет примерно такой:
blog/
blod.db - база
code.py - само WSGI-приложение
config.py - конфиг блога для всяких переменных
model.py - описание модели и ф-ций для работы с БД
view.py - описание форм
tmpl/ -темплейты
add_entry.html
index.html
Начнёмс с модели, она будет довольно простой (model.py):
Хотя в принципе для подобных задач web.db было бы достаточно.
Code.py изначально имеет примерно такой вид:
Опишу подробно строки.
web.webapi.internalerror = web.debugerror подружает отладчик. таким образом кроме вывода ошибки в консоль, в браузере будет появляться отладочная инфа при возникновении внутренних ошибок.
Строкой ниже заменяем ф-цию отвечающую за ответ на 404 ошибку (страница не найдена) на свою (простой print "<b>404 B] </b>")
В urls описываются соответсвия URL и классов, URL задаётся при помощи регулярных выражений.
Ниже задаётся каталог с шаблонами (шаблоны встроенные), кэширование отключено для возможности разработки без перезагрузки сервера, кстати за это отвечает 3й параметр web.run() - web.reloader (в версии 0.21 он не работает)
Класс index включает в себя всего один метод GET для обработки запросов соответсвующего типа, web.header() позволяет изменить http заголовок на нужным, в нашем случае задаём кодировку. Ниже идут ф-ции получения записей их количества.
web.output служит для выдачи результата клиенту, в принципе во всех примерах используется простой print, по больщому счёту разницы никакой, данная ф-ция лишь добавляет строку (в нашем случаем возвращает темплейт) к ответу сервера и кодирует юникод объекты в utf-8, но имхо так более кошерней смотрится В)).
render.index вызывает темплейт. Темплятор находит в каталоге 'tmpl/' шаблон index.html и передаёт ему заданные параметры.
Рассмотрим непосредственно сам темплейт:
чтоб получить доступ к xrange() ф-цие, необходимо добавить в code.py строку web.template.Template.globals['xrange'] = xrange, после чего в шаблонах можно обратиться к ней как $:xrange(), только вот как заюзать эту ф-цию например в цикле for я не вкурил.... ":" говорит о том что метод глобальный, хотя например $:i.entry говорит, что вывод будет осуществяться "так как есть", без прогонки через web.websafe() (эта ф-ция запрещает вывод html, заменяет <> и тп символы).
Также хочу отметить небольшую попу в версии 0.22, unicode объекты не раскодируются в utf-8, поэтому в примере я делал это вручную, в текущей svn версии 0.3 это делается автоматом.
В принципе это наверно всё по темлейтам, думаю добавить нечего) Подробней можно прочесть на офсайте.
Рассмотрим создание и обработку форм на примере добавления записей:
Как я уже говорил в первой заметке web.form довольно удобен и поддерживает валидаторы, с которыми разобрался даже я В)
Добавим 2й класс add_entry, добавим описание формы во view.py (кстати важно чтоб форма была глобальна, и к ней можно было обратиться из метода GET (непосредственного рисования) и POST для обработки и валидации) и шаблон: Метод GET только рисует форму, POST - получает введённые данные через web.input(), получаем объект типа storage(), мы можем получить значение поля login как через i.login, так и через i['login'].value, после чего передаём все введённые данные нашей форме для проверки на валидность, метод validates() говорит нам валидна ли данные, если да - добавляем в базу, перекодируя текст в хтмл, пропуская через markdown (safemarkdown защищает от XSS-уязвимостей и убирает <> и тп, можете глянуть в utils.py). Методweb.seeother() перекидывает на главную страницу, в принципе есть метод web.redirect(), разница лишь в http-ответах (подробности в RFC по HTTP или рассылке по webpy B] )
view.py:
Думаю тут всё понятно, form.notnull делает поле обязательным. Также можно задать более серьёзные валидаторы, кому инетерсно глянет на http://web.py/form
add_entry.html: Здесь я реализовал все свои космические знания по web, забабахав CSS =))
В данном темплейте проверяется форма, если она не валидна - выводится мессага "Incorrect data", при этом автоматом справа от невалидного поля появляется надпись Required (надо будет глянуть как его заменить =) )
Вот так это выглядит:
Вот почти и всё что хотелось показать, осталось сделать выборку постов по slug:
Так как описание ulr'a для данной операции имеет вид /view/(.*) то всё что будет передаваться после /view/ необходимо получить и обработать, для этого метод GET имеет такой вид: Все необходимые данные будут заноситься в slug переменную, по которой и будет проходить выборка.
Запущенное на дефолтном серваке всё это дело ест 8.5мб озу
Вот и всё, писал как можно проще, чтоб было понятно начинающим и интересно бывалым =) Надеюсь не зря убил время В) Думаю в это статье мне удалось описать большую часть ф-ций webpy и способов работы с элементарными вещами и показать что web.py максимально прост, но за этой простотой скрывается достаточный функционал, который при желании легко достигается сторонними модулями. Если сравнивать с другими "большими" фреймворками, то webpy мне напоминает pylons, по крайней мере не вижу сложности взять все модули используемые в пилонах и прикрутить к webpy ) Также webpy подойдёт людям, которые не могу найти нормальный фреймворк, но имеют пристрастия к определённым пакетам для работы с формами, темплейтами, БД и вэбом в целом, опять же из-за простоты привязки. webpy не мешает писать и не навязывает свои правила.
Чёт меня в философию потянуло ёптъ... (O_o)
В дальнейшем постараюсь написать про использование cookie, поддержку сессий родную (в web.py 0.3svn)/через flup, поддержку openid посредством модуля weboid , добавление rss и тп.
Надеюсь желание писать не пропадёт ).
PS: под вэб никогда толком не кодил, решил пострадать фигнёй )
Позже напишу пару строк про настройку lighttp + fcgi для этой гадости (хотя там всё стандартно)
Полные исходники можно взять с http://slav0nic.org.ua/static/files/sl_blog.zip
- blog = Table('blog', metadata,
- Column('id', Integer, primary_key=True), # в sqlite primary_key типа int == autoincrement
- Column('author', String),
- Column('subject', String),
- Column('entry', String),
- Column('slug', String, unique=True),
- Column('tags', String),
- Column('date', DateTime, default=func.current_timestamp()),
- )
Code.py изначально имеет примерно такой вид:
- import web
- import view
- import model
- import config
- web.webapi.internalerror = web.debugerror
- web.webapi.notfound = view.not_found
- urls = (
- '/', 'index',
- '/add', 'add_entry',
- '/view/(.*)', 'view_entry',
- # '/login', 'login',
- # '/logout', 'logout',
- # '/edit/(.*)', 'edit',
- # '/page/(\.*)', 'page',
- # '/view/(.*)/comment', 'comment',
- )
- render = web.template.render('tmpl/', cache=False)
- class index:
- def GET(self):
- web.header("Content-Type","text/html; charset=utf-8")
- posts = model.getEntries()
- entry_count = model.getEntriesCount()
- web.output(render.index(posts, config, xrange(entry_count/config.ENTRIES_PER_PAGE)))
- if __name__ == "__main__":
- web.run(urls, globals(), web.reloader)
Опишу подробно строки.
web.webapi.internalerror = web.debugerror подружает отладчик. таким образом кроме вывода ошибки в консоль, в браузере будет появляться отладочная инфа при возникновении внутренних ошибок.
Строкой ниже заменяем ф-цию отвечающую за ответ на 404 ошибку (страница не найдена) на свою (простой print "<b>404 B] </b>")
В urls описываются соответсвия URL и классов, URL задаётся при помощи регулярных выражений.
Ниже задаётся каталог с шаблонами (шаблоны встроенные), кэширование отключено для возможности разработки без перезагрузки сервера, кстати за это отвечает 3й параметр web.run() - web.reloader (в версии 0.21 он не работает)
Класс index включает в себя всего один метод GET для обработки запросов соответсвующего типа, web.header() позволяет изменить http заголовок на нужным, в нашем случае задаём кодировку. Ниже идут ф-ции получения записей их количества.
web.output служит для выдачи результата клиенту, в принципе во всех примерах используется простой print, по больщому счёту разницы никакой, данная ф-ция лишь добавляет строку (в нашем случаем возвращает темплейт) к ответу сервера и кодирует юникод объекты в utf-8, но имхо так более кошерней смотрится В)).
render.index вызывает темплейт. Темплятор находит в каталоге 'tmpl/' шаблон index.html и передаёт ему заданные параметры.
Рассмотрим непосредственно сам темплейт:
Язык довольно прост, все строки питона начинаются с $, для приёма параметров служит ф-ция with (posts, config, entry_count), стоит обратить внимание, что после with должен стоять пробел, иначе парсер ругнётся, также стоит следить за пробелами после : в циклах =\ (на будущее скажу, что парсер реально гавно=), хотя в целом к темплейтам привыкнуть можно). В принципе не сильно радует отсутствие импортов модулей, всё ф-ции необходимо передавать через глобальный свойство global класса Template. Например
чтоб получить доступ к xrange() ф-цие, необходимо добавить в code.py строку web.template.Template.globals['xrange'] = xrange, после чего в шаблонах можно обратиться к ней как $:xrange(), только вот как заюзать эту ф-цию например в цикле for я не вкурил.... ":" говорит о том что метод глобальный, хотя например $:i.entry говорит, что вывод будет осуществяться "так как есть", без прогонки через web.websafe() (эта ф-ция запрещает вывод html, заменяет <> и тп символы).
Также хочу отметить небольшую попу в версии 0.22, unicode объекты не раскодируются в utf-8, поэтому в примере я делал это вручную, в текущей svn версии 0.3 это делается автоматом.
В принципе это наверно всё по темлейтам, думаю добавить нечего) Подробней можно прочесть на офсайте.
Рассмотрим создание и обработку форм на примере добавления записей:
Как я уже говорил в первой заметке web.form довольно удобен и поддерживает валидаторы, с которыми разобрался даже я В)
Добавим 2й класс add_entry, добавим описание формы во view.py (кстати важно чтоб форма была глобальна, и к ней можно было обратиться из метода GET (непосредственного рисования) и POST для обработки и валидации) и шаблон:
- class add_entry:
- def GET(self):
- web.header("Content-Type","text/html; charset=utf-8")
- web.output(render.add_entry(view.add_entry_form))
- def POST(self):
- web.header("Content-Type","text/html; charset=utf-8")
- i = web.input()
- v = view.add_entry_form(i)
- if v.validates():
- web.output("posting...")
- model.postEntry((i.login, i.subject, i.tags, web.safemarkdown(i.entry.decode('utf-8'))))
- web.output("ok")
- web.seeother("/")
- else:
- web.output(render.add_entry(v))
view.py:
- from web import form
- #форма добавления записи
- add_entry_form = form.Form(
- form.Textbox("login", form.notnull),
- form.Textbox("subject", form.notnull),
- form.Textbox("tags"),
- form.Textarea("entry", form.notnull, rows="30", cols="80" ),
- )
Думаю тут всё понятно, form.notnull делает поле обязательным. Также можно задать более серьёзные валидаторы, кому инетерсно глянет на http://web.py/form
add_entry.html:
- $def with (add_entry_form)
- <style type="text/css">
- th { text-align: left; background-color: #EEEEEE; }
- p.error { background-color: #FFEEEE; }
- </style>
- <form name="main" method="post">
- $if not add_entry_form.valid:
- <p class="error">Incorrect data</p>
- $:add_entry_form.render()
- <input type="submit" />
- $else:
- $:add_entry_form.render()
- <input type="submit" />
- </form>
- </html>
В данном темплейте проверяется форма, если она не валидна - выводится мессага "Incorrect data", при этом автоматом справа от невалидного поля появляется надпись Required (надо будет глянуть как его заменить =) )
Вот так это выглядит:
Вот почти и всё что хотелось показать, осталось сделать выборку постов по slug:
Так как описание ulr'a для данной операции имеет вид /view/(.*) то всё что будет передаваться после /view/ необходимо получить и обработать, для этого метод GET имеет такой вид:
- class view_entry:
- def GET(self, slug):
- ...
Запущенное на дефолтном серваке всё это дело ест 8.5мб озу
Вот и всё, писал как можно проще, чтоб было понятно начинающим и интересно бывалым =) Надеюсь не зря убил время В) Думаю в это статье мне удалось описать большую часть ф-ций webpy и способов работы с элементарными вещами и показать что web.py максимально прост, но за этой простотой скрывается достаточный функционал, который при желании легко достигается сторонними модулями. Если сравнивать с другими "большими" фреймворками, то webpy мне напоминает pylons, по крайней мере не вижу сложности взять все модули используемые в пилонах и прикрутить к webpy ) Также webpy подойдёт людям, которые не могу найти нормальный фреймворк, но имеют пристрастия к определённым пакетам для работы с формами, темплейтами, БД и вэбом в целом, опять же из-за простоты привязки. webpy не мешает писать и не навязывает свои правила.
Чёт меня в философию потянуло ёптъ... (O_o)
В дальнейшем постараюсь написать про использование cookie, поддержку сессий родную (в web.py 0.3svn)/через flup, поддержку openid посредством модуля weboid , добавление rss и тп.
Надеюсь желание писать не пропадёт ).
PS: под вэб никогда толком не кодил, решил пострадать фигнёй )
Позже напишу пару строк про настройку lighttp + fcgi для этой гадости (хотя там всё стандартно)
Полные исходники можно взять с http://slav0nic.org.ua/static/files/sl_blog.zip