Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def to_choices_dict(choices):
"""
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([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2'}}
"""
Expand All @@ -142,15 +142,15 @@ def to_choices_dict(choices):
for choice in choices:
if not isinstance(choice, (list, tuple)):
# single choice
ret[choice] = choice
ret[choice] = str(choice)
else:
key, value = choice
if isinstance(value, (list, tuple)):
# grouped choices (category, sub choices)
ret[key] = to_choices_dict(value)
else:
# paired choice (key, display value)
ret[key] = value
ret[key] = str(value)
return ret


Expand Down Expand Up @@ -1421,7 +1421,7 @@ def to_internal_value(self, data):
def to_representation(self, value):
if value in ('', None):
return value
return self.choice_strings_to_values.get(str(value), value)
return self._choices.get(value, str(value))

def iter_options(self):
"""
Expand All @@ -1440,11 +1440,15 @@ def _set_choices(self, choices):
self.grouped_choices = to_choices_dict(choices)
self._choices = flatten_choices_dict(self.grouped_choices)

# Map the string representation of choices to the underlying value.
# Allows us to deal with eg. integer choices while supporting either
# integer or string input, but still get the correct datatype out.
# `self._choices` is a dictionary that maps the data value of a
# particular choice to its display representation. Here, we want to
# 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 = {
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)
Expand Down
39 changes: 20 additions & 19 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,12 +953,13 @@ class TestFilePathField(FieldValues):
"""

valid_inputs = {
__file__: __file__,
os.path.basename(__file__): os.path.abspath(__file__),
}
invalid_inputs = {
'wrong_path': ['"wrong_path" is not a valid path choice.']
}
outputs = {
os.path.abspath(__file__): os.path.basename(__file__),
}
field = serializers.FilePathField(
path=os.path.abspath(os.path.dirname(__file__))
Expand Down Expand Up @@ -1559,15 +1560,15 @@ class TestChoiceField(FieldValues):
Valid and invalid values for `ChoiceField`.
"""
valid_inputs = {
'poor': 'poor',
'medium': 'medium',
'good': 'good',
'Poor quality': 'poor',
'Medium quality': 'medium',
'Good quality': 'good',
}
invalid_inputs = {
'amazing': ['"amazing" is not a valid choice.']
}
outputs = {
'good': 'good',
'good': 'Good quality',
'': '',
'amazing': 'amazing',
}
Expand Down Expand Up @@ -1658,16 +1659,16 @@ class TestChoiceFieldWithType(FieldValues):
instead of a char type.
"""
valid_inputs = {
'1': 1,
3: 3,
'Poor quality': 1,
'Good quality': 3,
}
invalid_inputs = {
5: ['"5" is not a valid choice.'],
'abc': ['"abc" is not a valid choice.']
}
outputs = {
'1': 1,
1: 1
'1': '1',
1: 'Poor quality',
}
field = serializers.ChoiceField(
choices=[
Expand Down Expand Up @@ -1703,15 +1704,15 @@ class TestChoiceFieldWithGroupedChoices(FieldValues):
choices, rather than a list of pairs of (`value`, `description`).
"""
valid_inputs = {
'poor': 'poor',
'medium': 'medium',
'good': 'good',
'Poor quality': 'poor',
'Medium quality': 'medium',
'Good quality': 'good',
}
invalid_inputs = {
'awful': ['"awful" is not a valid choice.']
}
outputs = {
'good': 'good'
'good': 'Good quality'
}
field = serializers.ChoiceField(
choices=[
Expand All @@ -1733,15 +1734,15 @@ class TestChoiceFieldWithMixedChoices(FieldValues):
grouped.
"""
valid_inputs = {
'poor': 'poor',
'Poor quality': 'poor',
'medium': 'medium',
'good': 'good',
'Good quality': 'good',
}
invalid_inputs = {
'awful': ['"awful" is not a valid choice.']
}
outputs = {
'good': 'good'
'good': 'Good quality'
}
field = serializers.ChoiceField(
choices=[
Expand All @@ -1763,12 +1764,12 @@ class TestMultipleChoiceField(FieldValues):
"""
valid_inputs = {
(): set(),
('aircon',): {'aircon'},
('aircon', 'manual'): {'aircon', 'manual'},
('AirCon',): {'aircon'},
('AirCon', 'Manual drive'): {'aircon', 'manual'},
}
invalid_inputs = {
'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 = [
(['aircon', 'manual', 'incorrect'], {'aircon', 'manual', 'incorrect'})
Expand Down
4 changes: 2 additions & 2 deletions tests/test_model_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class Issue3674ChildModel(models.Model):
class UniqueChoiceModel(models.Model):
CHOICES = (
('choice1', 'choice 1'),
('choice2', 'choice 1'),
('choice2', 'choice 2'),
)

name = models.CharField(max_length=254, unique=True, choices=CHOICES)
Expand Down Expand Up @@ -1196,7 +1196,7 @@ class Meta:
fields = '__all__'

UniqueChoiceModel.objects.create(name='choice1')
serializer = TestUniqueChoiceSerializer(data={'name': 'choice1'})
serializer = TestUniqueChoiceSerializer(data={'name': 'choice 1'})
assert not serializer.is_valid()
assert serializer.errors == {'name': ['unique choice model with this name already exists.']}

Expand Down
4 changes: 2 additions & 2 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def test_choices(self):
Make sure a value for choices works as expected.
"""
f = serializers.ChoiceField(choices=self.CHOICES)
value = self.CHOICES[0][0]
value = self.CHOICES[0][1]
try:
f.to_internal_value(value)
except serializers.ValidationError:
Expand All @@ -218,7 +218,7 @@ 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]
value = self.CHOICES_NESTED[0][1][0][1]
try:
f.to_internal_value(value)
except serializers.ValidationError:
Expand Down