English
Skip to page content Skip to main navigation

e.g. "templates" or "datastore"

# The Django Form Validation Framework on Google App Engine

Alexander Power
April 2008

## Introduction

In this article we show how to use Django's form validation framework with the Google App Engine. This framework allows you to construct HTML forms from your data models, and handle the inputted information from the forms seamlessly when interacting with the datastore.

## What is the Django Form Validation Framework?

Django's form validation framework is included with the Django project. It uses a database model to construct well formed HTML forms for your application. It also handles the server-side functionality to validate the entry and put the data entered in to your datastore. With the Django forms framework, you can easily turn your data model into a set of pages that can be used to insert and update data in a datastore. Once the data is in the datastore, you can use GQL queries to access this data.

## How do Django Forms Interact with the Datastore?

The Google App Engine model class, `db.Model`, is not the same as the model class used by Django. As a result, you cannot directly use the Django forms framework with Google App Engine. However, Google App Engine includes a module, db.djangoforms, which casts between the datastore models used with Google App Engine and the Django models specification. In most cases, you can use db.djangoforms.ModelForm in the same manner as the Django framework.

## Shopping List - An Example

We are going to develop a simple application uses Django forms which creates pages that allow you to add and edit items on a shopping list, then stores the information in our datastore. Finally we will use GQL to query the data and display it in our application.

First, we specify the Python module imports and Google App Engine imports for our application. Please note that you must import `google.appengine.webapp.template` before importing any Django modules:

    import cgi
    import wsgiref.handlers

    from google.appengine.api import users
    from google.appengine.ext import db
    from google.appengine.ext import webapp
    from google.appengine.ext.webapp import template

    from google.appengine.ext.db import djangoforms

## Define Model and Form Classes for our Example

Next, we define our model for a shopping list entry as a subclass of the Google App Engine `db.Model` class. For each object, we note the name of the item, how many we want, what price we want to pay, the date we made the entry, and by whom it was made:

    class Item(db.Model):
      name = db.StringProperty()
      quantity = db.IntegerProperty(default=1)
      target_price = db.FloatProperty()
      priority = db.StringProperty(default='Medium',choices=[
        'High', 'Medium', 'Low'])
      entry_time = db.DateTimeProperty(auto_now_add=True)
      added_by = db.UserProperty()

Next, we create the form object based on the model. To do this, we create a class that inherits from `djangofroms.ModelForm`, and create a subclass of that class called `Meta` in which we specify the model and any excluded fields that we do not need in our form. In this case, since we will utilize the Users API to get the person who added the item to the shopping list, we will not need to include the added_by field on our form:

    class ItemForm(djangoforms.ModelForm):
      class Meta:
        model = Item
        exclude = ['added_by']

We also don't need to have the user specify the entrytime. However, DateTime fields with auto_now or auto_now_add set to true are automatically not displayed when a form is generated. Therefore, we do not need to include the entry_time field in the list for exclusions.

## Defining Request Handlers

### Adding an Item

Now, let us create the request handlers for our shopping list. The form will be accessed at the root URL via HTTP GET, and the user will submit it to our application via an HTTP POST to the same URL.

One of the advantages of Django forms is the automatic form validation. If the user submitted data is invalid, it will produce an error and automatically display form messaging that will ask the user to fix their input errors.

To define the HTTP GET request method:

    class MainPage(webapp.RequestHandler):
      def get(self):
        self.response.out.write('<html><body>'
                                '<form method="POST" '
                                'action="/">'
                                '<table>')
        # This generates our shopping list form and writes it in the response
        self.response.out.write(ItemForm())
        self.response.out.write('</table>'
                                '<input type="submit">'
                                '</form></body></html>')

The form will pre-populate the entries with default values where they are provided, and will otherwise leave blanks. Additionally, if a fixed list of choices are provided, a drop-down menu will be created.

Next, we write a method to handle the HTTP POST request:

      def post(self):
        data = ItemForm(data=self.request.POST)
        if data.is_valid():
          # Save the data, and redirect to the view page
          entity = data.save(commit=False)
          entity.added_by = users.get_current_user()
          entity.put()
          self.redirect('/items.html')
        else:
          # Reprint the form
          self.response.out.write('<html><body>'
                                  '<form method="POST" '
                                  'action="/">'
                                  '<table>')
          self.response.out.write(data)
          self.response.out.write('</table>'
                                  '<input type="submit">'
                                  '</form></body></html>')

We use `data.is_valid()` to check that there are no errors in the user input. If errors are found, the form is reprinted with the user entered information and appropriate error messages.

