There is only one truth. It is the source.

Converting a submitted form values into a querystring

April 24, 2015

Tags: django

If you have a page that uses a form that submits GET requests (possibly a search form or similar), you sometimes want to be able to create links on that page that contain the same querystring as submitted. In other words you need a way to convert a bound or unbound form to a querystring. Django does not have a built-in API for doing this. In most cases you can get away with simply unpacking values from the form's cleaned_data or initial dictionary and converting that into a querystring. This has an interesting failure when done with a MultipleChoiceField:

In [1]: import urllib

In [2]: from django.http import QueryDict

In [3]: d = QueryDict(mutable=True)

In [4]: d['foo'] = []  # The initial value of a MultipleChoiceField is []

In [5]: d.urlencode()
Out[5]: u'foo=%5B%5D'

In [6]: urllib.unquote(u'foo=%5B%5D')
Out[6]: u'foo=[]'

Clearly this is not what we want. I solved this by creating a new method on form fields called convert_to_querystring_dict() and a helper function get_query_dict_from_form():

class QuerystringableMultipleChoiceField(forms.MultipleChoiceField):
    def convert_to_querystring_dict(self, field_name, value):
        if value == []:
            if not self.required:
                return {}
            else:
                return {field_name: ''}
        return {field_name: value}


class QuerystringableBooleanField(forms.BooleanField):
    def convert_to_querystring_dict(self, field_name, value):
        if not self.to_python(value):
            if not self.required:
                return {}
            else:
                return {field_name: ''}
        else:
            return {field_name: 'on'}


class QuerystringableChoiceField(forms.ChoiceField):
    def convert_to_querystring_dict(self, field_name, value):
        if self.to_python(value) == '':
            if not self.required:
                return {}
            else:
                return {field_name: ''}
        return {field_name: self.to_python(value)}


def get_query_dict_from_form(form, require_querystringable_fields=False):
    query_dict = QueryDict('', mutable=True)
    for field in form.fields.keys():
        the_field = form.fields[field]
        if hasattr(the_field, 'convert_to_querystring_dict'):
            if form.is_bound:
                querystring_dict = the_field.convert_to_querystring_dict(
                    field, form.cleaned_data.get(field))
            else:
                querystring_dict = the_field.convert_to_querystring_dict(
                    field, form.initial.get(field))
            for k, v in querystring_dict.items():
                if isinstance(v, (list, tuple)):
                    for x in v:
                        query_dict.appendlist(k, x)
                else:
                    query_dict[k] = v
        elif require_querystringable_fields and not hasattr(
                the_field, 'convert_to_querystring_dict'):
            raise ValueError(
                '{} does not have a convert_to_querystring_dict method'.format(
                    the_field))
        else:
            if form.is_bound:
                query_dict[field] = form.cleaned_data.get(field)
            else:
                query_dict[field] = form.initial.get(field)
    return query_dict