Google App Engine and dynamic Django Forms
January 28th, 2009Let me define dynamic form: a form that can have 0 to x number of entries for a field. For example a form that contains a list of items, like a grocery list. Its part of the same form but can have more than one entry. In PHP, they can be grouped by appending “[]” to their name attribute, and PHP stores them the $_POST variable as array (i.e. $_POST['list'][0]). I am working a project using Google App Engine, which uses Django forms, and I needed a dynamic form (list). I found this can be done using the get_all() function of the self.request object (i.e. self.request.get_all(‘list’)) will return a list object.
Cool! But how do I use that with the Django form model with this list? Make a list of form models. Loop through the posted list, create a form object with that data, append it to the form list, check if its valid and repeat. If there was an error, just send the form list to the template, and loop through the forms displaying them with error messages. Else, the entire form is valid, save it to the data object. Here is some code:
#main.py
import wsgiref.handlers
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
class Groceries(db.Model):
quantity = db.IntegerProperty()
name = db.StringProperty(required=True)
class GroceryForm(djangoforms.ModelForm):
class Meta:
model = Groceries
class MainHandler(webapp.RequestHandler):
def get(self):
groceryform = GroceryForm()
values = {'groceriesformlist': [groceryform]}
self.response.out.write(template.render('main.html',values))
def post(self):
postedQuantities = self.request.get_all('quantity')
postedNames = self.request.get_all('name')
groceriesList = []
allGroceriesValid = True;
n = 0;
for i in self.request.get_all('quantity'):
groc = GroceryForm(data={'quantity':postedQuantities[n],'name':postedNames[n]})
allIngredientsValid = allIngredientsValid and groc.is_valid()
groceriesList.append(groc)
n += 1
if allGroceriesValid:
for grocerydata in groceriesList:
grocery = grocerydata.save(commit=False)
#do something with data
grocery.put()
self.redirect('/')
else:
values = {'groceriesformlist': groceriesList}
self.response.out.write(template.render('main.html',values))
def main():
application = webapp.WSGIApplication([('/', MainHandler)],debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()
The commit=False is useful when you do not want to save the data right away, like some other variable manipulation. Then there is the template file. There are numerous ways to output the form, it even has a built in table or paragraph method. If you wanted to just use the default table method to output the form, with errors, simple use <table>{{groceryform}}</table> (the output omits the table declartions, just the rows and columns) and your are done. I like a little more control, so to output the erros: {{ groceryform.quanity.errors }} and then output the input tag: {{groceryform.quantity}. Here is the template file, main.html:
<h3 class="form_headers">Groceries</h3>
<ol id="directions_form">
{% for groceryform in groceriesformlist %}
<li>{{ groceryform.quanity.errors }}{{groceryform.quantity}}{{ groceryform.name.errors }}{{groceryform.name}}</li>
{% endfor %}
</ol>
<input type="button" value="+" id="add_btn" onclick="AddLi(this.previousSibling.previousSibling)" />
You also need to toss in some javascript for adding new grocery items:
function AddDirLi(ol)
{
var li = ol.firstChild.nextSibling.cloneNode(true);
ol.appendChild(li);
}
There are many ways to accomplish this, with AJAX or just posting items one at a time. But my project actually had a few sections of this nature and it this method seemed to work the best. I hope that made sense. The next post will cover Using Relationships with the Google App Engine Datastore. Creating and storing multiple grocery lists. The grocery list would be the the parent to many grocery items.
Tags: django, googl app engine, python, Tutorials

January 29th, 2009 at 5:02 am
Don’t you mean this?
for n in self.request.get_all('quantity'):
January 30th, 2009 at 10:18 pm
No, in Python the for loop is more like a foeach loop. So in the case above,
for i in self.request.get_all('quantity'):
the i will be the values of the quantity input, not 0,1,2… # of quantities. That is why I added the n so I could reference the elements of the postedNames and postedQuantities lists. Perhaps I should of made a while loop:
n = 0 while n < len(postedQuantities): groc = GroceryForm(data={'quantity':postedQuantities[n],'name':postedNames[n]}) allIngredientsValid = allIngredientsValid and groc.is_valid() groceriesList.append(groc) n += 1Since the form will have the same number of quantities and names that go with the quantities the loop will hit all the values. Hope that helps. Thanks for the feedback!
Python For Loop Reference:
http://ibiblio.org/g2swap/byteofpython/read/for-loop.html