[Django]-How do I specify order of fields in Django form?

4👍

I asked in the Django developers mailing list and I have been told not to manipulate the order of fields myself, but instead to use the supported API methods documented here. So I changed the code and used self.order_fields instead:

class LocalizedFirstLastNameMixin(object):
    def __init__(self, *args, **kwargs):
        self.language_code = kwargs.pop('language_code', 'en')
        super().__init__(*args, **kwargs)
        localized_fields = self.get_localized_fields()
        for loc_field in localized_fields:
            self.fields[loc_field] = User._meta.get_field(loc_field).formfield()
            self.fields[loc_field].required = True
            self.initial[loc_field] = getattr(self.instance, loc_field, '')
        self.order_fields(field_order=localized_fields)

Notice that I only order the first two fields. All the other fields are kept in the default order. I also don’t have to add the fields now in a reversed order.

👤Uri

2👍

Is there a change in the implementation of the fields in forms

What changed between Django 2.2 and 3 is how declared fields are initialized:

I guess this is because Django 3 supports Python from version 3.6 or later (https://docs.djangoproject.com/en/3.0/faq/install/) and since Python 3.6 dicts are insertion ordered (Are dictionaries ordered in Python 3.6+?).


I would convert self.fields to an OrderedDict (basically going back to what it used to be in version 2.2) to enable self.fields.move_to_end again.

from collections import OrderedDict


class LocalizedFirstLastNameMixin(object):
    def __init__(self, *args, **kwargs):
        self.language_code = kwargs.pop('language_code', 'en')
        super().__init__(*args, **kwargs)
        self.fields = OrderedDict(self.fields)
        for loc_field in reversed(self.get_localized_fields()):
            self.fields[loc_field] = User._meta.get_field(loc_field).formfield()
            self.fields[loc_field].required = True
            self.fields.move_to_end(loc_field, last=False)
            self.initial[loc_field] = getattr(self.instance, loc_field, '')

0👍

OrderedDict is from the standard library.

from collections import OrderedDict

and instances have .move_to_end(), It just says you dict object has no .move_to_end() which implies you’re using a normal dict. Cast it to an OrderedDict like this

x = { "key" : "value"}
y = OrderedDict(x)
# OrderedDict([('key', 'value')])

now .move_to_end() will work

0👍

I’m late to this party, but this may be of use to somebody else. With recent Python (3.6+), an ordinary dict keeps keys in the order they are added. With earlier versions, you need to use collections.OrderedDict. Django used to use it’s own dict-like class, but now uses a vanilla dict. print( type(form_instance.fields)) if in doubt.

Anyway, you can re-order the fields any way you like by re-defining self.fields in a form’s __init__ method. As in:

class OrderUncWaferBatchForm( ModelForm): 

    #added non-model fields ...
    ponum = forms.CharField( max_length=10,
        label=mark_safe("Purchase Order<br>Number")
    )
    supplier = forms.CharField( max_length = 16)

    def __init__(self, *args, **kwargs):
        super().__init__( *args, **kwargs)

        self.fields = reorder_dict( self.fields,
            # these fields in this order at the top of the form 
            ['ponum','supplier', 's_cost', 'wafer_stock_orig'])

    class Meta:
        model=WaferBatch
        fields = ...

And since I’ve written a pretty general reorder_dict function, here it is

def reorder_dict(old_dict, reordering, dict_class=None):
    """ Returns a new dict with keys added in the order specified by
        reordering. This is a sequence of tuples (fromkey, tokey) which
        operates on the list of keys of old_dict. Each tuple
        (from_key, to_key) removes the named key from its current
        posotion and inserts it before to_key. Each tuple operates
        on the list permuted by all previous ones.

        Any key that is not in old_dict will cause an IndexError

        to_key can also be an integer for list.insert position. Using
        a bignum will insert at the end. Using 0, at the top. 

        As a special case reordering can also be a list of key strings (not tuples). 
        This means to put the keys in the list at the top (positions 0,1,...) and
        leave any keys not mentioned in theit original order after the
        ones in the list.

        Efficiency is not a consideration. It's intended for use
        on form.fields, which rarely contains more than a handful of fields
    """
    if dict_class is None:
        dict_class = dict  # on old Python wouold have to be an OrderedDict

    if isinstance( reordering[0], str):
        reordering = [ (k,n) for n,k in enumerate( reordering) ]

    keys = list( old_dict.keys() )

    for from_key, to_key in reordering:

        fom = keys.index( from_key)  # from is reserved word
        if isinstance( to_key, int):
            to = to_key
        else:
            to = keys.index( to_key)

        keys.insert( to, from_key)
        if to <= fom:
            fom += 1  # it's been pushed down
        del keys[fom]

    result = dict_class()
    for k in keys:
        result[k] = old_dict[k]

    return result

Leave a comment