fabro/PATCH.diff
Fabro e1a3d636de fabro(01KKTJ36PQ2Y1VDQVV50ECCG3K): solve (success)
Fabro-Run: 01KKTJ36PQ2Y1VDQVV50ECCG3K
Fabro-Completed: 3
Fabro-Checkpoint: 0630f73530

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-16 05:38:16 +00:00

143 lines
5.9 KiB
Diff

diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 9ce46e3f73..b7cc0e07ce 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -955,6 +955,7 @@ class CharField(Field):
return [
*super().check(**kwargs),
*self._check_max_length_attribute(**kwargs),
+ *self._check_choices_fit_max_length(**kwargs),
]
def _check_max_length_attribute(self, **kwargs):
@@ -978,6 +979,49 @@ class CharField(Field):
else:
return []
+ def _check_choices_fit_max_length(self, **kwargs):
+ if not self.choices or self.max_length is None:
+ return []
+
+ def get_choice_values(choices):
+ """Extract all choice values from choices list (handles grouped choices)."""
+ for choice in choices:
+ try:
+ choice_value, choice_display = choice
+ except (TypeError, ValueError):
+ # Skip if not a proper pair
+ continue
+
+ # Check if this is a group (second element is iterable but not string)
+ if isinstance(choice_display, (list, tuple)):
+ # It's a grouped choice, recurse
+ yield from get_choice_values(choice_display)
+ else:
+ # It's a regular choice
+ yield choice_value
+
+ errors = []
+ for choice_value in get_choice_values(self.choices):
+ # Convert to string to get the length (choice values are typically strings)
+ choice_str = str(choice_value) if choice_value is not None else ''
+ if len(choice_str) > self.max_length:
+ errors.append(
+ checks.Error(
+ "Field max_length is not large enough to fit the longest "
+ "choice value '{value}' (length {length}). "
+ "Increase max_length to at least {length}.".format(
+ value=choice_str,
+ length=len(choice_str),
+ ),
+ obj=self,
+ id='fields.E122',
+ )
+ )
+ # Only report the first error to avoid too many messages
+ break
+
+ return errors
+
def cast_db_type(self, connection):
if self.max_length is None:
return connection.ops.cast_char_field_without_max_length
diff --git a/tests/check_framework/test_model_checks.py b/tests/check_framework/test_model_checks.py
index 02c36dc610..574dba3435 100644
--- a/tests/check_framework/test_model_checks.py
+++ b/tests/check_framework/test_model_checks.py
@@ -358,3 +358,74 @@ class ConstraintNameTests(TestCase):
constraints = [constraint]
self.assertEqual(checks.run_checks(app_configs=apps.get_app_configs()), [])
+
+
+@isolate_apps('check_framework', attr_name='apps')
+@override_system_checks([checks.model_checks.check_all_models])
+class CharFieldChoicesTests(SimpleTestCase):
+ def test_charfield_choices_with_max_length_too_short(self):
+ """CharField max_length must be large enough for all choice values."""
+ class Model(models.Model):
+ status = models.CharField(
+ max_length=2,
+ choices=[
+ ('active', 'Active'),
+ ('inactive', 'Inactive'), # 'inactive' is 8 chars
+ ]
+ )
+
+ errors = checks.run_checks(app_configs=self.apps.get_app_configs())
+ self.assertEqual(len(errors), 1)
+ self.assertEqual(errors[0].id, 'fields.E122')
+ self.assertIn('max_length', errors[0].msg.lower())
+
+ def test_charfield_choices_with_sufficient_max_length(self):
+ """CharField max_length large enough should not raise error."""
+ class Model(models.Model):
+ status = models.CharField(
+ max_length=10,
+ choices=[
+ ('active', 'Active'),
+ ('inactive', 'Inactive'),
+ ]
+ )
+
+ errors = checks.run_checks(app_configs=self.apps.get_app_configs())
+ # Filter out unrelated checks, only look for E122
+ choice_errors = [e for e in errors if e.id == 'fields.E122']
+ self.assertEqual(len(choice_errors), 0)
+
+ def test_charfield_grouped_choices_with_max_length_too_short(self):
+ """CharField max_length check should work with grouped choices."""
+ class Model(models.Model):
+ status = models.CharField(
+ max_length=3,
+ choices=[
+ ('Group1', [
+ ('a', 'Option A'),
+ ('verylongvalue', 'Very Long Value'), # 14 chars
+ ]),
+ ]
+ )
+
+ errors = checks.run_checks(app_configs=self.apps.get_app_configs())
+ choice_errors = [e for e in errors if e.id == 'fields.E122']
+ self.assertEqual(len(choice_errors), 1)
+
+ def test_charfield_no_choices(self):
+ """CharField without choices should not raise E122."""
+ class Model(models.Model):
+ status = models.CharField(max_length=10)
+
+ errors = checks.run_checks(app_configs=self.apps.get_app_configs())
+ choice_errors = [e for e in errors if e.id == 'fields.E122']
+ self.assertEqual(len(choice_errors), 0)
+
+ def test_charfield_empty_choices(self):
+ """CharField with empty choices should not raise E122."""
+ class Model(models.Model):
+ status = models.CharField(max_length=10, choices=[])
+
+ errors = checks.run_checks(app_configs=self.apps.get_app_configs())
+ choice_errors = [e for e in errors if e.id == 'fields.E122']
+ self.assertEqual(len(choice_errors), 0)
\ No newline at end of file