Мы двигаемся вперед со скоростью реактивного паравоза, и пока хабралюди читают и осмысливают первую и вторую части статьи, я со скоростью пулемета пишу продолжение. На этот раз речь пойдет о сердце любого веб-приложения —
Какое-то время назад мы уже определили несколько URL в
Как мы видим, определены три типа URL —
Он довольно лаконичен. То есть он предельно лаконичен, но в то же время очень важен — все что он делает, это импортирует содержимое
Как видно из кода выше, данный обработчик будет использоваться для всех запросов, которые пришли на «главную страницу» приложения — а именно, "/". Его код размещен ниже:
Принципиально этот код ничем не отличается от кода
Напомню вам картинку со структурой нашего приложения:
Как следует из описания страницы
Возможно, это и не самое лучшее архитектурное решение, однако в иллюстрационных целях мы объявляем класс и наследуем его от
Описанный подход использует Datastore Query, который начинает со «всех» объектов данного типа, и потом постепенно «уточняет» окончательный набор данных посредством вызовов
Теперь, имея такой полезный класс, мы можем легко описать наш обработчик:
Как видно из кода, обработчик получает инстанс Visitor, ассоциированный с текущим пользователем (возможно, создает его), увеличивает количество посещений на единицу и сохраняет инстанс. После этого пользователь без лишних слов перенаправляется на главную страницу. Пользователя проверять уже не надо, поскольку URL
Код последнего обработчика предельно прост. Он прост настолько, что это навевает на мысли, что там явно должно быть что-то еще:
Действительно, там есть что-то еще. Это что-то называется Представление, использует шаблоны Django и будет описано в следующей части нашей статьи ;-)
Напоминаю, что полный исходный код можно посмотреть в SVN, а я жду ваших вопросов и комментариев!
P.S. придумайте, пожалуйста, как исходники с подсветкой в Хабр выкладывать — а то я буду все картинками заливать!
Контроллер
Какое-то время назад мы уже определили несколько URL в
app.yaml
— пора разобраться, как заставить приложение правильно «реагировать» на них. Вот так выглядят наши маппинги:# $Id: app.yaml 4 2010-01-25 12:14:48Z sigizmund $ application: helloworld version: 1 runtime: python api_version: 1 handlers: - url: /(stats|login) script: main.py login: required - url: .* script: main.py
Как мы видим, определены три типа URL —
/stats
, /login
и «всё остальное». Все три, что характерно, будут обработаны одним и тем же скриптом main.py
, однако настройки отличаются — /stats
и /login
требуют наличия активной пользовательской сессии, в то время как для остальных это не обязательно. Давайте рассмотрим содержимое скрипта main.py
:#!/usr/bin/env python ''' $Id: main.py 4 2010-01-25 12:14:48Z sigizmund $ ''' import controller from google.appengine.ext import webapp from google.appengine.ext.webapp import util from google.appengine.api import users def main(): application = webapp.WSGIApplication([('/', controller.DefaultRequestHandler), ('/stats', controller.StatsRequestController), ('/login', controller.LoginController)], debug=True) util.run_wsgi_app(application) if __name__ == '__main__': main()
Он довольно лаконичен. То есть он предельно лаконичен, но в то же время очень важен — все что он делает, это импортирует содержимое
controllers
и создает инстанс webapp.WSGIApplication
, который сопоставляет URL'ы запросов и соответствующие обработчики. Эти обработчики как правило бывает удобно вынести в отдельный package дабы отделить код служебный от кода реального, который, собственно, и определяет поведение приложения. Рассмотрим эти обработчики по очереди.DefaultRequestHandler — обработчик по умолчанию
Как видно из кода выше, данный обработчик будет использоваться для всех запросов, которые пришли на «главную страницу» приложения — а именно, "/". Его код размещен ниже:
class DefaultRequestHandler(webapp.RequestHandler): ''' Handles default requests - checks whether user is logged in; if it is - saves an information about his visit in the database. ''' def get(self): user = users.get_current_user() page = None if not user: page = view.StartPage(self.request) else: page = view.WelcomePage(self.request) page.render(self.response.out)
Принципиально этот код ничем не отличается от кода
MainHandler
в первой части, за исключением того, что он не генерирует содержимое страницы самостоятельно, а использует какие-то вспомогательные классы. На данном этапе мы не будем рассматривать, что они делают — для нас достаточно знать, что они генерируют HTML соответствующий нашим требованиям — то есть предлагает залогиниться пользователям, которые еще этого не сделали и приветствует тех, кто с этим успешно справился. Login Handler — между двух страниц
Напомню вам картинку со структурой нашего приложения:
Как следует из описания страницы
/login
, пользователь редиректится на нее автоматически и так же автоматически перенаправляется дальше, на станицу /
. Что происходит на этой странице? Для этого нам потребуется рассмотреть сразу два класса.class LoggedInRequestHandler(webapp.RequestHandler): def currentVisitor(self): user = users.get_current_user() # we shouldn't check user, as /login and /stats specifies # login:required in app.yaml q = model.Visitor.all() q.filter('user = ', user) qr = q.fetch(2) if len(qr) == 0: u = model.Visitor() elif len(qr) > 1: # something is horribly wrong here, it shouldn't happen # but it still could logging.error("Duplicating user %s in datastore" % user.nickname()) raise Exception("Duplicating user %s in datastore" % user.nickname()) else: u = qr[0] self.currentVisitor = u return u
Возможно, это и не самое лучшее архитектурное решение, однако в иллюстрационных целях мы объявляем класс и наследуем его от
webapp.RequestHandler
. Как видно, описанный класс не определяет метода get
— то есть в качестве независимого разработчика он будет довольно бесполезен. Все, что он делает — предоставляет метод currentVisitor()
, который либо достает из Datastore либо создает новый инстанс Visitor
и возвращает его. Рассмотрим этот код подробнее.Описанный подход использует Datastore Query, который начинает со «всех» объектов данного типа, и потом постепенно «уточняет» окончательный набор данных посредством вызовов
filter()
и ancestor()
. Приведенный пример очень прост, однако показывает практически все, что нужно, чтобы извлечь из Datastore необходимую запись; разумеется, в реальных приложениях этот запрос будет наверняка намного сложнее.Теперь, имея такой полезный класс, мы можем легко описать наш обработчик:
class LoginController(LoggedInRequestHandler): ''' We use this controller just for handling the login event ''' def get(self): u = self.currentVisitor() u.hits = u.hits + 1 u.put() self.redirect('/')
Как видно из кода, обработчик получает инстанс Visitor, ассоциированный с текущим пользователем (возможно, создает его), увеличивает количество посещений на единицу и сохраняет инстанс. После этого пользователь без лишних слов перенаправляется на главную страницу. Пользователя проверять уже не надо, поскольку URL
/login
помечен как требущий обязательного логина (то есть если пользователь попытается зайти на него без аутентификации, он будет автоматически перенаправлен на страницу входа). StatsRequestController — статистика по запросу
Код последнего обработчика предельно прост. Он прост настолько, что это навевает на мысли, что там явно должно быть что-то еще:
class StatsRequestController(LoggedInRequestHandler): def get(self): u = self.currentVisitor() page = view.StatsPage(self.request, u) page.render(self.response.out)
Действительно, там есть что-то еще. Это что-то называется Представление, использует шаблоны Django и будет описано в следующей части нашей статьи ;-)
Напоминаю, что полный исходный код можно посмотреть в SVN, а я жду ваших вопросов и комментариев!
P.S. придумайте, пожалуйста, как исходники с подсветкой в Хабр выкладывать — а то я буду все картинками заливать!