Контроллер
Какое-то время назад мы уже определили несколько 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. придумайте, пожалуйста, как исходники с подсветкой в Хабр выкладывать — а то я буду все картинками заливать!

