diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py
index 6567c4f08f..4867e72deb 100644
--- a/django/forms/boundfield.py
+++ b/django/forms/boundfield.py
@@ -85,6 +85,8 @@ class BoundField(object):
widget.is_localized = True
attrs = attrs or {}
+ if not widget.is_hidden and self.field.required and self.form.use_required_attribute:
+ attrs['required'] = True
if self.field.disabled:
attrs['disabled'] = True
auto_id = self.auto_id
diff --git a/django/forms/forms.py b/django/forms/forms.py
index ac5cf12425..e2ed1a4ac1 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -67,10 +67,11 @@ class BaseForm(object):
# class, not to the Form class.
field_order = None
prefix = None
+ use_required_attribute = True
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
- empty_permitted=False, field_order=None):
+ empty_permitted=False, field_order=None, use_required_attribute=None):
self.is_bound = data is not None or files is not None
self.data = data or {}
self.files = files or {}
@@ -93,6 +94,9 @@ class BaseForm(object):
self._bound_fields_cache = {}
self.order_fields(self.field_order if field_order is None else field_order)
+ if use_required_attribute is not None:
+ self.use_required_attribute = use_required_attribute
+
def order_fields(self, field_order):
"""
Rearranges the fields according to field_order.
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 6400e4a67f..166a6adaeb 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -161,6 +161,10 @@ class BaseFormSet(object):
'auto_id': self.auto_id,
'prefix': self.add_prefix(i),
'error_class': self.error_class,
+ # Don't render the HTML 'required' attribute as it may cause
+ # incorrect validation for extra, optional, and deleted
+ # forms in the formset.
+ 'use_required_attribute': False,
}
if self.is_bound:
defaults['data'] = self.data
@@ -195,6 +199,7 @@ class BaseFormSet(object):
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
+ use_required_attribute=False,
**self.get_form_kwargs(None)
)
self.add_fields(form, None)
diff --git a/django/forms/models.py b/django/forms/models.py
index aac75b54ff..4ed8b746d9 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -278,7 +278,7 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
- empty_permitted=False, instance=None):
+ empty_permitted=False, instance=None, use_required_attribute=None):
opts = self._meta
if opts.model is None:
raise ValueError('ModelForm has no model class specified.')
@@ -296,8 +296,10 @@ class BaseModelForm(BaseForm):
# It is False by default so overriding self.clean() and failing to call
# super will stop validate_unique from being called.
self._validate_unique = False
- super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
- error_class, label_suffix, empty_permitted)
+ super(BaseModelForm, self).__init__(
+ data, files, auto_id, prefix, object_data, error_class,
+ label_suffix, empty_permitted, use_required_attribute=use_required_attribute,
+ )
# Apply ``limit_choices_to`` to each field.
for field_name in self.fields:
formfield = self.fields[field_name]
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index f1c46da947..c468dc38a0 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -244,9 +244,9 @@ precedence::
... comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print(f)
-
Name:
-
Url:
-
Comment:
+
Name:
+
Url:
+
Comment:
Checking which form data has changed
====================================
@@ -305,10 +305,10 @@ You can alter the field of :class:`Form` instance to change the way it is
presented in the form::
>>> f.as_table().split('\n')[0]
- '
'
Beware not to alter the ``base_fields`` attribute because this modification
will influence all subsequent ``ContactForm`` instances within the same Python
@@ -317,7 +317,7 @@ process::
>>> f.base_fields['name'].label = "Username"
>>> another_f = CommentForm(auto_id=False)
>>> another_f.as_table().split('\n')[0]
- '
Username:
'
+ '
Username:
'
Accessing "clean" data
======================
@@ -421,9 +421,9 @@ simply ``print`` it::
>>> f = ContactForm()
>>> print(f)
-
-
-
+
+
+
If the form is bound to data, the HTML output will include that data
@@ -438,9 +438,9 @@ include ``checked="checked"`` if appropriate::
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> print(f)
-
-
-
+
+
+
This default output is a two-column HTML table, with a ``
`` for each field.
@@ -485,11 +485,11 @@ containing one field::
>>> f = ContactForm()
>>> f.as_p()
- '
``as_table()``
@@ -522,11 +522,11 @@ it calls its ``as_table()`` method behind the scenes::
>>> f = ContactForm()
>>> f.as_table()
- '
\n
\n
\n
'
+ '
\n
\n
\n
'
>>> print(f.as_table())
-
-
-
+
+
+
.. _ref-forms-api-styling-form-rows:
@@ -597,19 +597,19 @@ tags nor ``id`` attributes::
>>> f = ContactForm(auto_id=False)
>>> print(f.as_table())
-
Subject:
-
Message:
-
Sender:
+
Subject:
+
Message:
+
Sender:
Cc myself:
>>> print(f.as_ul())
-
Subject:
-
Message:
-
Sender:
+
Subject:
+
Message:
+
Sender:
Cc myself:
>>> print(f.as_p())
-
Subject:
-
Message:
-
Sender:
+
Subject:
+
Message:
+
Sender:
Cc myself:
If ``auto_id`` is set to ``True``, then the form output *will* include
@@ -618,19 +618,19 @@ field::
>>> f = ContactForm(auto_id=True)
>>> print(f.as_table())
-
-
-
+
+
+
>>> print(f.as_ul())
-
-
-
+
+
+
>>> print(f.as_p())
-
-
-
+
+
+
If ``auto_id`` is set to a string containing the format character ``'%s'``,
@@ -641,19 +641,19 @@ attributes based on the format string. For example, for a format string
>>> f = ContactForm(auto_id='id_for_%s')
>>> print(f.as_table())
-
-
-
+
+
+
>>> print(f.as_ul())
-
-
-
+
+
+
>>> print(f.as_p())
-
-
-
+
+
+
If ``auto_id`` is set to any other true value -- such as a string that doesn't
@@ -671,15 +671,15 @@ It's possible to customize that character, or omit it entirely, using the
>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
>>> print(f.as_ul())
-
-
-
+
+
+
>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->')
>>> print(f.as_ul())
-
-
-
+
+
+
Note that the label suffix is added only if the last character of the
@@ -692,6 +692,17 @@ This will take precedence over :attr:`Form.label_suffix
using the ``label_suffix`` parameter to
:meth:`~django.forms.BoundField.label_tag`.
+.. attribute:: Form.use_required_attribute
+
+.. versionadded:: 1.10
+
+When set to ``True`` (the default), required form fields will have the
+``required`` HTML attribute.
+
+:doc:`Formsets ` instantiate forms with
+``use_required_attribute=False`` to avoid incorrect browser validation when
+adding and deleting forms from a formset.
+
Notes on field ordering
-----------------------
@@ -741,21 +752,21 @@ method you're using::
... 'cc_myself': True}
>>> f = ContactForm(data, auto_id=False)
>>> print(f.as_table())
-
More granular output
@@ -803,25 +814,25 @@ using the field's name as the key::
>>> form = ContactForm()
>>> print(form['subject'])
-
+
To retrieve all ``BoundField`` objects, iterate the form::
>>> form = ContactForm()
>>> for boundfield in form: print(boundfield)
-
-
-
+
+
+
The field-specific output honors the form object's ``auto_id`` setting::
>>> f = ContactForm(auto_id=False)
>>> print(f['message'])
-
+
>>> f = ContactForm(auto_id='id_%s')
>>> print(f['message'])
-
+
Attributes of ``BoundField``
----------------------------
@@ -852,7 +863,7 @@ Attributes of ``BoundField``
>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
>>> f = ContactForm(data, auto_id=False)
>>> print(f['message'])
-
+
>>> f['message'].errors
['This field is required.']
>>> print(f['message'].errors)
@@ -904,7 +915,7 @@ Attributes of ``BoundField``
.. code-block:: html
-
+
.. attribute:: BoundField.is_hidden
@@ -1125,11 +1136,11 @@ fields are ordered first::
... priority = forms.CharField()
>>> f = ContactFormWithPriority(auto_id=False)
>>> print(f.as_ul())
-
Subject:
-
Message:
-
Sender:
+
Subject:
+
Message:
+
Sender:
Cc myself:
-
Priority:
+
Priority:
It's possible to subclass multiple forms, treating forms as mixins. In this
example, ``BeatleForm`` subclasses both ``PersonForm`` and ``InstrumentForm``
@@ -1146,10 +1157,10 @@ classes::
... haircut_type = CharField()
>>> b = BeatleForm(auto_id=False)
>>> print(b.as_ul())
-
First name:
-
Last name:
-
Instrument:
-
Haircut type:
+
First name:
+
Last name:
+
Instrument:
+
Haircut type:
It's possible to declaratively remove a ``Field`` inherited from a parent class
by setting the name of the field to ``None`` on the subclass. For example::
@@ -1179,11 +1190,11 @@ You can put several Django forms inside one ``"""
)
# Case 3: POST with valid data (the success message).)
@@ -2392,23 +2429,23 @@ Password:
{{ form.username.errors.as_ul }}
{{ form.password1.errors.as_ul }}
{{ form.password2.errors.as_ul }}
-
+
''')
self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """