mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Add support for grouped choices.
This also adds support for mixing single and paired choices: ``` [ ('poor', 'Poor quality'), 'medium', ('good', 'Good quality'), ] ```
This commit is contained in:
parent
c091addb83
commit
56b3f19605
|
@ -5,6 +5,7 @@ import copy
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import inspect
|
import inspect
|
||||||
|
import itertools
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
@ -1098,17 +1099,8 @@ class ChoiceField(Field):
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, choices, **kwargs):
|
def __init__(self, choices, **kwargs):
|
||||||
# Allow either single or paired choices style:
|
flat_choices = [self.flatten_choice(c) for c in choices]
|
||||||
# choices = [1, 2, 3]
|
self.choices = OrderedDict(itertools.chain(*flat_choices))
|
||||||
# choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
|
|
||||||
pairs = [
|
|
||||||
isinstance(item, (list, tuple)) and len(item) == 2
|
|
||||||
for item in choices
|
|
||||||
]
|
|
||||||
if all(pairs):
|
|
||||||
self.choices = OrderedDict([(key, display_value) for key, display_value in choices])
|
|
||||||
else:
|
|
||||||
self.choices = OrderedDict([(item, item) for item in choices])
|
|
||||||
|
|
||||||
# Map the string representation of choices to the underlying value.
|
# Map the string representation of choices to the underlying value.
|
||||||
# Allows us to deal with eg. integer choices while supporting either
|
# Allows us to deal with eg. integer choices while supporting either
|
||||||
|
@ -1121,6 +1113,30 @@ class ChoiceField(Field):
|
||||||
|
|
||||||
super(ChoiceField, self).__init__(**kwargs)
|
super(ChoiceField, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def flatten_choice(self, choice):
|
||||||
|
"""
|
||||||
|
Convert a choices choice into a flat list of choices.
|
||||||
|
|
||||||
|
Returns a list of choices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Allow single, paired or grouped choices style:
|
||||||
|
# choices = [1, 2, 3]
|
||||||
|
# choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
|
||||||
|
# choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
|
||||||
|
if (not isinstance(choice, (list, tuple))):
|
||||||
|
# single choice
|
||||||
|
return [(choice, choice)]
|
||||||
|
else:
|
||||||
|
key, display_value = choice
|
||||||
|
if isinstance(display_value, (list, tuple)):
|
||||||
|
# grouped choices
|
||||||
|
sub_choices = [self.flatten_choice(c) for c in display_value]
|
||||||
|
return list(itertools.chain(*sub_choices))
|
||||||
|
else:
|
||||||
|
# paired choice
|
||||||
|
return [(key, display_value)]
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
if data == '' and self.allow_blank:
|
if data == '' and self.allow_blank:
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -1091,6 +1091,66 @@ class TestChoiceFieldWithListChoices(FieldValues):
|
||||||
field = serializers.ChoiceField(choices=('poor', 'medium', 'good'))
|
field = serializers.ChoiceField(choices=('poor', 'medium', 'good'))
|
||||||
|
|
||||||
|
|
||||||
|
class TestChoiceFieldWithGroupedChoices(FieldValues):
|
||||||
|
"""
|
||||||
|
Valid and invalid values for a `Choice` field that uses a grouped list for the
|
||||||
|
choices, rather than a list of pairs of (`value`, `description`).
|
||||||
|
"""
|
||||||
|
valid_inputs = {
|
||||||
|
'poor': 'poor',
|
||||||
|
'medium': 'medium',
|
||||||
|
'good': 'good',
|
||||||
|
}
|
||||||
|
invalid_inputs = {
|
||||||
|
'awful': ['"awful" is not a valid choice.']
|
||||||
|
}
|
||||||
|
outputs = {
|
||||||
|
'good': 'good'
|
||||||
|
}
|
||||||
|
field = serializers.ChoiceField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
'Category',
|
||||||
|
(
|
||||||
|
('poor', 'Poor quality'),
|
||||||
|
('medium', 'Medium quality'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
('good', 'Good quality'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestChoiceFieldWithMixedChoices(FieldValues):
|
||||||
|
"""
|
||||||
|
Valid and invalid values for a `Choice` field that uses a single paired or
|
||||||
|
grouped.
|
||||||
|
"""
|
||||||
|
valid_inputs = {
|
||||||
|
'poor': 'poor',
|
||||||
|
'medium': 'medium',
|
||||||
|
'good': 'good',
|
||||||
|
}
|
||||||
|
invalid_inputs = {
|
||||||
|
'awful': ['"awful" is not a valid choice.']
|
||||||
|
}
|
||||||
|
outputs = {
|
||||||
|
'good': 'good'
|
||||||
|
}
|
||||||
|
field = serializers.ChoiceField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
'Category',
|
||||||
|
(
|
||||||
|
('poor', 'Poor quality'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'medium',
|
||||||
|
('good', 'Good quality'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestMultipleChoiceField(FieldValues):
|
class TestMultipleChoiceField(FieldValues):
|
||||||
"""
|
"""
|
||||||
Valid and invalid values for `MultipleChoiceField`.
|
Valid and invalid values for `MultipleChoiceField`.
|
||||||
|
|
|
@ -141,6 +141,8 @@ class TestMaxValueValidatorValidation(TestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
# regression tests for issue: 1533
|
||||||
|
|
||||||
class TestChoiceFieldChoicesValidate(TestCase):
|
class TestChoiceFieldChoicesValidate(TestCase):
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(0, 'Small'),
|
(0, 'Small'),
|
||||||
|
@ -148,6 +150,8 @@ class TestChoiceFieldChoicesValidate(TestCase):
|
||||||
(2, 'Large'),
|
(2, 'Large'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SINGLE_CHOICES = [0, 1, 2]
|
||||||
|
|
||||||
CHOICES_NESTED = [
|
CHOICES_NESTED = [
|
||||||
('Category', (
|
('Category', (
|
||||||
(1, 'First'),
|
(1, 'First'),
|
||||||
|
@ -157,6 +161,15 @@ class TestChoiceFieldChoicesValidate(TestCase):
|
||||||
(4, 'Fourth'),
|
(4, 'Fourth'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MIXED_CHOICES = [
|
||||||
|
('Category', (
|
||||||
|
(1, 'First'),
|
||||||
|
(2, 'Second'),
|
||||||
|
)),
|
||||||
|
3,
|
||||||
|
(4, 'Fourth'),
|
||||||
|
]
|
||||||
|
|
||||||
def test_choices(self):
|
def test_choices(self):
|
||||||
"""
|
"""
|
||||||
Make sure a value for choices works as expected.
|
Make sure a value for choices works as expected.
|
||||||
|
@ -168,6 +181,39 @@ class TestChoiceFieldChoicesValidate(TestCase):
|
||||||
except serializers.ValidationError:
|
except serializers.ValidationError:
|
||||||
self.fail("Value %s does not validate" % str(value))
|
self.fail("Value %s does not validate" % str(value))
|
||||||
|
|
||||||
|
def test_single_choices(self):
|
||||||
|
"""
|
||||||
|
Make sure a single value for choices works as expected.
|
||||||
|
"""
|
||||||
|
f = serializers.ChoiceField(choices=self.SINGLE_CHOICES)
|
||||||
|
value = self.SINGLE_CHOICES[0]
|
||||||
|
try:
|
||||||
|
f.to_internal_value(value)
|
||||||
|
except serializers.ValidationError:
|
||||||
|
self.fail("Value %s does not validate" % str(value))
|
||||||
|
|
||||||
|
def test_nested_choices(self):
|
||||||
|
"""
|
||||||
|
Make sure a nested value for choices works as expected.
|
||||||
|
"""
|
||||||
|
f = serializers.ChoiceField(choices=self.CHOICES_NESTED)
|
||||||
|
value = self.CHOICES_NESTED[0][1][0][0]
|
||||||
|
try:
|
||||||
|
f.to_internal_value(value)
|
||||||
|
except serializers.ValidationError:
|
||||||
|
self.fail("Value %s does not validate" % str(value))
|
||||||
|
|
||||||
|
def test_mixed_choices(self):
|
||||||
|
"""
|
||||||
|
Make sure mixed values for choices works as expected.
|
||||||
|
"""
|
||||||
|
f = serializers.ChoiceField(choices=self.MIXED_CHOICES)
|
||||||
|
value = self.MIXED_CHOICES[1]
|
||||||
|
try:
|
||||||
|
f.to_internal_value(value)
|
||||||
|
except serializers.ValidationError:
|
||||||
|
self.fail("Value %s does not validate" % str(value))
|
||||||
|
|
||||||
|
|
||||||
class RegexSerializer(serializers.Serializer):
|
class RegexSerializer(serializers.Serializer):
|
||||||
pin = serializers.CharField(
|
pin = serializers.CharField(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user