Two-Faced Django Part 3: Newforms

December 4, 2007. Filed under djangonewforms

Alright, and now we begin part three of this tutorial. You should have [wandered in from part two][parttwo]. If you can't be bothered with working through the previous segments, you can [grab a copy of our current setup][polling2] we put together in the first and second parts of the tutorial.

This segment we are adding a little bit more to our core application, but it'll be a relatively brief segment compared to the first two (and, for that matter, much briefer than the next four as well. Okay lets get down to business.

Using newforms to ask our users questions

In our Polling project, we are going to allow our users to create new polls, and we need a simple way to ask our users about the poll they want to create. An easy way of doing this is with the Django.newforms module.

First, navigate into the Polling project directory, and then we are going to make a new file, core/, for our form to live in.

emacs core/

Now lets add our imports to the file:

from django import newforms as forms
from polling.core.models import Poll

And now lets put together a simple form, which will only have field, the question to be asked. Its going to look like this:

class PollForm(forms.Form):
    question = forms.CharField(max_length=200)

Okay, so thats admittedly a very simple example (Sorry!), but we want a little bit more from it: we want to verify that a question is unique, or to add an error if it is not. We do that by adding a method clean_question to the PollForm class. Thanks to a little metaprogramming magic, Django knows to look for that method name and to call it for extra validation of the question field.

Our code to do that looks like this:

def clean_question(self):
    question = self.cleaned_data['question']
    count = Poll.objects.filter(question=question).count()
    if count != 0:
        raise forms.ValidationError('The question "%s" is already in use.' % question)
    return question

And, thats really all there is to writing the form. But, we do need to write a few tests before we move on. Lets head over to our core/ file and add this line to our imports:

from polling.core.forms import PollForm

And then lets make a new unittest.TestCase:

class CoreFormTest(unittest.TestCase):
    "Test the forms contained in the 'core' app."
    def test_PollForm(self):

And make sure that worked...

python test

And it did, so lets fill out the *test_PollForm* unittest.

def test_PollForm(self):
    f = PollForm()
    self.assertEquals(False, f.is_valid())
    data = {}
    f = PollForm(data)
    self.assertEquals(False, f.is_valid())
    self.assertEquals([u'This field is required.'], f.errors['question'])
    data = {"question":"Is this valid?"}
    f = PollForm(data)
    self.assertEquals(True, f.is_valid())
    self.assertEquals("Is this valid?", f.cleaned_data['question'])
    u = User(name="Will L")
    p = Poll(question="Is it real?", creator=u)
    data = {"question":"Is it real?"}
    f = PollForm(data)
    # should be false because Poll with this question already
    # exists in our database
    self.assertEquals(False, f.is_valid())
    self.assertEquals([u'The question "Is it real?" is already in use.'], f.errors['question'])

Like our earlier tests, we start out with a few sanity checks to make sure that we know what we are doing, and that Django hasn't flipped out on us completely. After feeling a little better about that, we get into testing that our form is valid and invalid when we want it to be, and also that it is reporting the error messages we expect it to.

So, lets run those tests...

python test

And it looks like we're still golden. Excellent.

Next steps

Now that we've put together this form, we are ready to begin building the actual content in the web and fb apps. The [zipped code for the current state of the project is available here][polling3], and move along to the fourth segment when you're ready.