Home » Django » Django forms, inheritance and order of form fields

Django forms, inheritance and order of form fields

Posted by: admin November 30, 2017 Leave a comment


I’m using Django forms in my website and would like to control the order of the fields.

Here’s how I define my forms:

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)

class create_form(edit_form):
    name = forms.CharField()

The name is immutable and should only be listed when the entity is created. I use inheritance to add consistency and DRY principles. What happens which is not erroneous, in fact totally expected, is that the name field is listed last in the view/html but I’d like the name field to be on top of summary and description. I do realize that I could easily fix it by copying summary and description into create_form and loose the inheritance but I’d like to know if this is possible.

Why? Imagine you’ve got 100 fields in edit_form and have to add 10 fields on the top in create_form – copying and maintaining the two forms wouldn’t look so sexy then. (This is not my case, I’m just making up an example)

So, how can I override this behavior?


Apparently there’s no proper way to do this without going through nasty hacks (fiddling with .field attribute). The .field attribute is a SortedDict (one of Django’s internal datastructures) which doesn’t provide any way to reorder key:value pairs. It does how-ever provide a way to insert items at a given index but that would move the items from the class members and into the constructor. This method would work, but make the code less readable. The only other way I see fit is to modify the framework itself which is less-than-optimal in most situations.

In short the code would become something like this:

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)

class create_form(edit_form):
    def __init__(self,*args,**kwargs):


That shut me up 🙂


I had this same problem and I found another technique for reordering fields in the Django CookBook:

class EditForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)

class CreateForm(EditForm):
    name = forms.CharField()

    def __init__(self, *args, **kwargs):
        super(CreateForm, self).__init__(*args, **kwargs)
        self.fields.keyOrder = ['name', 'summary', 'description']


From Django 1.9: https://docs.djangoproject.com/en/1.10/ref/forms/api/#notes-on-field-ordering

Original answer: Django 1.9 will support this by default on the form with field_order:

class MyForm(forms.Form):
    field_order = ['field_1', 'field_2']



I used the solution posted by Selene but found that it removed all fields which weren’t assigned to keyOrder. The form that I’m subclassing has a lot of fields so this didn’t work very well for me. I coded up this function to solve the problem using akaihola’s answer, but if you want it to work like Selene’s all you need to do is set throw_away to True.

def order_fields(form, field_list, throw_away=False):
    Accepts a form and a list of dictionary keys which map to the
    form's fields. After running the form's fields list will begin
    with the fields in field_list. If throw_away is set to true only
    the fields in the field_list will remain in the form.

    example use:
    field_list = ['first_name', 'last_name']
    order_fields(self, field_list)
    if throw_away:
        form.fields.keyOrder = field_list
        for field in field_list[::-1]:
            form.fields.insert(0, field, form.fields.pop(field))

This is how I’m using it in my own code:

class NestableCommentForm(ExtendedCommentSecurityForm):
    # TODO: Have min and max length be determined through settings.
    comment = forms.CharField(widget=forms.Textarea, max_length=100)
    parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)

    def __init__(self, *args, **kwargs):
        super(NestableCommentForm, self).__init__(*args, **kwargs)
        order_fields(self, ['comment', 'captcha'])


It appears that at some point the underlying structure of field order was changed from a django specific SordedDict to a python standard OrderedDict

Thus, in 1.7 I had to do the following:

from collections import OrderedDict

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        original_fields = self.fields
        new_order = OrderedDict()
        for key in ['first', 'second', ... 'last']:
            new_order[key] = original_fields[key]
        self.fields = new_order

I’m sure someone could golf that into two or three lines, but for S.O. purposes I think clearly showing how it works is better than cleaver.


You could also create a decorator to order fields (inspired by Joshua’s solution):

def order_fields(*field_list):
    def decorator(form):
        original_init = form.__init__
        def init(self, *args, **kwargs):
            original_init(self, *args, **kwargs)        
            for field in field_list[::-1]:
                self.fields.insert(0, field, self.fields.pop(field))
        form.__init__ = init
        return form            
    return decorator

