diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index c9c2c83a5..21ce259aa 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -375,6 +375,7 @@ Used by `ModelSerializer` to automatically generate fields if the corresponding - `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. - `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. - `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` +- `use_value` - If set to `True` then input and output values changed with choice field values. Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d2079d5d6..6d1df8ac4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1341,12 +1341,23 @@ class ChoiceField(Field): self.choices = flatten_choices_dict(self.grouped_choices) self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff) self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text) + self.use_value = kwargs.pop("use_value", False) # 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.choice_strings_to_values = { - six.text_type(key): key for key in self.choices.keys() + + if self.use_value: + self.choice_strings_to_values = { + six.text_type(value): key for key, value in self.choices.items() + } + else: + self.choice_strings_to_values = { + six.text_type(key): key for key in self.choices.keys() + } + + self.choice_values_to_strings = { + six.text_type(key): value for key, value in self.choices.items() } self.allow_blank = kwargs.pop('allow_blank', False) @@ -1365,6 +1376,8 @@ class ChoiceField(Field): def to_representation(self, value): if value in ('', None): return value + if self.use_value: + return self.choice_values_to_strings.get(six.text_type(value), value) return self.choice_strings_to_values.get(six.text_type(value), value) def iter_options(self): diff --git a/tests/test_fields.py b/tests/test_fields.py index 4173e6ab5..3fed39731 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1452,6 +1452,31 @@ class TestChoiceFieldWithType(FieldValues): ) +class TestChoiceFieldWithUseDone(FieldValues): + """ + Valid and invalid values for a `Choice` field that uses an integer type, + instead of a char type. + """ + valid_inputs = { + 'seller': 1, + 'client': 2, + } + invalid_inputs = { + "2": ['"2" is not a valid choice.'] + } + outputs = { + '1': 'seller', + 2: 'client' + } + field = serializers.ChoiceField( + choices=[ + (1, 'seller'), + (2, 'client') + ], + use_value=True + ) + + class TestChoiceFieldWithListChoices(FieldValues): """ Valid and invalid values for a `Choice` field that uses a flat list for the