Using Django forms with HTTP GETs

February 10, 2011

All of the Django form examples and documentation that I've seen on the Django website (and in casual reading elsewhere) talks about using them for POST-based requests. This is the usual way to use forms in general, especially complicated data-driven forms, and anyways the whole REST style prefers putting things in the actual URL instead of in GET query parameters. But sometimes you really want to use GET-based forms; my case today was changing the sort order of a table of data.

(I know that good modern web developers do this in Javascript, perhaps on the fly, instead of with explicit forms (or at least without requiring the user to do explicit form submission). I'm a bit behind the modern web age.)

Django forms can be used with GET-based form submission pretty much just the same as with POST-based form submission. The form information is available in request.GET just as it is with request.POST, and you construct the bound form in the same way:

form = YourForm(request.GET)

The usual form CSRF protection is not required on GETs and not necessary if all of your GET-based URLs are idempotent and harmless (which they certainly should be) and don't cause any side effects.

The actual form class is defined in the same way as for POST-based forms, and used in templates in the same way too. For obvious reasons I don't think that it makes any sense to use a model-based form. I also don't think it makes sense to try to use a formset; with a formset of any decent size, you are going to get very big URLs and there are size limits.

The one slightly inobvious trick is deciding when to construct a bound form and when to construct an unbound form (ie, detecting when the user has submitted the form versus when they're looking at the initial page). POST based forms tell the two apart based on whether the request is a GET (initial page view) or a POST (form submission), but this doesn't work for GET-based forms for the obvious reason. The approach I am using is to check whether a form field appears as a query parameter in request.GET:

if request.method == 'GET' and \
   'param1' in request.GET:
      form = YourForm(request.GET)
      ...

(I check the request method too because I am notably paranoid.)

Any form field will do; any legitimate form submission will include all of them, even if some of them have blank contents. You need to check for form.is_valid() before looking at the form data, as usual.

Sidebar: the cheap trick to do changeable queryset ordering

Construct a tuple of choices values of the form:

SORTS = (('', '(none)'),
         ('field1', 'Friendly'),
         ('-field2', 'Labels'),
         ....)

Use this as the choices parameter in one or more forms.ChoiceField fields in your form (more than one field lets users have multiple levels of ordering, so you can do things like order first by state and then by name).

When you are using the form, do something like the following:

order = []
for field in 'first', 'second', 'third':
  if form.cleaned_data[field]:
    order.append(form.cleaned_data[field])

if order:
  qs = qs.order_by(*order)

(Then you continue on to display the objects from the queryset in your template.)

I believe that this is safe and will not let people edit the URL by hand to feed arbitrary fields and so on to .order_by(); form validation insures that only values from your SORTS list of choices are accepted.


Comments on this page:

From 72.93.186.207 at 2011-02-10 07:22:03:

You could generate sorting links server side like .../page/sort/Friendly

Written on 10 February 2011.
« On flow (a digression)
The letdown »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Feb 10 01:36:07 2011
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.