This will ensure that all the fields passed to the decorator come first.
You can use it like this:

class CreateForm(EditForm):
    name = forms.CharField()


The accepted answer’s approach makes use of an internal Django forms API that was changed in Django 1.7. The project team’s opinion is that it should never have been used in the first place. I now use this function to reorder my forms. This code makes use of an OrderedDict:

def reorder_fields(fields, order):
    """Reorder form fields by order, removing items not in order.

    >>> reorder_fields(
    ...     OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
    ...     ['b', 'c', 'a'])
    OrderedDict([('b', 2), ('c', 3), ('a', 1)])
    for key, v in fields.items():
        if key not in order:
            del fields[key]

    return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0])))

Which I use in classes like this:

class ChangeOpenQuestion(ChangeMultipleChoice):

    def __init__(self, *args, **kwargs):
        super(ChangeOpenQuestion, self).__init__(*args, **kwargs)
        key_order = ['title',

        self.fields = reorder_fields(self.fields, key_order)


See the notes in this SO question on the way Django’s internals keep track of field order; the answers include suggestions on how to “reorder” fields to your liking (in the end it boils down to messing with the .fields attribute).


Alternate methods for changing the field order:


self.fields.insert(0, 'name', self.fields.pop('name'))


self.fields['summary'] = self.fields.pop('summary')
self.fields['description'] = self.fields.pop('description')


for key in ('name', 'summary', 'description'):
    self.fields[key] = self.fields.pop(key)


self.fields = SortedDict( [ (key, self.fields[key])
                            for key in ('name', 'summary' ,'description') ] )

But Selene’s approach from the Django CookBook still feels clearest of all.


Based on an answer by @akaihola and updated to work with latest Django 1.5 as self.fields.insert is being depreciated.

from easycontactus.forms import *
from django import forms
class  CustomEasyContactUsForm(EasyContactUsForm):
    ### form settings and configuration
    CAPTHCA_PHRASE = 'igolf'

    ### native methods
    def __init__(self, *args, **kwargs):
        super(CustomEasyContactUsForm, self).__init__(*args, **kwargs)
        # re-order placement of added attachment field 

    ### field defintitions
    attachment = forms.FileField()

In the above we are extending an EasyContactUsForm base class as it is defined in django-easycontactus package.


I built a form ‘ExRegistrationForm’ inherited from the ‘RegistrationForm’ from Django-Registration-Redux. I faced two issues, one of which was reordering the fields on the html output page once the new form had been created.

I solved them as follows:

1. ISSUE 1: Remove Username from the Registration Form: In my_app.forms.py

    class ExRegistrationForm(RegistrationForm):
          #Below 2 lines extend the RegistrationForm with 2 new fields firstname & lastname
          first_name = forms.CharField(label=(u'First Name'))
          last_name = forms.CharField(label=(u'Last Name'))

          #Below 3 lines removes the username from the fields shown in the output of the this form
          def __init__(self, *args, **kwargs):
          super(ExRegistrationForm, self).__init__(*args, **kwargs)

2. ISSUE 2: Make FirstName and LastName appear on top: In templates/registration/registration_form.html

You can individually display the fields in the order that you want. This would help in case the number of fields are less, but not if you have a large number of fields where it becomes practically impossible to actually write them in the form.

     {% extends "base.html" %}
     {% load i18n %}

     {% block content %}
     <form method="post" action=".">
          {% csrf_token %}

          #The Default html is: {{ form.as_p }} , which can be broken down into individual elements as below for re-ordering.
          <p>First Name: {{ form.first_name }}</p>
          <p>Last Name:  {{ form.last_name }}</p>
          <p>Email: {{ form.email }}</p>
          <p>Password: {{ form.password1 }}</p>
          <p>Confirm Password: {{ form.password2 }}</p>

          <input type="submit" value="{% trans 'Submit' %}" />
      {% endblock %}