mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Fabro-Run: 01KKTJ36PQ2Y1VDQVV50ECCG3K
Fabro-Completed: 3
Fabro-Checkpoint: 0630f73530
⚒️ Generated with [Fabro](https://fabro.sh)
143 lines
5.9 KiB
Diff
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
|