diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 9ce46e3f73..b55d41bb85 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -257,6 +257,7 @@ class Field(RegisterLookupMixin): ) ] + choice_max_length = 0 # Expect [group_name, [value, display]] for choices_group in self.choices: try: @@ -270,16 +271,32 @@ class Field(RegisterLookupMixin): for value, human_name in group_choices ): break + if self.max_length is not None and group_choices: + choice_max_length = max( + choice_max_length, + *(len(value) for value, _ in group_choices if isinstance(value, str)), + ) except (TypeError, ValueError): # No groups, choices in the form [value, display] value, human_name = group_name, group_choices if not is_value(value) or not is_value(human_name): break + if self.max_length is not None and isinstance(value, str): + choice_max_length = max(choice_max_length, len(value)) # Special case: choices=['ab'] if isinstance(choices_group, str): break else: + if self.max_length is not None and choice_max_length > self.max_length: + return [ + checks.Error( + "'max_length' is too small to fit the longest value " + "in 'choices' (%d characters)." % choice_max_length, + obj=self, + id='fields.E009', + ), + ] return [] return [ diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 4da932720e..f147d9dc0b 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -138,6 +138,8 @@ Model fields * **fields.E006**: ``db_index`` must be ``None``, ``True`` or ``False``. * **fields.E007**: Primary keys must not have ``null=True``. * **fields.E008**: All ``validators`` must be callable. +* **fields.E009**: ``max_length`` is too small to fit the longest value in + ``choices`` (```` characters). * **fields.E100**: ``AutoField``\s must set primary_key=True. * **fields.E110**: ``BooleanField``\s do not accept null values. *This check appeared before support for null values was added in Django 2.1.* diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index 5bb1847a70..2ef0907a6a 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -304,6 +304,32 @@ class CharFieldTests(SimpleTestCase): self.assertEqual(Model._meta.get_field('field').check(), []) + def test_choices_in_max_length(self): + class Model(models.Model): + field = models.CharField( + max_length=2, choices=[ + ('ABC', 'Value Too Long!'), ('OK', 'Good') + ], + ) + group = models.CharField( + max_length=2, choices=[ + ('Nested', [('OK', 'Good'), ('Longer', 'Longer')]), + ('Grouped', [('Bad', 'Bad')]), + ], + ) + + for name, choice_max_length in (('field', 3), ('group', 6)): + with self.subTest(name): + field = Model._meta.get_field(name) + self.assertEqual(field.check(), [ + Error( + "'max_length' is too small to fit the longest value " + "in 'choices' (%d characters)." % choice_max_length, + obj=field, + id='fields.E009', + ), + ]) + def test_bad_db_index_value(self): class Model(models.Model): field = models.CharField(max_length=10, db_index='bad')