Fixed #25078 -- Added support for disabled form fields

Thanks Keryn Knight and Tim Graham for the reviews.
This commit is contained in:
Claude Paroz 2015-07-07 21:06:57 +02:00
parent 1fed8dd715
commit 1ef4aeab40
6 changed files with 65 additions and 2 deletions

View File

@ -70,7 +70,7 @@ class Field(six.with_metaclass(RenameFieldMethods, object)):
def __init__(self, required=True, widget=None, label=None, initial=None,
help_text='', error_messages=None, show_hidden_initial=False,
validators=[], localize=False, label_suffix=None):
validators=[], localize=False, disabled=False, label_suffix=None):
# required -- Boolean that specifies whether the field is required.
# True by default.
# widget -- A Widget class, or instance of a Widget class, that should
@ -90,11 +90,14 @@ class Field(six.with_metaclass(RenameFieldMethods, object)):
# hidden widget with initial value after widget.
# validators -- List of additional validators to use
# localize -- Boolean that specifies if the field should be localized.
# disabled -- Boolean that specifies whether the field is disabled, that
# is its widget is shown in the form but not editable.
# label_suffix -- Suffix to be added to the label. Overrides
# form's label_suffix.
self.required, self.label, self.initial = required, label, initial
self.show_hidden_initial = show_hidden_initial
self.help_text = help_text
self.disabled = disabled
self.label_suffix = label_suffix
widget = widget or self.widget
if isinstance(widget, type):

View File

@ -386,6 +386,9 @@ class BaseForm(object):
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.initial.get(name, field.initial)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
@ -567,6 +570,8 @@ class BoundField(object):
widget.is_localized = True
attrs = attrs or {}
if self.field.disabled:
attrs['disabled'] = True
auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
if not only_initial:

View File

@ -299,6 +299,17 @@ as the rendered output.
See the :ref:`format localization <format-localization>` documentation for
more information.
``disabled``
~~~~~~~~~~~~
.. attribute:: Field.disabled
.. versionadded:: 1.9
The ``disabled`` boolean argument, when set to ``True``, disables a form field
using the ``disabled`` HTML attribute so that it won't be editable by users.
Even if a user tampers with the field's value submitted to the server, it will
be ignored in favor of the value from the form's initial data.
Checking if the field data has changed
--------------------------------------

View File

@ -313,6 +313,9 @@ Forms
and trailing whitespace. As this defaults to ``True`` this is different
behavior from previous releases.
* Form fields now support the :attr:`~django.forms.Field.disabled` argument,
allowing the field widget to be displayed disabled by browsers.
Generic Views
^^^^^^^^^^^^^

View File

@ -176,6 +176,10 @@ class FieldsTests(SimpleTestCase):
self.assertEqual(f.clean(' 1'), ' 1')
self.assertEqual(f.clean('1 '), '1 ')
def test_charfield_disabled(self):
f = CharField(disabled=True)
self.assertWidgetRendersTo(f, '<input type="text" name="f" id="id_f" disabled />')
# IntegerField ################################################################
def test_integerfield_1(self):
@ -1076,6 +1080,12 @@ class FieldsTests(SimpleTestCase):
form = ChoiceFieldForm()
self.assertEqual([('P', 'Paul')], list(form.fields['choicefield'].choices))
def test_choicefield_disabled(self):
f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')], disabled=True)
self.assertWidgetRendersTo(f,
'<select id="id_f" name="f" disabled><option value="J">John</option>'
'<option value="P">Paul</option></select>')
# TypedChoiceField ############################################################
# TypedChoiceField is just like ChoiceField, except that coerced types will
# be returned:

View File

@ -501,6 +501,37 @@ class FormsTestCase(SimpleTestCase):
<option value="P" selected="selected">Paul McCartney</option>
</select>""")
def test_form_with_disabled_fields(self):
class PersonForm(Form):
name = CharField()
birthday = DateField(disabled=True)
class PersonFormFieldInitial(Form):
name = CharField()
birthday = DateField(disabled=True, initial=datetime.date(1974, 8, 16))
# Disabled fields are generally not transmitted by user agents.
# The value from the form's initial data is used.
f1 = PersonForm({'name': 'John Doe'}, initial={'birthday': datetime.date(1974, 8, 16)})
f2 = PersonFormFieldInitial({'name': 'John Doe'})
for form in (f1, f2):
self.assertTrue(form.is_valid())
self.assertEqual(
form.cleaned_data,
{'birthday': datetime.date(1974, 8, 16), 'name': 'John Doe'}
)
# Values provided in the form's data are ignored.
data = {'name': 'John Doe', 'birthday': '1984-11-10'}
f1 = PersonForm(data, initial={'birthday': datetime.date(1974, 8, 16)})
f2 = PersonFormFieldInitial(data)
for form in (f1, f2):
self.assertTrue(form.is_valid())
self.assertEqual(
form.cleaned_data,
{'birthday': datetime.date(1974, 8, 16), 'name': 'John Doe'}
)
def test_hidden_data(self):
class SongForm(Form):
name = CharField()