Code

Opened 7 years ago

Closed 5 years ago

Last modified 5 years ago

#5986 closed (duplicate)

Custom field order in newforms

Reported by: emes Owned by: nobody
Component: Forms Version: master
Severity: Keywords: field order weight form newforms
Cc: marc.garcia@…, matti.haavikko@…, sime Triage Stage: Design decision needed
Has patch: yes Needs documentation: yes
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

Consider this example:

from django import newforms as forms

class UserForm(forms.Form):
    # Used for registering new accounts.
    username = forms.CharField(max_length=20)
    email = forms.EmailField()
    password = forms.CharField(max_length=20)
    password2 = forms.CharField(max_length=20)

    # Lots of validators which check if username is unique,
    # password1 == password2 and so on.

class UserProfileForm(UserForm):
    # Inherits all UserForm fields and validators and adds
    # optional profile entries.
    first_name = forms.CharField(max_length=30)
    last_name = forms.CharField(max_length=30)
    jabber_id = forms.EmailField(required=False)

The UserProfileForm can inherit all the goods of UserForm: fields and validators. But the field order may look a bit messy then:

>>> UserProfileForm.base_fields.keys()
['username',
 'email',
 'password',
 'password2',
 'first_name',
 'last_name',
 'jabber_id']

It would be nice to have email grouped with jabber_id, first_name and last_name with username, etc. It's of course possible to do it using custom template, but violates DRY principle and renders as_*() methods useless.

The attached patch allows to specify a custom Field order within a Form, even with inherited fields.

Every Field may have an additional weight parameter with default value of 0. All fields are sorted in ascending weight order.

The example forms customized with weight parameters:

from django import newforms as forms

class UserForm(forms.Form):
    username = forms.CharField(max_length=20, weight=-2)
    email = forms.EmailField()
    password = forms.CharField(max_length=20, weight=1)
    password2 = forms.CharField(max_length=20, weight=1)

class UserProfileForm(UserForm):
    first_name = forms.CharField(max_length=30, weight=-1)
    last_name = forms.CharField(max_length=30, weight=-1)
    jabber_id = forms.EmailField()

And the effect:

>>> UserProfileForm.base_fields.keys()
['username',
 'first_name',
 'last_name',
 'email',
 'jabber_id',
 'password',
 'password2']

Attachments (4)

django-fields-weight.patch (1.9 KB) - added by emes 7 years ago.
The patch adding weight parameter to newforms.Field
django-fields-order.patch (1.4 KB) - added by emes 7 years ago.
Second approach, with Form.Meta.fields_order
django-fields-order.2.patch (4.9 KB) - added by Patryk Zawadzki <patrys@…> 7 years ago.
Correct patch including regression tests
django-fields-order.3.patch (5.6 KB) - added by Patryk Zawadzki <patrys@…> 7 years ago.
Added support for form_for_model

Download all attachments as: .zip

Change History (26)

Changed 7 years ago by emes

The patch adding weight parameter to newforms.Field

comment:1 Changed 7 years ago by patrys@…

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

Might be more useful to reimplement it as a meta-property containing the desired list of field names (the way you define columns to display for admin interface). This way each form can have its own independent list there and you can actually try to extend two forms at once while maintaining the correct and predictable output.

comment:2 Changed 7 years ago by emes

So, here it goes, with Form.Meta.fields_order list.

Example:

from django import newforms as forms

class UserForm(forms.Form):
    # Used for registering new accounts.
    username = forms.CharField(max_length=20)
    email = forms.EmailField()
    password = forms.CharField(max_length=20)
    password2 = forms.CharField(max_length=20)

    # Lots of validators which check if username is unique,
    # password1 == password2 and so on.


class UserProfileForm(UserForm):
    # Inherits all UserForm fields and validators and adds
    # optional profile entries.
    first_name = forms.CharField(max_length=30)
    last_name = forms.CharField(max_length=30)
    jabber_id = forms.EmailField()

    class Meta:
        fields_order = ['username', 'first_name', 'last_name',
                'email', 'jabber_id', 'password']

Changed 7 years ago by emes

Second approach, with Form.Meta.fields_order

Changed 7 years ago by Patryk Zawadzki <patrys@…>

Correct patch including regression tests

comment:3 Changed 7 years ago by Patryk Zawadzki <patrys@…>

  • Summary changed from Custom field order in newforms to [PATCH] Custom field order in newforms

Changed 7 years ago by Patryk Zawadzki <patrys@…>

Added support for form_for_model

comment:4 Changed 7 years ago by Patryk Zawadzki <patrys@…>

  • Has patch set

comment:5 follow-ups: Changed 7 years ago by jkocherhans

I'm sorry to be blunt, but I couldn't be more against this change, or rather the weight=X syntax. I'm working on a new class called ModelForm right now (search django-dev for the relevant thread) that should allow something similar to the fields_order attribute above. It's just called fields and it will actually restrict the fields that occur in the form as well.

comment:6 in reply to: ↑ 5 Changed 7 years ago by Patryk Zawadzki <patrys@…>

Replying to jkocherhans:

I'm sorry to be blunt, but I couldn't be more against this change, or rather the weight=X syntax. I'm working on a new class called ModelForm right now (search django-dev for the relevant thread) that should allow something similar to the fields_order attribute above. It's just called fields and it will actually restrict the fields that occur in the form as well.

This has little or nothing to do with form_for_* syntax. This adds the ordering ability for all Form subclasses as it only operates inside the metaclass. If your ModelForm class or any other class extends Form then it gains this feature for free.

The only relevant bit would be removing the small block of code including the comment about form_for_* once these functions die.

I think the patch is still appropriate to commit and it adds a nice regression test so you can be sure things work like they did and will continue to do so.

comment:7 in reply to: ↑ 5 Changed 7 years ago by Michał Sałaban <michal@…>

Replying to jkocherhans:

I'm sorry to be blunt, but I couldn't be more against this change, or rather the weight=X syntax. I'm working on a new class called ModelForm right now (search django-dev for the relevant thread) that should allow something similar to the fields_order attribute above. It's just called fields and it will actually restrict the fields that occur in the form as well.

The weight syntax is obsolete. Please, see the latest patch and example.

I found the discussion about ModelForms but don't see if they deal with the order of fields. And how can they be used to create Forms which are not Model-based?

Anyway, I see no problem in coexistence of ModelForms and Meta.fields_order above.

comment:8 Changed 7 years ago by bear330

This might be useless, but my way to order the fields is:

def sortDictByList(dic, seq):
    """
    Sort dictionary by giving a sequence.

    @param dic The dictionary instance you want to sort.
    @param seq The sequence you want to specify how to sort.
    @return Sorted dictionary.
    """
    n = SortedDict()
    for k in seq:
        n[k] = dic[k]

    for k, v in dic.iteritems():
        if n.has_key(k):
            continue
        n[k] = v
    return n

class SortFormFieldsMixin(object):
    """
    Sort the form fields by 'fieldsOrder' attribute in mixined form class.
    The fieldsOrder attribute is a list to figure out the order of form fields.
    """
    def __new__(cls):
        if not hasattr(cls, 'fieldsOrder'):
            raise RuntimeError("Bitch! You use SortFormFieldsMixin but no "
                "fieldsOrder attribute in it, stupid man~~~!!")
                                        
        cls.base_fields = sortDictByList(cls.base_fields, cls.fieldsOrder)
        return super(SortFormFieldsMixin, cls).__new__(cls)
    
BaseAForm = forms.form_for_model(models.A, BaseForm)

class FinalForm(BaseAForm, SortFormFieldsMixin):
    fieldsOrder = ['name', 'desc', 'link']

Anyway, the class Meta with fields_order should be a better way because the coordination with django's convention.

I posted here just for a reference. :)

comment:9 Changed 6 years ago by PJCrosier

  • Needs documentation set
  • Summary changed from [PATCH] Custom field order in newforms to Custom field order in newforms

comment:10 Changed 6 years ago by MichaelBishop

  • Triage Stage changed from Unreviewed to Design decision needed

Decision needs to be made whether adding a custom field order is a "good thing". IMHO this doesn't appear to be a critical feature. Either way a design decision is needed.

comment:11 Changed 6 years ago by ubernostrum

#6369 and #6878 were closed as duplicates.

comment:12 Changed 6 years ago by dcramer

Can we call it ordering to stick w/ a variable name thats already used instead of creating more?

Michael, features don't need to be critical to be wanted :)

comment:14 Changed 6 years ago by garcia_marc

  • Cc marc.garcia@… added
  • milestone set to 1.0 beta

+1 on this.

Specially because it's also needed for ModelForm, where fields Meta attribute couldn't be used for sorting. Without a order parameter, it's not easy to position in the correct place an additional parameter of a ModelForm.

comment:15 Changed 6 years ago by mir

  • milestone changed from 1.0 beta to post-1.0

not a bug => not milestone 1.0 beta.

comment:16 Changed 6 years ago by haavikko

  • Cc matti.haavikko@… added

comment:17 Changed 6 years ago by GertSteyn

After reeding CookBookNewFormsFieldOrdering this came to mind...

class FooForm(forms.ModelForm):

class Meta:

fields = ('first_field', 'second_field', 'third_field',)

def init(self, *args, kwargs):

super(FooForm, self).init(*args, kwargs)
self.fields.keyOrder = self.Meta.fields

The patch has now been reduced to a one liner:
"self.fields.keyOrder = self.Meta.fields"

comment:18 Changed 6 years ago by sime

  • Cc sime added

comment:19 Changed 6 years ago by lorien

The patch has now been reduced to a one liner: "self.fields.keyOrder = self.Meta.fields"

I think that is not correct. In this case only those fields that described in Meta.fields will be displayed.
Custom fields that have been added in the init or in the class will be truncated.

comment:20 Changed 5 years ago by miracle2k

comment:21 Changed 5 years ago by Alex

  • Resolution set to duplicate
  • Status changed from new to closed

Closing as a dpue of #8164 since it has the better API.

comment:22 Changed 5 years ago by anonymous

  • milestone post-1.0 deleted

Milestone post-1.0 deleted

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.