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] - 'Name:' + 'Name:' >>> f.fields['name'].label = "Username" >>> f.as_table().split('\n')[0] - 'Username:' + 'Username:' 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() - '

\n

\n

\n

' + '

\n

\n

\n

' >>> print(f.as_p()) -

-

-

+

+

+

``as_ul()`` @@ -504,11 +504,11 @@ flexibility:: >>> f = ContactForm() >>> f.as_ul() - '
  • \n
  • \n
  • \n
  • ' + '
  • \n
  • \n
  • \n
  • ' >>> print(f.as_ul()) -
  • -
  • -
  • +
  • +
  • +
  • ``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()) - 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:

    +

    Subject:

    +

    Message:

    -

    Sender:

    +

    Sender:

    Cc myself:

    .. _ref-forms-error-list-format: @@ -778,10 +789,10 @@ Python 2):: >>> f = ContactForm(data, auto_id=False, error_class=DivErrorList) >>> f.as_p()
    This field is required.
    -

    Subject:

    -

    Message:

    +

    Subject:

    +

    Message:

    Enter a valid email address.
    -

    Sender:

    +

    Sender:

    Cc myself:

    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 ``
    `` tag. To give each >>> mother = PersonForm(prefix="mother") >>> father = PersonForm(prefix="father") >>> print(mother.as_ul()) -
  • -
  • +
  • +
  • >>> print(father.as_ul()) -
  • -
  • +
  • +
  • The prefix can also be specified on the form class:: diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 1748262de4..f21bae462a 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -115,9 +115,9 @@ We've specified ``auto_id=False`` to simplify the output:: ... comment = forms.CharField() >>> f = CommentForm(auto_id=False) >>> print(f) - Your name: - Your website: - Comment: + Your name: + Your website: + Comment: ``label_suffix`` ---------------- @@ -133,9 +133,9 @@ The ``label_suffix`` argument lets you override the form's ... captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =') >>> f = ContactForm(label_suffix='?') >>> print(f.as_p()) -

    -

    -

    +

    +

    +

    ``initial`` ----------- @@ -157,9 +157,9 @@ field is initialized to a particular value. For example:: ... comment = forms.CharField() >>> f = CommentForm(auto_id=False) >>> print(f) - Name: - Url: - Comment: + Name: + Url: + Comment: You may be thinking, why not just pass a dictionary of the initial values as data when displaying the form? Well, if you do that, you'll trigger validation, @@ -172,9 +172,9 @@ and the HTML output will include any validation errors:: >>> default_data = {'name': 'Your name', 'url': 'http://'} >>> f = CommentForm(default_data, auto_id=False) >>> print(f) - Name: - Url: - Comment: + Name: + Url: + Comment: This is why ``initial`` values are only displayed for unbound forms. For bound forms, the HTML output will use the bound data. @@ -201,7 +201,7 @@ Instead of a constant, you can also pass any callable:: >>> class DateForm(forms.Form): ... day = forms.DateField(initial=datetime.date.today) >>> print(DateForm()) - Day: + Day: The callable will be evaluated only when the unbound form is displayed, not when it is defined. @@ -237,19 +237,19 @@ fields. We've specified ``auto_id=False`` to simplify the output:: ... cc_myself = forms.BooleanField(required=False) >>> f = HelpTextContactForm(auto_id=False) >>> print(f.as_table()) - Subject:
    100 characters max. - Message: - Sender:
    A valid email address, please. + Subject:
    100 characters max. + Message: + Sender:
    A valid email address, please. Cc myself: >>> print(f.as_ul())) -
  • Subject: 100 characters max.
  • -
  • Message:
  • -
  • Sender: A valid email address, please.
  • +
  • Subject: 100 characters max.
  • +
  • Message:
  • +
  • Sender: A valid email address, please.
  • Cc myself:
  • >>> print(f.as_p()) -

    Subject: 100 characters max.

    -

    Message:

    -

    Sender: A valid email address, please.

    +

    Subject: 100 characters max.

    +

    Message:

    +

    Sender: A valid email address, please.

    Cc myself:

    ``error_messages`` diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index e347876724..f2b132ce3d 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -135,9 +135,9 @@ provided for each widget will be rendered exactly the same:: >>> f = CommentForm(auto_id=False) >>> f.as_table() - Name: - Url: - Comment: + Name: + Url: + Comment: On a real Web page, you probably don't want every widget to look the same. You might want a larger input element for the comment, and you might want the @@ -154,9 +154,9 @@ Django will then include the extra attributes in the rendered output: >>> f = CommentForm(auto_id=False) >>> f.as_table() - Name: - Url: - Comment: + Name: + Url: + Comment: You can also set the HTML ``id`` using :attr:`~Widget.attrs`. See :attr:`BoundField.id_for_label` for an example. @@ -204,7 +204,7 @@ foundation for custom widgets. >>> from django import forms >>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name',}) >>> name.render('name', 'A name') - '' + '' If you assign a value of ``True`` or ``False`` to an attribute, it will be rendered as an HTML5 boolean attribute:: @@ -627,16 +627,16 @@ Selector and checkbox widgets .. code-block:: html
    - +
    - +
    - +
    - +
    That included the ``