Fixed #28748 -- Made model field choices check more strict for named groups.

This commit is contained in:
François Freitag 2017-11-29 22:33:24 -08:00 committed by Tim Graham
parent 8cdeb8acfc
commit f9844f4841
2 changed files with 84 additions and 22 deletions

View File

@ -241,8 +241,13 @@ class Field(RegisterLookupMixin):
return [] return []
def _check_choices(self): def _check_choices(self):
if self.choices: if not self.choices:
if isinstance(self.choices, str) or not is_iterable(self.choices): return []
def is_value(value):
return isinstance(value, str) or not is_iterable(value)
if is_value(self.choices):
return [ return [
checks.Error( checks.Error(
"'choices' must be an iterable (e.g., a list or tuple).", "'choices' must be an iterable (e.g., a list or tuple).",
@ -250,9 +255,32 @@ class Field(RegisterLookupMixin):
id='fields.E004', id='fields.E004',
) )
] ]
elif any(isinstance(choice, str) or
not is_iterable(choice) or len(choice) != 2 # Expect [group_name, [value, display]]
for choice in self.choices): for choices_group in self.choices:
try:
group_name, group_choices = choices_group
except ValueError:
# Containing non-pairs
break
try:
if not all(
is_value(value) and is_value(human_name)
for value, human_name in group_choices
):
break
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
# Special case: choices=['ab']
if isinstance(choices_group, str):
break
else:
return []
return [ return [
checks.Error( checks.Error(
"'choices' must be an iterable containing " "'choices' must be an iterable containing "
@ -261,10 +289,6 @@ class Field(RegisterLookupMixin):
id='fields.E005', id='fields.E005',
) )
] ]
else:
return []
else:
return []
def _check_db_index(self): def _check_db_index(self):
if self.db_index not in (None, True, False): if self.db_index not in (None, True, False):

View File

@ -210,6 +210,44 @@ class CharFieldTests(TestCase):
self.assertEqual(Model._meta.get_field('field').check(), []) self.assertEqual(Model._meta.get_field('field').check(), [])
def test_choices_named_group_non_pairs(self):
class Model(models.Model):
field = models.CharField(
max_length=10,
choices=[['knights', [['L', 'Lancelot', 'Du Lac']]]],
)
field = Model._meta.get_field('field')
self.assertEqual(field.check(), [
Error(
"'choices' must be an iterable containing (actual value, "
"human readable name) tuples.",
obj=field,
id='fields.E005',
),
])
def test_choices_named_group_bad_structure(self):
class Model(models.Model):
field = models.CharField(
max_length=10, choices=[
['knights', [
['Noble', [['G', 'Galahad']]],
['Combative', [['L', 'Lancelot']]],
]],
],
)
field = Model._meta.get_field('field')
self.assertEqual(field.check(), [
Error(
"'choices' must be an iterable containing (actual value, "
"human readable name) tuples.",
obj=field,
id='fields.E005',
),
])
def test_bad_db_index_value(self): def test_bad_db_index_value(self):
class Model(models.Model): class Model(models.Model):
field = models.CharField(max_length=10, db_index='bad') field = models.CharField(max_length=10, db_index='bad')