Fixed #5986 -- Added ability to customize order of Form fields

This commit is contained in:
Thomas Tanner 2015-03-16 01:33:59 +01:00 committed by Tim Graham
parent 39573a11db
commit 28986da4ca
5 changed files with 100 additions and 8 deletions

View File

@ -1,7 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import OrderedDict
from django import forms from django import forms
from django.contrib.auth import authenticate, get_user_model from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.hashers import ( from django.contrib.auth.hashers import (
@ -303,6 +301,8 @@ class PasswordChangeForm(SetPasswordForm):
old_password = forms.CharField(label=_("Old password"), old_password = forms.CharField(label=_("Old password"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
field_order = ['old_password', 'new_password1', 'new_password2']
def clean_old_password(self): def clean_old_password(self):
""" """
Validates that the old_password field is correct. Validates that the old_password field is correct.
@ -315,11 +315,6 @@ class PasswordChangeForm(SetPasswordForm):
) )
return old_password return old_password
PasswordChangeForm.base_fields = OrderedDict(
(k, PasswordChangeForm.base_fields[k])
for k in ['old_password', 'new_password1', 'new_password2']
)
class AdminPasswordChangeForm(forms.Form): class AdminPasswordChangeForm(forms.Form):
""" """

View File

@ -73,9 +73,11 @@ class BaseForm(object):
# class is different than Form. See the comments by the Form class for more # class is different than Form. See the comments by the Form class for more
# information. Any improvements to the form API should be made to *this* # information. Any improvements to the form API should be made to *this*
# class, not to the Form class. # class, not to the Form class.
field_order = None
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None, initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False): empty_permitted=False, field_order=None):
self.is_bound = data is not None or files is not None self.is_bound = data is not None or files is not None
self.data = data or {} self.data = data or {}
self.files = files or {} self.files = files or {}
@ -96,6 +98,29 @@ class BaseForm(object):
# self.base_fields. # self.base_fields.
self.fields = copy.deepcopy(self.base_fields) self.fields = copy.deepcopy(self.base_fields)
self._bound_fields_cache = {} self._bound_fields_cache = {}
self.order_fields(self.field_order if field_order is None else field_order)
def order_fields(self, field_order):
"""
Rearranges the fields according to field_order.
field_order is a list of field names specifying the order. Fields not
included in the list are appended in the default order for backward
compatibility with subclasses not overriding field_order. If field_order
is None, all fields are kept in the order defined in the class.
Unknown fields in field_order are ignored to allow disabling fields in
form subclasses without redefining ordering.
"""
if field_order is None:
return
fields = OrderedDict()
for key in field_order:
try:
fields[key] = self.fields.pop(key)
except KeyError: # ignore unknown fields
pass
fields.update(self.fields) # add remaining fields in original order
self.fields = fields
def __str__(self): def __str__(self):
return self.as_table() return self.as_table()

View File

@ -700,6 +700,31 @@ example, in the ``ContactForm`` example, the fields are defined in the order
``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML ``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML
output, just change the order in which those fields are listed in the class. output, just change the order in which those fields are listed in the class.
There are several other ways to customize the order:
.. attribute:: Form.field_order
.. versionadded:: 1.9
By default ``Form.field_order=None``, which retains the order in which you
define the fields in your form class. If ``field_order`` is a list of field
names, the fields are ordered as specified by the list and remaining fields are
appended according to the default order. Unknown field names in the list are
ignored. This makes it possible to disable a field in a subclass by setting it
to ``None`` without having to redefine ordering.
You can also use the ``Form.field_order`` argument to a :class:`Form` to
override the field order. If a :class:`~django.forms.Form` defines
:attr:`~Form.field_order` *and* you include ``field_order`` when instantiating
the ``Form``, then the latter ``field_order`` will have precedence.
.. method:: Form.order_fields(field_order)
.. versionadded:: 1.9
You may rearrange the fields any time using ``order_fields()`` with a list of
field names as in :attr:`~django.forms.Form.field_order`.
How errors are displayed How errors are displayed
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -119,6 +119,10 @@ Forms
``field_classes`` to customize the type of the fields. See ``field_classes`` to customize the type of the fields. See
:ref:`modelforms-overriding-default-fields` for details. :ref:`modelforms-overriding-default-fields` for details.
* You can now specify the order in which form fields are rendered with the
:attr:`~django.forms.Form.field_order` attribute, the ``field_order``
constructor argument , or the :meth:`~django.forms.Form.order_fields` method.
Generic Views Generic Views
^^^^^^^^^^^^^ ^^^^^^^^^^^^^

View File

@ -1046,6 +1046,49 @@ class FormsTestCase(TestCase):
<tr><th>Field13:</th><td><input type="text" name="field13" /></td></tr> <tr><th>Field13:</th><td><input type="text" name="field13" /></td></tr>
<tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr>""") <tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr>""")
def test_explicit_field_order(self):
class TestFormParent(Form):
field1 = CharField()
field2 = CharField()
field4 = CharField()
field5 = CharField()
field6 = CharField()
field_order = ['field6', 'field5', 'field4', 'field2', 'field1']
class TestForm(TestFormParent):
field3 = CharField()
field_order = ['field2', 'field4', 'field3', 'field5', 'field6']
class TestFormRemove(TestForm):
field1 = None
class TestFormMissing(TestForm):
field_order = ['field2', 'field4', 'field3', 'field5', 'field6', 'field1']
field1 = None
class TestFormInit(TestFormParent):
field3 = CharField()
field_order = None
def __init__(self, **kwargs):
super(TestFormInit, self).__init__(**kwargs)
self.order_fields(field_order=TestForm.field_order)
p = TestFormParent()
self.assertEqual(list(p.fields.keys()), TestFormParent.field_order)
p = TestFormRemove()
self.assertEqual(list(p.fields.keys()), TestForm.field_order)
p = TestFormMissing()
self.assertEqual(list(p.fields.keys()), TestForm.field_order)
p = TestForm()
self.assertEqual(list(p.fields.keys()), TestFormMissing.field_order)
p = TestFormInit()
order = list(TestForm.field_order) + ['field1']
self.assertEqual(list(p.fields.keys()), order)
TestForm.field_order = ['unknown']
p = TestForm()
self.assertEqual(list(p.fields.keys()), ['field1', 'field2', 'field4', 'field5', 'field6', 'field3'])
def test_form_html_attributes(self): def test_form_html_attributes(self):
# Some Field classes have an effect on the HTML attributes of their associated # Some Field classes have an effect on the HTML attributes of their associated
# Widget. If you set max_length in a CharField and its associated widget is # Widget. If you set max_length in a CharField and its associated widget is