Fixed #18134 -- BoundField.label_tag now includes the form's label_suffix

There was an inconsistency between how the label_tag for forms were
generated depending on which method was used: as_p, as_ul and as_table
contained code to append the label_suffix where as label_tag called on a
form field directly did NOT append the label_suffix. The code for
appending the label_suffix has been moved in to the label_tag code of
the field and the HTML generation code for as_p, as_ul and as_table now
calls this code as well.

This is a backwards incompatible change because users who have added the
label_suffix manually in their templates may now get double label_suffix
characters in their forms.
This commit is contained in:
Gabe Jackson 2012-06-08 15:32:35 +02:00 committed by Tim Graham
parent a643e4d200
commit 584bd14dcf
8 changed files with 77 additions and 34 deletions

View File

@ -170,11 +170,6 @@ class BaseForm(object):
if bf.label: if bf.label:
label = conditional_escape(force_text(bf.label)) label = conditional_escape(force_text(bf.label))
# Only add the suffix if the label does not end in
# punctuation.
if self.label_suffix:
if label[-1] not in ':?.!':
label = format_html('{0}{1}', label, self.label_suffix)
label = bf.label_tag(label) or '' label = bf.label_tag(label) or ''
else: else:
label = '' label = ''
@ -522,6 +517,10 @@ class BoundField(object):
If attrs are given, they're used as HTML attributes on the <label> tag. If attrs are given, they're used as HTML attributes on the <label> tag.
""" """
contents = contents or self.label contents = contents or self.label
# Only add the suffix if the label does not end in punctuation.
if self.form.label_suffix:
if contents[-1] not in ':?.!':
contents = format_html('{0}{1}', contents, self.form.label_suffix)
widget = self.field.widget widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id id_ = widget.attrs.get('id') or self.auto_id
if id_: if id_:

View File

@ -498,6 +498,8 @@ include ``%s`` -- then the library will act as if ``auto_id`` is ``True``.
By default, ``auto_id`` is set to the string ``'id_%s'``. By default, ``auto_id`` is set to the string ``'id_%s'``.
.. attribute:: Form.label_suffix
Normally, a colon (``:``) will be appended after any label name when a form is Normally, a colon (``:``) will be appended after any label name when a form is
rendered. It's possible to change the colon to another character, or omit it rendered. It's possible to change the colon to another character, or omit it
entirely, using the ``label_suffix`` parameter:: entirely, using the ``label_suffix`` parameter::
@ -650,12 +652,17 @@ To separately render the label tag of a form field, you can call its
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> print(f['message'].label_tag()) >>> print(f['message'].label_tag())
<label for="id_message">Message</label> <label for="id_message">Message:</label>
Optionally, you can provide the ``contents`` parameter which will replace the Optionally, you can provide the ``contents`` parameter which will replace the
auto-generated label tag. An optional ``attrs`` dictionary may contain auto-generated label tag. An optional ``attrs`` dictionary may contain
additional attributes for the ``<label>`` tag. additional attributes for the ``<label>`` tag.
.. versionchanged:: 1.6
The label now includes the form's :attr:`~django.forms.Form.label_suffix`
(a semicolon, by default).
.. method:: BoundField.css_classes() .. method:: BoundField.css_classes()
When you use Django's rendering shortcuts, CSS classes are used to When you use Django's rendering shortcuts, CSS classes are used to

View File

