Fixed #5986 -- Added ability to customize order of Form fields
This commit is contained in:
parent
39573a11db
commit
28986da4ca
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue