Monday, April 13, 2009

Django Templates

For the first leg of the TA matching project, we made a rather lacking attempt at the project. Our submission was not lacking in the sense of required functionality, but rather maintainability and sound software engineering practices. Our Python code was cluttered throughout with snippets of:

self.response.out.write("""<form action="/login" method="post" >""")
self.response.out.write("""<input type="text" name="user_name">User Name:</input>


Then I discovered the magic of Django templates. Django is an open-source web application framework. Apparently it features "...an object-relational mapper which mediates between data models (defined as Python classes) and a relational database, a regular-expression-based URL dispatcher, a view system for processing requests, and a templating system." (source Wikipedia). My group only used the templating system, which is include in the Google webapp module.

For you to get started with Django templates in the Google App Engine infrastructure, this is what you need to know:

First of all, add the following two import statements to your Python file.

import os
from google.appengine.ext.webapp import template

Now create your html template file, mine is called mainTemplate.html, in the same directory as your Python file, with the following content.

<html>
  <body>
    <b>Hello {{ user }}</>
    <form action="/login" method="post">
      {% for field in formFields %}
        {{ field.printHTML }}
      {% endfor %}
        <input type="submit" />
    </form>
  </body>
</html>

Now the other part of the puzzle is your actual login.py file. When we started the second sprint of the TA matching project, our top priority was to refactor our code into something that was maintainable and scalable. Part of that refactoring included adding the Django template system to our project. Another crucial part that tied into the templates very well was the creation of a Field class for all html form input fields. Basically, there are subclasses of Field for each type of input field, and each field holds:
  • title text to display info about the field

  • an error message to display when the input isn't valid

  • the html name that's embedded in the html

  • a particular Validator object that validates the input (there are many subclasses of Validator that validate specific type of input)

  • a method to print the html for the field

  • a method to get the user's input from the request


Let's look at the login.py file section by section:

  • import statements (already gone over this)

  • constructor

    class MainPage (webapp.RequestHandler) :

      def __init__ (self) :
        # holds all the Fields for the form
        self.formFields = []
        self.user = ""

        # -----
        # login
        # -----
        self.loginTextBox = Field.TextBoxField()
        self.loginTextBox.fieldTitle = "Please log in"
        self.loginTextBox.errorMessage = " -- Invalid login"
        self.loginTextBox.htmlName = "login"
        self.loginTextBox.validator = Validator.NameValidator()
        self.formFields.append(self.loginTextBox)

    Create the formFields list that gets linked to the html template, and add as many Field objects to it as you like (here I've only added one for simplicity).

  • get method

      def get (self) :

        # create a dictionary of values to be referenced in the template
        template_values = { 'formFields': self.formFields, 'user' : self.user }


        # point to the login.html file you created
        path = os.path.join(os.path.dirname(__file__), 'login.html')

        # takes a file path to the template file and a dictionary
        # of values, and returns the rendered text.
        self.response.out.write(template.render(path, template_values))

    Here's where the list of fields 'self.formFields' in my python file gets linked to 'formFields' in the html template and 'self.user' gets linked to 'user' in the html template. So when the template is evaluated, {% for field in formFields %} gets executed just like a regular for loop in python, and {{ field.printHTML }} references the printHTML attribute of each Field object. In many cases, you can also pass Google datastore model objects directly as values, and access their properties from templates.

  • post method

      def post (self) :
        """
        Captures User input
        """
        # assume all fields are valid, and set to false if one isn't valid
        allFieldsValid = True

        for field in self.formFields :
          field.getUserInput(self.request)
          field.validate()
          if not field.isValid :
            allFieldsValid = False

        if allFieldsValid :
          self.user = self.formFields[0].input
          self.get()
        else :
          self.get()

    Here I'm just processing the user's input from the text field, but note the line self.user = self.formFields[0].input. This line now gives real meaning to the {{ user }} in the login.html file. If the page validates correctly, the resulting page now dynamically inserts the validated user's name into the html.



Here's the login.py file in its entirety.

# --------
# login.py
# --------

import cgi

from google.appengine.ext import webapp

# need these two lines templates to work
import os
from google.appengine.ext.webapp import template

import Field # our own Field class
import Validator # our own Validator class

class MainPage (webapp.RequestHandler) :

  def __init__ (self) :
    # holds all the Fields for the form
    self.formFields = []
    self.user = ""

    # -----
    # login
    # -----
    self.loginTextBox = Field.TextBoxField()
    self.loginTextBox.fieldTitle = "Please log in"
    self.loginTextBox.errorMessage = " -- Invalid login"
    self.loginTextBox.htmlName = "login"
    self.loginTextBox.validator = Validator.NameValidator()
    self.formFields.append(self.loginTextBox)

  def get (self) :

    # create a dictionary of values to be referenced in the template
    template_values = { 'formFields': self.formFields, 'user' : self.user }


    # point to the login.html file you created
    path = os.path.join(os.path.dirname(__file__), 'login.html')

    # takes a file path to the template file and a dictionary
    # of values, and returns the rendered text.
    self.response.out.write(template.render(path, template_values))

  def post (self) :
    """
    Captures User input
    """
    # assume all fields are valid, and set to false if one isn't valid
    allFieldsValid = True

    for field in self.formFields :
      field.getUserInput(self.request)
      field.validate()
      if not field.isValid :
        allFieldsValid = False

    if allFieldsValid :
      self.user = self.formFields[0].input
      self.get()
    else :
      self.get()

if __name__ == "__main__":
  main()


The combined use of Django templates and our own Field class together has made additions and modifications of our existing code a very rapid and streamlined process.

No comments:

Post a Comment