Using Django forms with HTTP GETs
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.
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
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
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
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
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
validation insures that only values from your
SORTS list of choices