Ticket #9321: 9321.manytomanyfield-helptext.diff

File 9321.manytomanyfield-helptext.diff, 13.3 KB (added by julien, 4 years ago)

Using extra_help_text

  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index ffa5692..3f2b219 100644
    a b from django.db.models.query import QuerySet 
    99from django.db.models.query_utils import QueryWrapper
    1010from django.db.models.deletion import CASCADE
    1111from django.utils.encoding import smart_unicode
    12 from django.utils.translation import (ugettext_lazy as _, string_concat,
    13     ungettext, ugettext)
     12from django.utils.translation import ugettext_lazy as _, ungettext, ugettext
    1413from django.utils.functional import curry
    1514from django.core import exceptions
    1615from django import forms
    class ManyToManyField(RelatedField, Field): 
    10211020
    10221021        Field.__init__(self, **kwargs)
    10231022
    1024         msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
    1025         self.help_text = string_concat(self.help_text, ' ', msg)
    1026 
    10271023    def get_choices_default(self):
    10281024        return Field.get_choices(self, include_blank=False)
    10291025
  • django/forms/fields.py

    diff --git a/django/forms/fields.py b/django/forms/fields.py
    index a5ea81d..2e35f3d 100644
    a b class Field(object): 
    8080            label = smart_unicode(label)
    8181        self.required, self.label, self.initial = required, label, initial
    8282        self.show_hidden_initial = show_hidden_initial
    83         if help_text is None:
    84             self.help_text = u''
    85         else:
    86             self.help_text = smart_unicode(help_text)
    8783        widget = widget or self.widget
    8884        if isinstance(widget, type):
    8985            widget = widget()
    class Field(object): 
    10399
    104100        self.widget = widget
    105101
     102        if help_text is None:
     103            self.help_text = u''
     104        else:
     105            self.help_text = smart_unicode(help_text)
     106
     107        # Allow the widget to append something to the help_text
     108        if self.widget.extra_help_text is not None:
     109            self.help_text = '%s %s' % (self.help_text,
     110                                        smart_unicode(self.widget.extra_help_text))
     111
    106112        # Increase the creation counter, and save our local copy.
    107113        self.creation_counter = Field.creation_counter
    108114        Field.creation_counter += 1
  • django/forms/widgets.py

    diff --git a/django/forms/widgets.py b/django/forms/widgets.py
    index 03152ea..9acae7b 100644
    a b class Widget(object): 
    142142    needs_multipart_form = False # Determines does this widget need multipart-encrypted form
    143143    is_localized = False
    144144    is_required = False
    145 
     145    extra_help_text = None # If set, will be appended to the form field's help text.
     146   
    146147    def __init__(self, attrs=None):
    147148        if attrs is not None:
    148149            self.attrs = attrs.copy()
    class Widget(object): 
    154155        obj.attrs = self.attrs.copy()
    155156        memo[id(self)] = obj
    156157        return obj
    157 
     158   
    158159    def render(self, name, value, attrs=None):
    159160        """
    160161        Returns this Widget rendered as HTML, as a Unicode string.
    class NullBooleanSelect(Select): 
    575576        return initial != data
    576577
    577578class SelectMultiple(Select):
     579   
     580    extra_help_text = ugettext_lazy('Hold down "Control", or "Command" '
     581                                    'on a Mac, to select more than one.')
     582   
    578583    def render(self, name, value, attrs=None, choices=()):
    579584        if value is None: value = []
    580585        final_attrs = self.build_attrs(attrs, name=name)
    class RadioSelect(Select): 
    690695    id_for_label = classmethod(id_for_label)
    691696
    692697class CheckboxSelectMultiple(SelectMultiple):
     698   
     699    # Do not show the 'Hold down "Control"' message that appears
     700    # for SelectMultiple.
     701    extra_help_text = None
     702   
    693703    def render(self, name, value, attrs=None, choices=()):
    694704        if value is None: value = []
    695705        has_id = attrs and 'id' in attrs
  • docs/ref/forms/widgets.txt

    diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
    index dbdf109..df94475 100644
    a b Django will then include the extra attributes in the rendered output:: 
    258258    <tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
    259259    <tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
    260260    <tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
     261
     262Adding to the help text
     263-----------------------
     264
     265.. versionadded:: 1.4
     266
     267In some cases you might want to add extra information to a form field's help
     268text depending on the widget used. To do so, simply use the widget's
     269:attr:`~extra_help_text` class attribute::
     270
     271    class MyCustomSelectMultiple(SelectMultiple):
     272       
     273        extra_help_text = 'You can hold down "Control" to select multiple ones.'
  • docs/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index 1b8510f..cf9bdee 100644
    a b A new helper function, 
    6060``template.Library`` to ease the creation of template tags that store some
    6161data in a specified context variable.
    6262
     63Widget.extra_help_text
     64~~~~~~~~~~~~~~~~~~~~~~
     65
     66The :attr:`~extra_help_text` class attribute was added to
     67`:class:~forms.widgets.Widget` to allow a widget to add extra information to
     68the help text provided by a form field or a model field. This introduced a
     69backwards incompatibility if you are defining custom ``SelectMultiple``
     70widgets. See `the notes on backwards incompatible changes`_ below to address
     71this change.
     72
     73.. _the notes on backwards incompatible changes: backwards-incompatible-changes-1.4_
     74
    6375.. _backwards-incompatible-changes-1.4:
    6476
    6577Backwards incompatible changes in 1.4
    you should add the following lines in your settings file:: 
    214226
    215227Don't forget to escape characters that have a special meaning in a regular
    216228expression.
     229
     230Custom ``SelectMultiple`` widgets and ``extra_help_text``
     231~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     232
     233The introduction of a new :attr:`~Widget.extra_help_text` class attribute in
     234this release was originally prompted by the fixing of a bug where the
     235'`Hold down "Control", or "Command" on a Mac, to select more than one.`'
     236message would systematically be appended to any ``ManyToManyField``'s help
     237text, even if that field were configured to use a different widget than
     238``SelectMultiple``. Thus, the responsibility for appending that message was
     239transfered from ``ManyToManyField`` to ``SelectMultiple`` via the new
     240``extra_help_text`` attribute::
     241
     242    class SelectMultiple(Select):
     243   
     244        extra_help_text = ugettext_lazy('Hold down "Control", or "Command" '
     245                                        'on a Mac, to select more than one.')
     246
     247This means that if you are defining a widget that inherits from
     248``SelectMultiple``, then that extra message will now systematically be appended
     249to the help text, which may not make sense in the context of use for your
     250widget. To cancel this new behaviour, simply override the ``extra_help_text``
     251class attribute of your widget like it is done, for example, by the
     252`:class:~forms.widgets.CheckboxSelectMultiple` class::
     253
     254    class CheckboxSelectMultiple(SelectMultiple):
     255       
     256        extra_help_text = None
  • tests/regressiontests/forms/tests/forms.py

    diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
    index 91a7472..5b2b7b4 100644
    a b class FormsTestCase(TestCase): 
    10921092<option value="f" selected="selected">foo</option>
    10931093<option value="b" selected="selected">bar</option>
    10941094<option value="w">whiz</option>
    1095 </select></li>""")
     1095</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
    10961096
    10971097        # The 'initial' parameter is meaningless if you pass data.
    10981098        p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False)
    class FormsTestCase(TestCase): 
    11021102<option value="f">foo</option>
    11031103<option value="b">bar</option>
    11041104<option value="w">whiz</option>
    1105 </select></li>""")
     1105</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
    11061106        p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False)
    11071107        self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
    11081108<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
    class FormsTestCase(TestCase): 
    11101110<option value="f">foo</option>
    11111111<option value="b">bar</option>
    11121112<option value="w">whiz</option>
    1113 </select></li>""")
     1113</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
    11141114        p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False)
    11151115        self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
    11161116<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
    class FormsTestCase(TestCase): 
    11181118<option value="f" selected="selected">foo</option>
    11191119<option value="b" selected="selected">bar</option>
    11201120<option value="w">whiz</option>
    1121 </select></li>""")
     1121</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
    11221122
    11231123        # A callable 'initial' value is *not* used as a fallback if data is not provided.
    11241124        # In this example, we don't provide a value for 'username', and the form raises a
    class FormsTestCase(TestCase): 
    11411141<option value="f">foo</option>
    11421142<option value="b" selected="selected">bar</option>
    11431143<option value="w" selected="selected">whiz</option>
    1144 </select></li>""")
     1144</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
    11451145        p = UserRegistration(initial={'username': initial_stephane, 'options': initial_options}, auto_id=False)
    11461146        self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
    11471147<li>Password: <input type="password" name="password" /></li>
    class FormsTestCase(TestCase): 
    11491149<option value="f" selected="selected">foo</option>
    11501150<option value="b" selected="selected">bar</option>
    11511151<option value="w">whiz</option>
    1152 </select></li>""")
     1152</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
    11531153
    11541154    def test_boundfield_values(self):
    11551155        # It's possible to get to the value which would be used for rendering
  • tests/regressiontests/model_forms_regress/tests.py

    diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py
    index f536001..bdd3b5b 100644
    a b from datetime import date 
    33from django import forms
    44from django.core.exceptions import FieldError, ValidationError
    55from django.core.files.uploadedfile import SimpleUploadedFile
     6from django.forms.widgets import CheckboxSelectMultiple
    67from django.forms.models import (modelform_factory, ModelChoiceField,
    78    fields_for_model, construct_instance)
    89from django.utils import unittest
    class ManyToManyCallableInitialTests(TestCase): 
    140141</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>"""
    141142            % (book1.pk, book2.pk, book3.pk))
    142143
     144class ManyToManyHelpTextTests(TestCase):
     145   
     146    def test_dont_display_hold_down_command_help_text(self):
     147        """
     148        Ensures that the 'Hold down "Control"' message is not systematically displayed for
     149        a M2M field if it does not use the default SelectMultiple widget.
     150        Refs #9321.
     151        """
     152        # Override the widget
     153        def formfield_for_dbfield(db_field, **kwargs):
     154            if db_field.name == 'publications':
     155                kwargs['widget'] = CheckboxSelectMultiple
     156            return db_field.formfield(**kwargs)
     157
     158        # Set up some Publications to use as data
     159        book1 = Publication.objects.create(title="First Book", date_published=date(2007,1,1))
     160        book2 = Publication.objects.create(title="Second Book", date_published=date(2008,1,1))
     161
     162        # Create a ModelForm, instantiate it, and check that the output is as expected
     163        ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield)
     164        form = ModelForm()
     165        self.assertEqual(unicode(form['publications'].help_text), u'')
     166       
    143167class CFFForm(forms.ModelForm):
    144168    class Meta:
    145169        model = CustomFF
  • tests/regressiontests/model_formsets_regress/tests.py

    diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py
    index e6c2633..bf86a85 100644
    a b class FormsetTests(TestCase): 
    228228            self.assertTrue(isinstance(form.errors, ErrorDict))
    229229            self.assertTrue(isinstance(form.non_field_errors(), ErrorList))
    230230
    231 class CustomWidget(forms.CharField):
     231class CustomWidget(forms.TextInput):
    232232    pass
    233233
    234234
Back to Top