If the form input is valid, data.save will generate the datastore entity. Since we wish to add the user information to the entity, we specify `commit=False`. Without this flag, calling save() would save the entity directly to the datastore. We include the current user information, and then call the `put()` method to save the data to our datastore.

Default values are very effective ways to initialize entities in your datastore. However, we cannot use this to initialize the current user with `users.get_current_user()`, because the value is cached between requests. The Property page has more information.

### Displaying our List

After we have added items to our shopping list, we want to display them to the user when he requests the URL `/items.html`. We create another request handler for this page that queries the datastore using the GQL query language.

    class ItemPage(webapp.RequestHandler):
      def get(self):
        query = db.GqlQuery("SELECT * FROM Item ORDER BY name")
        for item in query:
          self.response.out.write("%s - Need to buy %d, cost $%0.2f each<br>" %
                                  (item.name, item.quantity, item.target_price))

## Writing our `main()` Function

After writing all our handler classes, we need to define the `main()` function for this program to handle the CGI requests:

    def main():
      application = webapp.WSGIApplication(
                                           [('/', MainPage),
                                            ('/items.html', ItemPage),
                                            ],
                                           debug=True)
      wsgiref.handlers.CGIHandler().run(application)

    if __name__=="__main__":
      main()

This main function directs each request to the appropriate handler class based on the URL request.

## Writing our `app.yaml` File

With Google App Engine, the app.yaml file contains the specification as to which script will handle an incoming request. Our application only has one script, form.py, so all requests will be handled by that script:

    application: shoppinglist
    version: 1
    runtime: python
    api_version: 1

    handlers:
    - url: .*
      script: form.py

## Editing Existing Entities

We can extend the above example by allowing the users of our shopping list to edit an existing shopping list entry.

### Modifying our `ItemPage` Class

First, when handling the item page request, we'll add a link next to each item that they can click on to edit the entry. The link will request the `/edit` URL, and include the item id as a query argument.

    class ItemPage(webapp.RequestHandler):
      def get(self):
        query = db.GqlQuery("SELECT * FROM Item ORDER BY name")
        for item in query:
          self.response.out.write('<a href="/edit?id=%d">Edit</a> - ' %
                                  item.key().id())
          self.response.out.write("%s - Need to buy %d, cost $%0.2f each<br>" %
                                  (item.name, item.quantity, item.target_price))

### Adding an `EditPage` Class

Now we must add the `EditPage` class to handle the edit request. This handler is similar to handlers used to add an item.

In the `get()` method, for our EditPage class, we first retrieve the instance we are editing from the datastore, and then pass that instance to form renderer so that the item information can be populated. Also, we add the item id as a hidden input to the form so that we can access the id information when the user submits the edited item:

    class EditPage(webapp.RequestHandler):
      def get(self):
        id = int(self.request.get('id'))
        item = Item.get(db.Key.from_path('Item', id))
        self.response.out.write('<html><body>'
                                '<form method="POST" '
                                'action="/edit">'
                                '<table>')
        self.response.out.write(ItemForm(instance=item))
        self.response.out.write('</table>'
                                '<input type="hidden" name="_id" value="%s">'
                                '<input type="submit">'
                                '</form></body></html>' % id)

Next we write the method to handle the HTTP POST. We query our datastore for the item we are editing, and then validate the edited information using the validation framework available with Django forms. As before, if the information is valid, we put the change in the datastore, or else we allow the user to modify their input:

      def post(self):
        id = int(self.request.get('_id'))
        item = Item.get(db.Key.from_path('Item', id))
        data = ItemForm(data=self.request.POST, instance=item)
        if data.is_valid():
          # Save the data, and redirect to the view page
          entity = data.save(commit=False)
          entity.added_by = users.get_current_user()
          entity.put()
          self.redirect('/items.html')
        else:
          # Reprint the form
          self.response.out.write('<html><body>'
                                  '<form method="POST" '
                                  'action="/edit">'
                                  '<table>')
          self.response.out.write(data)
          self.response.out.write('</table>'
                                  '<input type="hidden" name="_id" value="%s">'
                                  '<input type="submit">'
                                  '</form></body></html>' % id)

### Modifying your `main()` Function

Lastly, we must add the new URL and request handler to our main function:

    def main():
      application = webapp.WSGIApplication(
                                           [('/', MainPage),
                                            ('/edit', EditPage),
                                            ('/items.html', ItemPage),
                                            ],
                                           debug=True)

      wsgiref.handlers.CGIHandler().run(application)

## Additional Notes on Setting Up Django Forms

Django's form validating framework does strict validation against data types. However, it does not provide Javascript validation on the client side of input. As a result, some data types, such as dates and lists, may be more difficult to use with Django forms, because users must enter the data in the specific format required.