@ -581,6 +581,37 @@ It is still possible to convert the fetched rows to ``Model`` objects
lazily by using the :meth:`~django.db.models.query.QuerySet.iterator()` lazily by using the :meth:`~django.db.models.query.QuerySet.iterator()`
method. method.
:meth:`BoundField.label_tag<django.forms.BoundField.label_tag>` now includes the form's :attr:`~django.forms.Form.label_suffix`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is consistent with how methods like
:meth:`Form.as_p<django.forms.Form.as_p>` and
:meth:`Form.as_ul<django.forms.Form.as_ul>` render labels.
If you manually render ``label_tag`` in your templates:
.. code-block:: html+django
{{ form.my_field.label_tag }}: {{ form.my_field }}
you'll want to remove the semicolon (or whatever other separator you may be
using) to avoid duplicating it when upgrading to Django 1.6. The following
template in Django 1.6 will render identically to the above template in Django
1.5, except that the semicolon will appear inside the ``<label>`` element.
.. code-block:: html+django
{{ form.my_field.label_tag }} {{ form.my_field }}
will render something like:
.. code-block:: html
<label for="id_my_field">My Field:</label> <input id="id_my_field" type="text" name="my_field" />
If you want to keep the current behavior of rendering ``label_tag`` without
the ``label_suffix``, instantiate the form ``label_suffix=''``.
Miscellaneous Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -302,7 +302,7 @@ loop::
{% for field in form %} {% for field in form %}
<div class="fieldWrapper"> <div class="fieldWrapper">
{{ field.errors }} {{ field.errors }}
{{ field.label_tag }}: {{ field }} {{ field.label_tag }} {{ field }}
</div> </div>
{% endfor %} {% endfor %}
<p><input type="submit" value="Send message" /></p> <p><input type="submit" value="Send message" /></p>
@ -316,8 +316,14 @@ attributes, which can be useful in your templates:
The label of the field, e.g. ``Email address``. The label of the field, e.g. ``Email address``.
``{{ field.label_tag }}`` ``{{ field.label_tag }}``
The field's label wrapped in the appropriate HTML ``<label>`` tag, The field's label wrapped in the appropriate HTML ``<label>`` tag.
e.g. ``<label for="id_email">Email address</label>``
.. versionchanged:: 1.6
This includes the form's :attr:`~django.forms.Form.label_suffix`. For
example, the default ``label_suffix`` is a semicolon::
<label for="id_email">Email address:</label>
``{{ field.value }}`` ``{{ field.value }}``
The value of the field. e.g ``someone@example.com`` The value of the field. e.g ``someone@example.com``
@ -375,7 +381,7 @@ these two methods::
{% for field in form.visible_fields %} {% for field in form.visible_fields %}
<div class="fieldWrapper"> <div class="fieldWrapper">
{{ field.errors }} {{ field.errors }}
{{ field.label_tag }}: {{ field }} {{ field.label_tag }} {{ field }}
</div> </div>
{% endfor %} {% endfor %}
<p><input type="submit" value="Send message" /></p> <p><input type="submit" value="Send message" /></p>
@ -403,7 +409,7 @@ using the :ttag:`include` tag to reuse it in other templates::
{% for field in form %} {% for field in form %}
<div class="fieldWrapper"> <div class="fieldWrapper">
{{ field.errors }} {{ field.errors }}
{{ field.label_tag }}: {{ field }} {{ field.label_tag }} {{ field }}
</div> </div>
{% endfor %} {% endfor %}

View File

@ -907,7 +907,7 @@ Third, you can manually render each field::
{{ formset.management_form }} {{ formset.management_form }}
{% for form in formset %} {% for form in formset %}
{% for field in form %} {% for field in form %}
{{ field.label_tag }}: {{ field }} {{ field.label_tag }} {{ field }}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</form> </form>

View File

@ -301,7 +301,7 @@ class UtilTests(SimpleTestCase):
self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(), self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
'<label for="id_text" class="required inline"><i>text</i>:</label>') '<label for="id_text" class="required inline"><i>text</i>:</label>')
self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
'<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>') '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i>:</label>')
# normal strings needs to be escaped # normal strings needs to be escaped
class MyForm(forms.Form): class MyForm(forms.Form):
@ -312,7 +312,7 @@ class UtilTests(SimpleTestCase):
self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(), self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
'<label for="id_text" class="required inline">&amp;text:</label>') '<label for="id_text" class="required inline">&amp;text:</label>')
self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
'<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>') '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb:</label>')
def test_flatten_fieldsets(self): def test_flatten_fieldsets(self):
""" """

View File

