From ccf32aca442feae4c765fb6133c418b4e387f428 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 5 Mar 2020 21:53:16 +0000 Subject: [PATCH] Fixed #8760 -- Changed ModelMultipleChoiceField to use invalid_list as a error message key. --- django/forms/models.py | 21 +++++++++++---- docs/internals/deprecation.txt | 2 ++ docs/ref/forms/fields.txt | 6 ++++- docs/releases/3.1.txt | 3 +++ .../forms_tests/tests/test_error_messages.py | 26 +++++++++++++++++-- 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/django/forms/models.py b/django/forms/models.py index a4d7118cd1..0f6608a4bc 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -2,7 +2,7 @@ Helper functions for creating Form classes from Django models and database field objects. """ - +import warnings from itertools import chain from django.core.exceptions import ( @@ -15,6 +15,7 @@ from django.forms.utils import ErrorList from django.forms.widgets import ( HiddenInput, MultipleHiddenInput, RadioSelect, SelectMultiple, ) +from django.utils.deprecation import RemovedInDjango40Warning from django.utils.text import capfirst, get_text_list from django.utils.translation import gettext, gettext_lazy as _ @@ -1291,7 +1292,7 @@ class ModelMultipleChoiceField(ModelChoiceField): widget = SelectMultiple hidden_widget = MultipleHiddenInput default_error_messages = { - 'list': _('Enter a list of values.'), + 'invalid_list': _('Enter a list of values.'), 'invalid_choice': _('Select a valid choice. %(value)s is not one of the' ' available choices.'), 'invalid_pk_value': _('ā€œ%(pk)sā€ is not a valid value.') @@ -1299,6 +1300,13 @@ class ModelMultipleChoiceField(ModelChoiceField): def __init__(self, queryset, **kwargs): super().__init__(queryset, empty_label=None, **kwargs) + if self.error_messages.get('list') is not None: + warnings.warn( + "The 'list' error message key is deprecated in favor of " + "'invalid_list'.", + RemovedInDjango40Warning, stacklevel=2, + ) + self.error_messages['invalid_list'] = self.error_messages['list'] def to_python(self, value): if not value: @@ -1312,7 +1320,10 @@ class ModelMultipleChoiceField(ModelChoiceField): elif not self.required and not value: return self.queryset.none() if not isinstance(value, (list, tuple)): - raise ValidationError(self.error_messages['list'], code='list') + raise ValidationError( + self.error_messages['invalid_list'], + code='invalid_list', + ) qs = self._check_values(value) # Since this overrides the inherited ModelChoiceField.clean # we run custom validators here @@ -1333,8 +1344,8 @@ class ModelMultipleChoiceField(ModelChoiceField): except TypeError: # list of lists isn't hashable, for example raise ValidationError( - self.error_messages['list'], - code='list', + self.error_messages['invalid_list'], + code='invalid_list', ) for pk in value: try: diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 580301436a..a4262af43b 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -64,6 +64,8 @@ details on these changes. * The ``length`` argument for ``django.utils.crypto.get_random_string()`` will be required. +* The ``list`` message for ``ModelMultipleChoiceField`` will be removed. + See the :ref:`Django 3.1 release notes ` for more details on these changes. diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index d2a0550a47..92c1af63d7 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -1255,7 +1255,7 @@ generating choices. See :ref:`iterating-relationship-choices` for details. * Normalizes to: A ``QuerySet`` of model instances. * Validates that every id in the given list of values exists in the queryset. - * Error message keys: ``required``, ``list``, ``invalid_choice``, + * Error message keys: ``required``, ``invalid_list``, ``invalid_choice``, ``invalid_pk_value`` The ``invalid_choice`` message may contain ``%(value)s`` and the @@ -1285,6 +1285,10 @@ generating choices. See :ref:`iterating-relationship-choices` for details. Same as :class:`ModelChoiceField.iterator`. +.. deprecated:: 3.1 + + The ``list`` message is deprecated, use ``invalid_list`` instead. + .. _iterating-relationship-choices: Iterating relationship choices diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 18b40b3bcc..bbde94a34e 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -557,6 +557,9 @@ Miscellaneous * Calling ``django.utils.crypto.get_random_string()`` without a ``length`` argument is deprecated. +* The ``list`` message for :class:`~django.forms.ModelMultipleChoiceField` is + deprecated in favor of ``invalid_list``. + .. _removed-features-3.1: Features removed in 3.1 diff --git a/tests/forms_tests/tests/test_error_messages.py b/tests/forms_tests/tests/test_error_messages.py index 52e436791a..f324c08096 100644 --- a/tests/forms_tests/tests/test_error_messages.py +++ b/tests/forms_tests/tests/test_error_messages.py @@ -7,7 +7,8 @@ from django.forms import ( SplitDateTimeField, TimeField, URLField, ValidationError, utils, ) from django.template import Context, Template -from django.test import SimpleTestCase, TestCase +from django.test import SimpleTestCase, TestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango40Warning from django.utils.safestring import mark_safe from ..models import ChoiceModel @@ -301,9 +302,30 @@ class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): e = { 'required': 'REQUIRED', 'invalid_choice': '%(value)s IS INVALID CHOICE', - 'list': 'NOT A LIST OF VALUES', + 'invalid_list': 'NOT A LIST OF VALUES', } f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) self.assertFormErrors(['REQUIRED'], f.clean, '') self.assertFormErrors(['NOT A LIST OF VALUES'], f.clean, '3') self.assertFormErrors(['4 IS INVALID CHOICE'], f.clean, ['4']) + + +class DeprecationTests(TestCase, AssertFormErrorsMixin): + @ignore_warnings(category=RemovedInDjango40Warning) + def test_list_error_message(self): + f = ModelMultipleChoiceField( + queryset=ChoiceModel.objects.all(), + error_messages={'list': 'NOT A LIST OF VALUES'}, + ) + self.assertFormErrors(['NOT A LIST OF VALUES'], f.clean, '3') + + def test_list_error_message_warning(self): + msg = ( + "The 'list' error message key is deprecated in favor of " + "'invalid_list'." + ) + with self.assertRaisesMessage(RemovedInDjango40Warning, msg): + ModelMultipleChoiceField( + queryset=ChoiceModel.objects.all(), + error_messages={'list': 'NOT A LIST OF VALUES'}, + )