Code


Version 1 (modified by toddobryan, 6 years ago) (diff)

--

Field Ordering in newforms

As you know, displaying a form using as_table(), as_p(), or as_ul() displays the form's fields in whatever order they're listed in the the form class. This is usually fine, because the order in the class is completely flexible. There are some cases, however, when it can get annoying.

Imagine you run a bookstore and are creating forms for people to enter book information. (Yes, these would probably be ModelForms, but suspend your disbelief long enough for me to make my point.) Here's the basic form:

class BookForm(forms.Form):
    title = forms.CharField(max_length=80)
    author = forms.CharField(max_length=40)
    publisher = forms.CharField(max_length=40)
    copyright = forms.IntegerField()

While this form works for most books, there are some that it has trouble with. Translations, for example, have a translator in addition to the author. Through the magic of inheritance, you can just create a new class that adds the translator field and inherits the rest:

class TranslationForm(BookForm):
    translator = forms.CharField(max_length=40)

Unfortunately, when you try to display a TranslationForm, the translator field will end up at the bottom, below the publisher and copyright. Clearly it makes more sense for it to appear right after the author. You could throw up your hands, forget about inheritance and create a whole new TranslationForm that doesn't inherit from BookForm.

But if you did that, you'd be RYing, and we all know that Django strives to be a DRY framework. You could also specify the order of the fields in your template, but that seems like a lot more work than just putting {{ form }}.

There is an easier way. It turns out that all the fields in a form are stored in a SortedDict, a nice little data structure defined in django.utils.datastructures. The key difference between a regular dict and a SortedDict is that the latter maintains a list of its keys in the order they were added in an attribute called keyOrder. Replace keyOrder with the order you want, and you've fooled the SortedDict. Here, then, is a TranslationForm that's much less cumbersome than the alternatives:

class TranslationForm(BookForm):
    translator = forms.CharField(max_length=40)

    def __init__(self, *args, **kwargs):
        super(TranslationForm, self).__init__(*args, **kwargs)
        self.fields.keyOrder = ['title', 'author', 'translator', 'publisher', 'copyright']

Simple as that and you've got the order you wanted.