views:

159

answers:

2

I'm just getting started with Google App Engine. Currently, my app has two pages: one lists all the inventory currently in stock, and the other is a detail page for a given item. I feel that my coding could be much more DRY. (I'm making all the calls to print headers and footers twice, for instance.)

Here is the code. How can I factor out the repetition?

from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp.util import run_wsgi_app
import os
from google.appengine.ext.webapp import template

def render(filename, main, template_values): 
    path = os.path.join(os.path.dirname(__file__), filename)
    main.response.out.write(template.render(path, template_values))

class Item(db.Model):
    CSIN = db.IntegerProperty()
    name = db.StringProperty()
    price = db.IntegerProperty() # OK that it's ints?
    quantity = db.IntegerProperty()

class MainPage(webapp.RequestHandler):

    def get(self):
        self.response.headers['Content-Type'] = 'text/html'

        render('Views/header.html', self, {'title' : 'Store'})

        self.response.out.write('<h1>This is going to be the best Store app EVER!</h1>')

        items = Item.all().order('name').fetch(10)
        render('Views/table.html', self, {'items': items})

        render('Views/footer.html', self, {})

class Detail(webapp.RequestHandler):
    def get(self, CSIN):
        self.response.headers['Content-Type'] = 'text/html'

        render('Views/header.html', self, {'title' : 'Store'})

        self.response.out.write('<h1>DETAILS %s</h1>' % CSIN)

        # SQL injection risk here, or is that taken care of by the pattern? 
        item = db.GqlQuery("SELECT * FROM Item WHERE CSIN = :1", int(CSIN)).get()
        if (item):
            render('Views/item_detail.html', self, {'item': item})
        else:
            render('Views/item_not_found.html', self, {'CSIN': CSIN})

        render('Views/footer.html', self, {})

application = webapp.WSGIApplication([('/detail/(\d+)', Detail),
                                      ('/.*', MainPage)], debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

Thank you for your comments!

+1  A: 

You could make MainPage and Detail be subclasses of MyRequestHandler, and then use a hook to modify the behavior of get:

class MyRequestHandler(webapp.RequestHandler):
    def get(self,*args,**kws):
        self.response.headers['Content-Type'] = 'text/html'
        render('Views/header.html', self, {'title' : 'Store'})
        self.get_hook(self,*args,**kws)
        render('Views/footer.html', self, {})

class MainPage(MyRequestHandler):
    def get_hook(self):
        self.response.out.write('<h1>This is going to be the best Store app EVER!</h1>')
        items = Item.all().order('name').fetch(10)
        render('Views/table.html', self, {'items': items})

class Detail(MyRequestHandler):
    def get_hook(self, CSIN):
        self.response.out.write('<h1>DETAILS %s</h1>' % CSIN)
        # SQL injection risk here, or is that taken care of by the pattern? 
        item = db.GqlQuery("SELECT * FROM Item WHERE CSIN = :1", int(CSIN)).get()
        if (item):
            render('Views/item_detail.html', self, {'item': item})
        else:
            render('Views/item_not_found.html', self, {'CSIN': CSIN})
unutbu
What does the `*` mean in method calls?
Rosarch
@Rosarch: `foo(*args)` is a way to pass an arbitrary number of arguments to a function. See http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
unutbu
+5  A: 

Here's one possible refactoring:

class BaseHandler(webapp.RequestHandler):

    def get(self, CSIN=None):
        self.response.headers['Content-Type'] = 'text/html'
        render('Views/header.html', self, {'title' : 'Store'})
        self.response.out.write('<h1>%s</h1> '% self.h1(CSIN))
        self.do_body(CSIN)
        render('Views/footer.html', self, {})

class MainPage(BaseHandler):

    def h1(self, CSIN):
        return 'This is going to be the best Store app EVER!'

    def do_body(self, CSIN):
        items = Item.all().order('name').fetch(10)
        render('Views/table.html', self, {'items': items})

class Detail(BaseHandler):

    def h1(self, CSIN):
        return 'DETAILS %s' % CSIN

    def do_body(self, CSIN):
        # no risk whatsoever of SQL injection here;-)
        item = db.GqlQuery("SELECT * FROM Item WHERE CSIN = :1", int(CSIN)).get()
        if (item):
            render('Views/item_detail.html', self, {'item': item})
        else:
            render('Views/item_not_found.html', self, {'CSIN': CSIN})

This uses the Template Method design pattern. A talk of mine about this (and other) DP (with summary transcript and timeline, and pointer to the PDF of the slides) can be found here.

Alex Martelli