Skip to content
Swizec Teller - a geek with a hatswizec.com

Django protip #2: Forms are awesome

  • Moss

    Image by warrenski via Flickr

Welcome to another installment of Swizec's Django protip. Previously we discussed a better way to structure your django apps, but nobody cared about that because everybody is rather silly. This time we'll be talking about how awesome forms are and why you should be using them for pretty much everything. At the end, I'll show you some neat tips and tricks I discovered during my Django Epiphany.

Why forms

When you look at code a lot of web developers produce (and yes, even I did it plenty of times back in the day) you will notice a lot of work goes into retrieving data from GET and POST parameters. Now despite most developers simply ignoring GET parameters as anything really special or dangerous and just handling them as if they were regular variables, because hey, what could go wrong about retrieving a page number right? At best you'll see code having a bunch of lines sort of like this:

$page = (isset($_GET['page'])) ? intval($_GET['page']) : 0;

Anyone notice a security flaw? Then think of this, what happens if the page is set to -1? Sure, if errors aren't being displayed right to the user nothing too important. But if they get shown an SQL error ... or worse ...

However people are usually at least a little bit more careful about POST data because they realise that hey, this is something a person filled in a web form and perhaps the data should go through a series of a little bit more stringent tests before it gets chucked into the database. Hell, maybe we could even tell the user where they screwed up and if the planets are in constelation, why not also make sure those required fields are actually filled out ... you know, so we don't get any weird inconsistencies in our database.

But still, it's a lot of work to do all of that by hand every god damn time. If only there were something easier, more transparent and plain old automagical ...

Cue Django Forms

In django, all of this comes automagically. There is this thing called a "form", which basically lets you define what parameters a request needs, be it GET or POST based, what they should validate against and most importantly, what's required and what is not.

If you're into that sort of stuff you even get building the form in a html, so the users can use it, completely for free and magically with all the required "Hey bozo, you filled so and so field wrong. Fix it!". For every field! Magic.

But since most of my time is spent on developing API's rather than user-facing websites let me show you how to use forms effectively in that sort of environment. For the html stuff just go check out the django form docs.

The basic use of forms goes a little bit like this (hopefully your forms are in a separate file from your views, this is just an example :P)

from django import forms
class ListForm(forms.Form):
feed = forms.IntegerField(required=False)
category = forms.IntegerField(required=False)
since = forms.DateTimeField(required=False)
count = forms.IntegerField(required=False)
page = forms.IntegerField(required=False)
include_content = forms.BooleanField(required=False)
def list(request, format='json'):
form = ListForm(request.user, request.GET)
if form.is_valid():
articles = Article.objects.filter(feed__in=form.cleaned_data['feeds']
).order_by('-time')[page*count:page*count+count]
return HttpResponse(json.dumps({'status': 'OK',
'count': len(articles),
'articles': articles}))
else:
return HttpResponseBadRequest(json.dumps({'status': 'ERROR'}))

Well something along those lines anyhow. What you can see is that I basically define a form and check that it's valid. Once I know it's valid I can go on using its cleaned_data without much regard for anything.

Some tips&tricks

And now let's get onto some tips&tricks :)

First thing you'll notice once you start using forms like you properly should is that all of your views follow this pattern: get form; validate form; do something; or do something else;

So I wrote up a descriptor for that, now I can be certain that when I'm in my view the form is valid and I can use the data.

def form_valid(form_type, data_type):
def inner(view_func):
def wrapper(request, *args, **kwargs):
form = form_type(request.__getattribute__(data_type))
if form.is_valid():
request.form = form
return view_func(request, *args, **kwargs)
else:
return HttpResponseBadRequest(json.dumps({'status': 'ERROR'}))
return wraps(view_func)(wrapper)
return inner
## the usage goes like so
@form_valid(ImageForm, 'GET')
def image(request):
blob = BlobInfo.gql("WHERE filename='%s' LIMIT 1" % request.form.cleaned_data['id'])[0]
return HttpResponse(BlobReader(blob.key()).read(),
content_type=blob.content_type)

Using the decorator thus makes for much much cleaner code.

Now let's look at some magic done with custom clean functions inside forms :P

## this enables us to handle <a class="zem_slink freebase/en/comma-separated_values" title="Comma-separated values" rel="wikipedia" href="http://en.wikipedia.org/wiki/Comma-separated_values">comma separated values</a> seamlessly
def clean_feed(self):
try:
feed = [int(id) for id in str(self.cleaned_data['feed']).split(',')]
except (AttributeError, ValueError, KeyError):
feed = None
return feed
## automagically parsing json parameters can be done too
def clean_feeds(self):
feeds = json.loads(self.cleaned_data['feeds'])
if type(feeds) != list:
raise forms.ValidationError("list of feeds expected")
return feeds
## or how about logging in the user while we're checking the user/pass is correct
## don't manually log in users unless you know at least somewhat what you're doing, usually django handles this
def clean(self):
cleaned_data = self.cleaned_data
if len(self.errors) != 0:
return cleaned_data
user = authenticate(username=cleaned_data['email'],
password=cleaned_data['password'])
if user is None:
del cleaned_data['email']
del cleaned_data['password']
raise forms.ValidationError('Bad login')
else:
cleaned_data['user'] = user
return cleaned_data
## now the strangest thing, when you have to handle grabbing data by different parameters
##(like being given a set of feed ids, or a feed category id, you can do this by returning
## a QuerySet in the form's cleaned_data
def clean(self):
cleaned_data = self.cleaned_data
if not (cleaned_data.get('feed', None) != None or cleaned_data.get('category', None) != None):
raise forms.ValidationError("feed or category required")
if cleaned_data['feed'] != None:
user_feeds = UserFeed.objects.filter(user=self.user,
id__in=cleaned_data['feed'])
else:
user_feeds = UserFeed.objects.filter(user=self.user,
categories__contains="'%d'" % cleaned_data['category'])
cleaned_data['feeds'] = map(lambda f: f.id,
Feed.objects.filter(id__in=map(lambda f: f.feed, user_feeds)))
reverse_feeds = {}
for feed in user_feeds:
reverse_feeds[feed.feed] = feed.id
cleaned_data['reverse_feeds'] = reverse_feeds
return cleaned_data

Conclusion

Anyhow, that's it as far as forms are concerned. Do sound off in the comments or on twitter if I fucked up somewhere. I know geeks like to argue. Come back next week when I'll be talking about different magical things you can do with decorators and why they are uber awesome to use in django (or well any other type of python development really)

Enhanced by Zemanta

Did you enjoy this article?

Published on August 3rd, 2010 in Application programming interface, django, Login, RSS, SQL, Uncategorized, Website

Learned something new?
Want to become a high value JavaScript expert?

Here's how it works 👇

Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

Start with an interactive cheatsheet 📖

Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career.

"Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"

~ Ashish Kumar

Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own?  Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️