diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 18637f21c..b335a2b1a 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -364,6 +364,18 @@ As with `ChoiceField`, both the `allow_blank` and `allow_null` options are valid --- +# Mapping field + +## MappingField + +A field that converts value with given mapping dict. + + **Signature:** `MappingField(mapping)` + +- `mapping` - A dict of valid values that should be converted both directions. + +--- + # File upload fields #### Parsers and file uploads. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 7960295bd..ff771128d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1165,6 +1165,35 @@ class MultipleChoiceField(ChoiceField): ]) +# Mapping types... + +class MappingField(Field): + default_error_messages = { + 'key_not_found': _('"{value}" not found in "mapping" keys'), + 'value_not_found': _('"{value}" not found in "mapping" values') + } + + def __init__(self, mapping, **kwargs): + super(MappingField, self).__init__(**kwargs) + + assert isinstance(mapping, dict), '"mapping" should be a dictionary' + for k, v in mapping.items(): + assert isinstance(k, (str, int)) and isinstance(v, (str, int)), '"mapping" can contain only str or int' + + self.mapping = mapping + self.reverse_mapping = {v: k for k, v in mapping.iteritems()} + + def to_representation(self, value): + if value in self.mapping: + return self.mapping[value] + self.fail('key_not_found', value=value) + + def to_internal_value(self, data): + if data in self.reverse_mapping: + return self.reverse_mapping[data] + self.fail('value_not_found', value=data) + + # File types... class FileField(Field): diff --git a/tests/test_fields.py b/tests/test_fields.py index 76e6d9d60..c13dc4c61 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1125,6 +1125,38 @@ class TestMultipleChoiceField(FieldValues): assert field.get_value(QueryDict({})) == rest_framework.fields.empty +# Mapping types... + +class TestMappingFieldWithIntKeys(FieldValues): + """ + Valid and invalid values for `MappingField`. + """ + valid_inputs = { + 'one': 1, + 'two': 2, + 3: 'three', + 4: 'four' + } + invalid_inputs = { + 5: ['"5" not found in "mapping" values'], + 'abc': ['"abc" not found in "mapping" values'] + } + outputs = { + 1: 'one', + 2: 'two', + 'three': 3, + 'four': 4 + } + field = serializers.MappingField( + mapping={ + 1: 'one', + 2: 'two', + 'three': 3, + 'four': 4 + } + ) + + # File serializers... class MockFile: