Ticket #9321: 9321.manytomanyfield-helptext.diff

File 9321.manytomanyfield-helptext.diff, 13.3 KB (added by julien, 5 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): 
    10221021        Field.__init__(self, **kwargs)
    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)
    10271023    def get_choices_default(self):
    10281024        return Field.get_choices(self, include_blank=False)
  • 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): 
    104100        self.widget = widget
     102        if help_text is None:
     103            self.help_text = u''
     104        else:
     105            self.help_text = smart_unicode(help_text)
     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))
    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    extra_help_text = None # If set, will be appended to the form field's help text.
    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
    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
    577578class SelectMultiple(Select):
     580    extra_help_text = ugettext_lazy('Hold down "Control", or "Command" '
     581                                    'on a Mac, to select more than one.')
    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)
    692697class CheckboxSelectMultiple(SelectMultiple):
     699    # Do not show the 'Hold down "Control"' message that appears
     700    # for SelectMultiple.
     701    extra_help_text = None
    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>
     262Adding to the help text
     265.. versionadded:: 1.4
     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::
     271    class MyCustomSelectMultiple(SelectMultiple):
     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.
     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.
     73.. _the notes on backwards incompatible changes: backwards-incompatible-changes-1.4_
    6375.. _backwards-incompatible-changes-1.4:
    6577Backwards incompatible changes in 1.4
    you should add the following lines in your settings file:: 
    215227Don't forget to escape characters that have a special meaning in a regular
     230Custom ``SelectMultiple`` widgets and ``extra_help_text``
     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::
     242    class SelectMultiple(Select):
     244        extra_help_text = ugettext_lazy('Hold down "Control", or "Command" '
     245                                        'on a Mac, to select more than one.')
     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::
     254    class CheckboxSelectMultiple(SelectMultiple):
     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>""")
    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>""")
    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>""")
    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))
     144class ManyToManyHelpTextTests(TestCase):
     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)
     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))
     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'')
    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))
    231 class CustomWidget(forms.CharField):
     231class CustomWidget(forms.TextInput):
    232232    pass
Back to Top