@ -1589,9 +1589,9 @@ class FormsTestCase(TestCase):
# Recall from above that passing the "auto_id" argument to a Form gives each # Recall from above that passing the "auto_id" argument to a Form gives each
# field an "id" attribute. # field an "id" attribute.
t = Template('''<form action=""> t = Template('''<form action="">
<p>{{ form.username.label_tag }}: {{ form.username }}</p> <p>{{ form.username.label_tag }} {{ form.username }}</p>
<p>{{ form.password1.label_tag }}: {{ form.password1 }}</p> <p>{{ form.password1.label_tag }} {{ form.password1 }}</p>
<p>{{ form.password2.label_tag }}: {{ form.password2 }}</p> <p>{{ form.password2.label_tag }} {{ form.password2 }}</p>
<input type="submit" /> <input type="submit" />
</form>''') </form>''')
self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action=""> self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action="">
@ -1601,18 +1601,18 @@ class FormsTestCase(TestCase):
<input type="submit" /> <input type="submit" />
</form>""") </form>""")
self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id='id_%s')})), """<form action=""> self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id='id_%s')})), """<form action="">
<p><label for="id_username">Username</label>: <input id="id_username" type="text" name="username" maxlength="10" /></p> <p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
<p><label for="id_password1">Password1</label>: <input type="password" name="password1" id="id_password1" /></p> <p><label for="id_password1">Password1:</label> <input type="password" name="password1" id="id_password1" /></p>
<p><label for="id_password2">Password2</label>: <input type="password" name="password2" id="id_password2" /></p> <p><label for="id_password2">Password2:</label> <input type="password" name="password2" id="id_password2" /></p>
<input type="submit" /> <input type="submit" />
</form>""") </form>""")
# User form.[field].help_text to output a field's help text. If the given field # User form.[field].help_text to output a field's help text. If the given field
# does not have help text, nothing will be output. # does not have help text, nothing will be output.
t = Template('''<form action=""> t = Template('''<form action="">
<p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p> <p>{{ form.username.label_tag }} {{ form.username }}<br />{{ form.username.help_text }}</p>
<p>{{ form.password1.label_tag }}: {{ form.password1 }}</p> <p>{{ form.password1.label_tag }} {{ form.password1 }}</p>
<p>{{ form.password2.label_tag }}: {{ form.password2 }}</p> <p>{{ form.password2.label_tag }} {{ form.password2 }}</p>
<input type="submit" /> <input type="submit" />
</form>''') </form>''')
self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action=""> self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action="">
@ -1819,17 +1819,17 @@ class FormsTestCase(TestCase):
testcases = [ # (args, kwargs, expected) testcases = [ # (args, kwargs, expected)
# without anything: just print the <label> # without anything: just print the <label>
((), {}, '<label for="id_field">Field</label>'), ((), {}, '<label for="id_field">Field:</label>'),
# passing just one argument: overrides the field's label # passing just one argument: overrides the field's label
(('custom',), {}, '<label for="id_field">custom</label>'), (('custom',), {}, '<label for="id_field">custom:</label>'),
# the overriden label is escaped # the overriden label is escaped
(('custom&',), {}, '<label for="id_field">custom&amp;</label>'), (('custom&',), {}, '<label for="id_field">custom&amp;:</label>'),
((mark_safe('custom&'),), {}, '<label for="id_field">custom&</label>'), ((mark_safe('custom&'),), {}, '<label for="id_field">custom&:</label>'),
# Passing attrs to add extra attributes on the <label> # Passing attrs to add extra attributes on the <label>
((), {'attrs': {'class': 'pretty'}}, '<label for="id_field" class="pretty">Field</label>') ((), {'attrs': {'class': 'pretty'}}, '<label for="id_field" class="pretty">Field:</label>')
] ]
for args, kwargs, expected in testcases: for args, kwargs, expected in testcases:
@ -1844,8 +1844,8 @@ class FormsTestCase(TestCase):
field = CharField() field = CharField()
boundfield = SomeForm(auto_id='')['field'] boundfield = SomeForm(auto_id='')['field']
self.assertHTMLEqual(boundfield.label_tag(), 'Field') self.assertHTMLEqual(boundfield.label_tag(), 'Field:')
self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&amp;') self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&amp;:')
def test_boundfield_label_tag_custom_widget_id_for_label(self): def test_boundfield_label_tag_custom_widget_id_for_label(self):
class CustomIdForLabelTextInput(TextInput): class CustomIdForLabelTextInput(TextInput):
@ -1861,5 +1861,5 @@ class FormsTestCase(TestCase):
empty = CharField(widget=EmptyIdForLabelTextInput) empty = CharField(widget=EmptyIdForLabelTextInput)
form = SomeForm() form = SomeForm()
self.assertHTMLEqual(form['custom'].label_tag(), '<label for="custom_id_custom">Custom</label>') self.assertHTMLEqual(form['custom'].label_tag(), '<label for="custom_id_custom">Custom:</label>')
self.assertHTMLEqual(form['empty'].label_tag(), '<label>Empty</label>') self.assertHTMLEqual(form['empty'].label_tag(), '<label>Empty:</label>')

View File

@ -45,8 +45,8 @@ class FormsRegressionsTestCase(TransRealMixin, TestCase):
field_2 = CharField(max_length=10, label=ugettext_lazy('field_2'), widget=TextInput(attrs={'id': 'field_2_id'})) field_2 = CharField(max_length=10, label=ugettext_lazy('field_2'), widget=TextInput(attrs={'id': 'field_2_id'}))
f = SomeForm() f = SomeForm()
self.assertHTMLEqual(f['field_1'].label_tag(), '<label for="id_field_1">field_1</label>') self.assertHTMLEqual(f['field_1'].label_tag(), '<label for="id_field_1">field_1:</label>')
self.assertHTMLEqual(f['field_2'].label_tag(), '<label for="field_2_id">field_2</label>') self.assertHTMLEqual(f['field_2'].label_tag(), '<label for="field_2_id">field_2:</label>')
# Unicode decoding problems... # Unicode decoding problems...
GENDERS = (('\xc5', 'En tied\xe4'), ('\xf8', 'Mies'), ('\xdf', 'Nainen')) GENDERS = (('\xc5', 'En tied\xe4'), ('\xf8', 'Mies'), ('\xdf', 'Nainen'))