Pages

Wednesday, April 29, 2009

MVC Controller for webapp using Routes

Foloowing on from my previous post my MVC home brew is coming along slowly. I decided to implement Routes (Rails style routing for Python), basing my wsgi class on one I found on the App Engine Recipe site.

I altered the wsgi class very slightly to use a constructor passing in the request and response objects.
# Initialize matched controller from given module.
__import__(module_name)
module = sys.modules[module_name]

if (module is not None and hasattr(module, class_name)):
controller = getattr(module, class_name)(request, response)
else:
raise ImportError('Controller %s could not be initialized.' % (class_name))

The Controller itself acts as a base class for the actual concrete controllers which will be used in the application itself.
import os
import urlparse
import cgi
import logging
import sys
import traceback
from google.appengine.ext.webapp import template
from SessionManager import SessionManager
from routes.util import url_for

class Controller(object):
def __init__(self, request, response):
self.request = request
self.response = response
self.sessionManager = SessionManager(self.request, self.response)
self.session = self.sessionManager.current()
self.initialize()

def initialize(self):
pass

def handle_exception(self, e, debug):
#self.error(500)
logging.exception(e)
if debug:
lines = ''.join(traceback.format_exception(*sys.exc_info()))
self.response.clear()
self.response.out.write('%s' % (cgi.escape(lines, quote=True)))

def error(self, code):
"""Clears the response output stream and sets the given HTTP error code.

Args:
code: the HTTP status error code (e.g., 501)
"""
self.response.set_status(code)
self.response.clear()

def rendertemplate(self, context, path):
if (path is None):
path = __file__ + '.html'

if (context is None):
context = dict()

context['session'] = self.session
context['request'] = self.request

path = os.path.join(os.path.dirname(__file__), path)
logging.warn(path)
self.response.out.write(template.render(path, context))

def redirect(self, uri, permanent=False):
"""Issues an HTTP redirect to the given relative URL.

Args:
uri: a relative or absolute URI (e.g., '../flowers.html')
permanent: if true, we use a 301 redirect instead of a 302 redirect
"""
if permanent:
self.response.set_status(301)
else:
self.response.set_status(302)

absolute_url = urlparse.urljoin(self.request.uri, uri)
self.response.headers['Location'] = str(absolute_url)
self.response.clear()

The Controller class has a couple of methods of note:

def rendertemplate(self, context, path): which takes a dictionary (context) and the relative path to an HTML template. The templates themselves will use the Django template markup tags.

def redirect(self, uri, permanent=False): which does an HTTP 301 or 302 redirect.

Here's an example concrete Controller class where the view method is the action as defined using Routes.

import twitter as twitter
from controllers.Controller import Controller as controllerBase

class ConcreteController(controllerBase):
def view(self, id):

data = doSomething(id)

template_values['data'] = data

self.rendertemplate(template_values, '../views/template.html')

No comments: