diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index ffa5692..3f2b219 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -9,8 +9,7 @@ from django.db.models.query import QuerySet
 from django.db.models.query_utils import QueryWrapper
 from django.db.models.deletion import CASCADE
 from django.utils.encoding import smart_unicode
-from django.utils.translation import (ugettext_lazy as _, string_concat,
-    ungettext, ugettext)
+from django.utils.translation import ugettext_lazy as _, ungettext, ugettext
 from django.utils.functional import curry
 from django.core import exceptions
 from django import forms
@@ -1021,9 +1020,6 @@ class ManyToManyField(RelatedField, Field):
 
         Field.__init__(self, **kwargs)
 
-        msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
-        self.help_text = string_concat(self.help_text, ' ', msg)
-
     def get_choices_default(self):
         return Field.get_choices(self, include_blank=False)
 
diff --git a/django/forms/fields.py b/django/forms/fields.py
index a5ea81d..2e35f3d 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -80,10 +80,6 @@ class Field(object):
             label = smart_unicode(label)
         self.required, self.label, self.initial = required, label, initial
         self.show_hidden_initial = show_hidden_initial
-        if help_text is None:
-            self.help_text = u''
-        else:
-            self.help_text = smart_unicode(help_text)
         widget = widget or self.widget
         if isinstance(widget, type):
             widget = widget()
@@ -103,6 +99,16 @@ class Field(object):
 
         self.widget = widget
 
+        if help_text is None:
+            self.help_text = u''
+        else:
+            self.help_text = smart_unicode(help_text)
+
+        # Allow the widget to append something to the help_text
+        if self.widget.extra_help_text is not None:
+            self.help_text = '%s %s' % (self.help_text, 
+                                        smart_unicode(self.widget.extra_help_text))
+
         # Increase the creation counter, and save our local copy.
         self.creation_counter = Field.creation_counter
         Field.creation_counter += 1
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 03152ea..9acae7b 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -142,7 +142,8 @@ class Widget(object):
     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
     is_localized = False
     is_required = False
-
+    extra_help_text = None # If set, will be appended to the form field's help text.
+    
     def __init__(self, attrs=None):
         if attrs is not None:
             self.attrs = attrs.copy()
@@ -154,7 +155,7 @@ class Widget(object):
         obj.attrs = self.attrs.copy()
         memo[id(self)] = obj
         return obj
-
+    
     def render(self, name, value, attrs=None):
         """
         Returns this Widget rendered as HTML, as a Unicode string.
@@ -575,6 +576,10 @@ class NullBooleanSelect(Select):
         return initial != data
 
 class SelectMultiple(Select):
+    
+    extra_help_text = ugettext_lazy('Hold down "Control", or "Command" '
+                                    'on a Mac, to select more than one.')
+    
     def render(self, name, value, attrs=None, choices=()):
         if value is None: value = []
         final_attrs = self.build_attrs(attrs, name=name)
@@ -690,6 +695,11 @@ class RadioSelect(Select):
     id_for_label = classmethod(id_for_label)
 
 class CheckboxSelectMultiple(SelectMultiple):
+    
+    # Do not show the 'Hold down "Control"' message that appears
+    # for SelectMultiple.
+    extra_help_text = None
+    
     def render(self, name, value, attrs=None, choices=()):
         if value is None: value = []
         has_id = attrs and 'id' in attrs
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index dbdf109..df94475 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -258,3 +258,16 @@ Django will then include the extra attributes in the rendered output::
     <tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
     <tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
     <tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
+
+Adding to the help text
+-----------------------
+
+.. versionadded:: 1.4
+
+In some cases you might want to add extra information to a form field's help
+text depending on the widget used. To do so, simply use the widget's
+:attr:`~extra_help_text` class attribute::
+
+    class MyCustomSelectMultiple(SelectMultiple):
+        
+        extra_help_text = 'You can hold down "Control" to select multiple ones.'
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 1b8510f..cf9bdee 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -60,6 +60,18 @@ A new helper function,
 ``template.Library`` to ease the creation of template tags that store some
 data in a specified context variable.
 
+Widget.extra_help_text
+~~~~~~~~~~~~~~~~~~~~~~
+
+The :attr:`~extra_help_text` class attribute was added to
+`:class:~forms.widgets.Widget` to allow a widget to add extra information to
+the help text provided by a form field or a model field. This introduced a
+backwards incompatibility if you are defining custom ``SelectMultiple``
+widgets. See `the notes on backwards incompatible changes`_ below to address
+this change.
+
+.. _the notes on backwards incompatible changes: backwards-incompatible-changes-1.4_
+
 .. _backwards-incompatible-changes-1.4:
 
 Backwards incompatible changes in 1.4
@@ -214,3 +226,31 @@ you should add the following lines in your settings file::
 
 Don't forget to escape characters that have a special meaning in a regular
 expression.
+
+Custom ``SelectMultiple`` widgets and ``extra_help_text``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The introduction of a new :attr:`~Widget.extra_help_text` class attribute in
+this release was originally prompted by the fixing of a bug where the
+'`Hold down "Control", or "Command" on a Mac, to select more than one.`'
+message would systematically be appended to any ``ManyToManyField``'s help
+text, even if that field were configured to use a different widget than
+``SelectMultiple``. Thus, the responsibility for appending that message was
+transfered from ``ManyToManyField`` to ``SelectMultiple`` via the new
+``extra_help_text`` attribute::
+
+    class SelectMultiple(Select):
+    
+        extra_help_text = ugettext_lazy('Hold down "Control", or "Command" '
+                                        'on a Mac, to select more than one.')
+
+This means that if you are defining a widget that inherits from
+``SelectMultiple``, then that extra message will now systematically be appended
+to the help text, which may not make sense in the context of use for your
+widget. To cancel this new behaviour, simply override the ``extra_help_text``
+class attribute of your widget like it is done, for example, by the
+`:class:~forms.widgets.CheckboxSelectMultiple` class::
+
+    class CheckboxSelectMultiple(SelectMultiple):
+        
+        extra_help_text = None
diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
index 91a7472..5b2b7b4 100644
--- a/tests/regressiontests/forms/tests/forms.py
+++ b/tests/regressiontests/forms/tests/forms.py
@@ -1092,7 +1092,7 @@ class FormsTestCase(TestCase):
 <option value="f" selected="selected">foo</option>
 <option value="b" selected="selected">bar</option>
 <option value="w">whiz</option>
-</select></li>""")
+</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
 
         # The 'initial' parameter is meaningless if you pass data.
         p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False)
@@ -1102,7 +1102,7 @@ class FormsTestCase(TestCase):
 <option value="f">foo</option>
 <option value="b">bar</option>
 <option value="w">whiz</option>
-</select></li>""")
+</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
         p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False)
         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>
 <li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
@@ -1110,7 +1110,7 @@ class FormsTestCase(TestCase):
 <option value="f">foo</option>
 <option value="b">bar</option>
 <option value="w">whiz</option>
-</select></li>""")
+</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
         p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False)
         self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
 <li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
@@ -1118,7 +1118,7 @@ class FormsTestCase(TestCase):
 <option value="f" selected="selected">foo</option>
 <option value="b" selected="selected">bar</option>
 <option value="w">whiz</option>
-</select></li>""")
+</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
 
         # A callable 'initial' value is *not* used as a fallback if data is not provided.
         # In this example, we don't provide a value for 'username', and the form raises a
@@ -1141,7 +1141,7 @@ class FormsTestCase(TestCase):
 <option value="f">foo</option>
 <option value="b" selected="selected">bar</option>
 <option value="w" selected="selected">whiz</option>
-</select></li>""")
+</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
         p = UserRegistration(initial={'username': initial_stephane, 'options': initial_options}, auto_id=False)
         self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
 <li>Password: <input type="password" name="password" /></li>
@@ -1149,7 +1149,7 @@ class FormsTestCase(TestCase):
 <option value="f" selected="selected">foo</option>
 <option value="b" selected="selected">bar</option>
 <option value="w">whiz</option>
-</select></li>""")
+</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>""")
 
     def test_boundfield_values(self):
         # It's possible to get to the value which would be used for rendering
diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py
index f536001..bdd3b5b 100644
--- a/tests/regressiontests/model_forms_regress/tests.py
+++ b/tests/regressiontests/model_forms_regress/tests.py
@@ -3,6 +3,7 @@ from datetime import date
 from django import forms
 from django.core.exceptions import FieldError, ValidationError
 from django.core.files.uploadedfile import SimpleUploadedFile
+from django.forms.widgets import CheckboxSelectMultiple
 from django.forms.models import (modelform_factory, ModelChoiceField,
     fields_for_model, construct_instance)
 from django.utils import unittest
@@ -140,6 +141,29 @@ class ManyToManyCallableInitialTests(TestCase):
 </select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>"""
             % (book1.pk, book2.pk, book3.pk))
 
+class ManyToManyHelpTextTests(TestCase):
+    
+    def test_dont_display_hold_down_command_help_text(self):
+        """
+        Ensures that the 'Hold down "Control"' message is not systematically displayed for
+        a M2M field if it does not use the default SelectMultiple widget.
+        Refs #9321.
+        """
+        # Override the widget
+        def formfield_for_dbfield(db_field, **kwargs):
+            if db_field.name == 'publications':
+                kwargs['widget'] = CheckboxSelectMultiple
+            return db_field.formfield(**kwargs)
+
+        # Set up some Publications to use as data
+        book1 = Publication.objects.create(title="First Book", date_published=date(2007,1,1))
+        book2 = Publication.objects.create(title="Second Book", date_published=date(2008,1,1))
+
+        # Create a ModelForm, instantiate it, and check that the output is as expected
+        ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield)
+        form = ModelForm()
+        self.assertEqual(unicode(form['publications'].help_text), u'')
+        
 class CFFForm(forms.ModelForm):
     class Meta:
         model = CustomFF
diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py
index e6c2633..bf86a85 100644
--- a/tests/regressiontests/model_formsets_regress/tests.py
+++ b/tests/regressiontests/model_formsets_regress/tests.py
@@ -228,7 +228,7 @@ class FormsetTests(TestCase):
             self.assertTrue(isinstance(form.errors, ErrorDict))
             self.assertTrue(isinstance(form.non_field_errors(), ErrorList))
 
-class CustomWidget(forms.CharField):
+class CustomWidget(forms.TextInput):
     pass
 
 
