Posts Tagged ‘django’

Google App Engine and dynamic Django Forms

Wednesday, January 28th, 2009

Let 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.