Commit 28986da4 authored by Thomas Tanner's avatar Thomas Tanner Committed by Tim Graham
Browse files

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

parent 39573a11
Loading
Loading
Loading
Loading
+2 −7
Original line number Diff line number Diff line
from __future__ import unicode_literals

from collections import OrderedDict

from django import forms
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.hashers import (
@@ -303,6 +301,8 @@ class PasswordChangeForm(SetPasswordForm):
    old_password = forms.CharField(label=_("Old password"),
                                   widget=forms.PasswordInput)

    field_order = ['old_password', 'new_password1', 'new_password2']

    def clean_old_password(self):
        """
        Validates that the old_password field is correct.
@@ -315,11 +315,6 @@ class PasswordChangeForm(SetPasswordForm):
            )
        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):
    """
+26 −1
Original line number Diff line number Diff line
@@ -73,9 +73,11 @@ class BaseForm(object):
    # 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*
    # class, not to the Form class.
    field_order = None

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=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.data = data or {}
        self.files = files or {}
@@ -96,6 +98,29 @@ class BaseForm(object):
        # self.base_fields.
        self.fields = copy.deepcopy(self.base_fields)
        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):
        return self.as_table()
+25 −0
Original line number Diff line number Diff line
@@ -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
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
~~~~~~~~~~~~~~~~~~~~~~~~

+4 −0
Original line number Diff line number Diff line
@@ -119,6 +119,10 @@ Forms
  ``field_classes`` to customize the type of the fields. See
  :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
^^^^^^^^^^^^^

+43 −0
Original line number Diff line number Diff line
@@ -1046,6 +1046,49 @@ class FormsTestCase(TestCase):
<tr><th>Field13:</th><td><input type="text" name="field13" /></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):
        # 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