mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-29 01:20:02 +03:00
Modified serializers.ChoiceField
behavior convert display_name
--> key
during deserialization and `key` --> `display_name` during serialization. Perhaps I misunderstand the intended behavior of `fields.ChoiceField`, but I noticed recently that the although every `ChoiceField` instance stores the full list of choices with which it is initialized (a list of pairs of `key` and `display_name`), I couldn't figure out when it ever uses a choice's `display_name`. As a result, I found myself manually converting user input values, which matched choice `display_name`s, to the underlying data values that match the choice `key`s. This commit modifies the behavior of `ChoiceField` instances so that they automatically deserialize user input into the corresponding underlying data value and do the reverse for serialization.
This commit is contained in:
parent
0cc09f0c0d
commit
85881488bb
|
@ -130,7 +130,7 @@ def to_choices_dict(choices):
|
||||||
"""
|
"""
|
||||||
Convert choices into key/value dicts.
|
Convert choices into key/value dicts.
|
||||||
|
|
||||||
to_choices_dict([1]) -> {1: 1}
|
to_choices_dict([1]) -> {1: '1'}
|
||||||
to_choices_dict([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
|
to_choices_dict([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
|
||||||
to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2'}}
|
to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2'}}
|
||||||
"""
|
"""
|
||||||
|
@ -142,7 +142,7 @@ def to_choices_dict(choices):
|
||||||
for choice in choices:
|
for choice in choices:
|
||||||
if not isinstance(choice, (list, tuple)):
|
if not isinstance(choice, (list, tuple)):
|
||||||
# single choice
|
# single choice
|
||||||
ret[choice] = choice
|
ret[choice] = str(choice)
|
||||||
else:
|
else:
|
||||||
key, value = choice
|
key, value = choice
|
||||||
if isinstance(value, (list, tuple)):
|
if isinstance(value, (list, tuple)):
|
||||||
|
@ -150,7 +150,7 @@ def to_choices_dict(choices):
|
||||||
ret[key] = to_choices_dict(value)
|
ret[key] = to_choices_dict(value)
|
||||||
else:
|
else:
|
||||||
# paired choice (key, display value)
|
# paired choice (key, display value)
|
||||||
ret[key] = value
|
ret[key] = str(value)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -1421,7 +1421,7 @@ class ChoiceField(Field):
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
if value in ('', None):
|
if value in ('', None):
|
||||||
return value
|
return value
|
||||||
return self.choice_strings_to_values.get(str(value), value)
|
return self._choices.get(value, str(value))
|
||||||
|
|
||||||
def iter_options(self):
|
def iter_options(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1440,11 +1440,15 @@ class ChoiceField(Field):
|
||||||
self.grouped_choices = to_choices_dict(choices)
|
self.grouped_choices = to_choices_dict(choices)
|
||||||
self._choices = flatten_choices_dict(self.grouped_choices)
|
self._choices = flatten_choices_dict(self.grouped_choices)
|
||||||
|
|
||||||
# Map the string representation of choices to the underlying value.
|
# `self._choices` is a dictionary that maps the data value of a
|
||||||
# Allows us to deal with eg. integer choices while supporting either
|
# particular choice to its display representation. Here, we want to
|
||||||
# integer or string input, but still get the correct datatype out.
|
# reverse that dictionary so that when a user supplies a choice's
|
||||||
|
# display value as input, we can look up the underlying data value to
|
||||||
|
# which we should deserialize it. All of the values in the `OrderedDict`
|
||||||
|
# returned by `to_choices_dict` should be strings, so `display` should
|
||||||
|
# always be a string.
|
||||||
self.choice_strings_to_values = {
|
self.choice_strings_to_values = {
|
||||||
str(key): key for key in self.choices
|
display_name: key for key, display_name in self.choices.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
choices = property(_get_choices, _set_choices)
|
choices = property(_get_choices, _set_choices)
|
||||||
|
|
|
@ -953,12 +953,13 @@ class TestFilePathField(FieldValues):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
valid_inputs = {
|
valid_inputs = {
|
||||||
__file__: __file__,
|
os.path.basename(__file__): os.path.abspath(__file__),
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
'wrong_path': ['"wrong_path" is not a valid path choice.']
|
'wrong_path': ['"wrong_path" is not a valid path choice.']
|
||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
|
os.path.abspath(__file__): os.path.basename(__file__),
|
||||||
}
|
}
|
||||||
field = serializers.FilePathField(
|
field = serializers.FilePathField(
|
||||||
path=os.path.abspath(os.path.dirname(__file__))
|
path=os.path.abspath(os.path.dirname(__file__))
|
||||||
|
@ -1559,15 +1560,15 @@ class TestChoiceField(FieldValues):
|
||||||
Valid and invalid values for `ChoiceField`.
|
Valid and invalid values for `ChoiceField`.
|
||||||
"""
|
"""
|
||||||
valid_inputs = {
|
valid_inputs = {
|
||||||
'poor': 'poor',
|
'Poor quality': 'poor',
|
||||||
'medium': 'medium',
|
'Medium quality': 'medium',
|
||||||
'good': 'good',
|
'Good quality': 'good',
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
'amazing': ['"amazing" is not a valid choice.']
|
'amazing': ['"amazing" is not a valid choice.']
|
||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
'good': 'good',
|
'good': 'Good quality',
|
||||||
'': '',
|
'': '',
|
||||||
'amazing': 'amazing',
|
'amazing': 'amazing',
|
||||||
}
|
}
|
||||||
|
@ -1658,16 +1659,16 @@ class TestChoiceFieldWithType(FieldValues):
|
||||||
instead of a char type.
|
instead of a char type.
|
||||||
"""
|
"""
|
||||||
valid_inputs = {
|
valid_inputs = {
|
||||||
'1': 1,
|
'Poor quality': 1,
|
||||||
3: 3,
|
'Good quality': 3,
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
5: ['"5" is not a valid choice.'],
|
5: ['"5" is not a valid choice.'],
|
||||||
'abc': ['"abc" is not a valid choice.']
|
'abc': ['"abc" is not a valid choice.']
|
||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
'1': 1,
|
'1': '1',
|
||||||
1: 1
|
1: 'Poor quality',
|
||||||
}
|
}
|
||||||
field = serializers.ChoiceField(
|
field = serializers.ChoiceField(
|
||||||
choices=[
|
choices=[
|
||||||
|
@ -1703,15 +1704,15 @@ class TestChoiceFieldWithGroupedChoices(FieldValues):
|
||||||
choices, rather than a list of pairs of (`value`, `description`).
|
choices, rather than a list of pairs of (`value`, `description`).
|
||||||
"""
|
"""
|
||||||
valid_inputs = {
|
valid_inputs = {
|
||||||
'poor': 'poor',
|
'Poor quality': 'poor',
|
||||||
'medium': 'medium',
|
'Medium quality': 'medium',
|
||||||
'good': 'good',
|
'Good quality': 'good',
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
'awful': ['"awful" is not a valid choice.']
|
'awful': ['"awful" is not a valid choice.']
|
||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
'good': 'good'
|
'good': 'Good quality'
|
||||||
}
|
}
|
||||||
field = serializers.ChoiceField(
|
field = serializers.ChoiceField(
|
||||||
choices=[
|
choices=[
|
||||||
|
@ -1733,15 +1734,15 @@ class TestChoiceFieldWithMixedChoices(FieldValues):
|
||||||
grouped.
|
grouped.
|
||||||
"""
|
"""
|
||||||
valid_inputs = {
|
valid_inputs = {
|
||||||
'poor': 'poor',
|
'Poor quality': 'poor',
|
||||||
'medium': 'medium',
|
'medium': 'medium',
|
||||||
'good': 'good',
|
'Good quality': 'good',
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
'awful': ['"awful" is not a valid choice.']
|
'awful': ['"awful" is not a valid choice.']
|
||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
'good': 'good'
|
'good': 'Good quality'
|
||||||
}
|
}
|
||||||
field = serializers.ChoiceField(
|
field = serializers.ChoiceField(
|
||||||
choices=[
|
choices=[
|
||||||
|
@ -1763,12 +1764,12 @@ class TestMultipleChoiceField(FieldValues):
|
||||||
"""
|
"""
|
||||||
valid_inputs = {
|
valid_inputs = {
|
||||||
(): set(),
|
(): set(),
|
||||||
('aircon',): {'aircon'},
|
('AirCon',): {'aircon'},
|
||||||
('aircon', 'manual'): {'aircon', 'manual'},
|
('AirCon', 'Manual drive'): {'aircon', 'manual'},
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
'abc': ['Expected a list of items but got type "str".'],
|
'abc': ['Expected a list of items but got type "str".'],
|
||||||
('aircon', 'incorrect'): ['"incorrect" is not a valid choice.']
|
('AirCon', 'incorrect'): ['"incorrect" is not a valid choice.']
|
||||||
}
|
}
|
||||||
outputs = [
|
outputs = [
|
||||||
(['aircon', 'manual', 'incorrect'], {'aircon', 'manual', 'incorrect'})
|
(['aircon', 'manual', 'incorrect'], {'aircon', 'manual', 'incorrect'})
|
||||||
|
|
|
@ -107,7 +107,7 @@ class Issue3674ChildModel(models.Model):
|
||||||
class UniqueChoiceModel(models.Model):
|
class UniqueChoiceModel(models.Model):
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
('choice1', 'choice 1'),
|
('choice1', 'choice 1'),
|
||||||
('choice2', 'choice 1'),
|
('choice2', 'choice 2'),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=254, unique=True, choices=CHOICES)
|
name = models.CharField(max_length=254, unique=True, choices=CHOICES)
|
||||||
|
@ -1196,7 +1196,7 @@ class Test5004UniqueChoiceField(TestCase):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
UniqueChoiceModel.objects.create(name='choice1')
|
UniqueChoiceModel.objects.create(name='choice1')
|
||||||
serializer = TestUniqueChoiceSerializer(data={'name': 'choice1'})
|
serializer = TestUniqueChoiceSerializer(data={'name': 'choice 1'})
|
||||||
assert not serializer.is_valid()
|
assert not serializer.is_valid()
|
||||||
assert serializer.errors == {'name': ['unique choice model with this name already exists.']}
|
assert serializer.errors == {'name': ['unique choice model with this name already exists.']}
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ class TestChoiceFieldChoicesValidate(TestCase):
|
||||||
Make sure a value for choices works as expected.
|
Make sure a value for choices works as expected.
|
||||||
"""
|
"""
|
||||||
f = serializers.ChoiceField(choices=self.CHOICES)
|
f = serializers.ChoiceField(choices=self.CHOICES)
|
||||||
value = self.CHOICES[0][0]
|
value = self.CHOICES[0][1]
|
||||||
try:
|
try:
|
||||||
f.to_internal_value(value)
|
f.to_internal_value(value)
|
||||||
except serializers.ValidationError:
|
except serializers.ValidationError:
|
||||||
|
@ -218,7 +218,7 @@ class TestChoiceFieldChoicesValidate(TestCase):
|
||||||
Make sure a nested value for choices works as expected.
|
Make sure a nested value for choices works as expected.
|
||||||
"""
|
"""
|
||||||
f = serializers.ChoiceField(choices=self.CHOICES_NESTED)
|
f = serializers.ChoiceField(choices=self.CHOICES_NESTED)
|
||||||
value = self.CHOICES_NESTED[0][1][0][0]
|
value = self.CHOICES_NESTED[0][1][0][1]
|
||||||
try:
|
try:
|
||||||
f.to_internal_value(value)
|
f.to_internal_value(value)
|
||||||
except serializers.ValidationError:
|
except serializers.ValidationError:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user