Python

Django - Moving fields on modelforms

You can use the inner Meta class's 'fields' to supply the order of fields on a modelform, and this works fine, except that you need to list ALL the fields you want to see on the form, and that means you will need to remember to update this list every time you add a new field to the model. I prefer to exclude fields that I don't want on the form, but that leaves the problem of ordering the fields.

A second option is just to re-order the fields on the underlying model. This won't matter to any other part of Django, and is the way I would prefer for fields derived from the model. Keeping this relationship makes it easier to find fields on the model, if hey are in the same order as they on the screen.

A third option is to create a template that renders all the fields individually. This gives you excellent control, but is a pain if all you want is a simple form created with .as_p() or .as_table().

However, in one project I wanted to add an additional control on the page that was used to guide the process of validating the data and updating the database, but wasn't actually written to the database. Its easy to add fields to the form,just by including them on the Modelform definition:

class MeercatTrainerForm(forms.ModelForm):
    new_field = forms.IntegerField()

    class Meta:
        model = MeercatTrainer

This new field will appear at the bottom of a form rendered using the as_p() or as_table() methods. To move them somewhere else requires diving into the internals of the form. The order of the fields on a modelform is described by an attribute of the form called 'fields', which is a 'django.utils.datastructures.SortedDict' dictionary-like object. It has a method 'insert' that allows you to insert new fields, and another called 'pop' that allows you to "pop" them off the list. So I created this simple utility function:

def move_field_before(frm, field_name, before_name):
    fld = frm.fields.pop(field_name)
    pos = frm.fields.keys().index(before_name)
    frm.fields.insert(pos, field_name, fld)

To use this to put (for example) the new field before the MeercatTrainer's name, you simply override the forms __init__ method:

class MeercatTrainerForm(forms.ModelForm):
    new_field = forms.IntegerField()

    def __init__(self, *args, **kwargs):
        super(MeercatTrainerForm, self).__init__(*args, **kwargs)
        move_field_before(self, 'new_field', 'name')

    class Meta:
        model = MeercatTrainer

Simples!

Version 1 published 5 Dec 2014, 12:43 p.m.