diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 0b7c9d864..86a65636e 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -320,6 +320,24 @@ class SlugRelatedField(RelatedField): return getattr(obj, self.slug_field) +class MultipleSlugRelatedField(SlugRelatedField): + def __init__(self, separator=None, **kwargs): + assert separator is not None, 'The `separator` argument is required.' + self.separator = separator + super(MultipleSlugRelatedField, self).__init__(**kwargs) + + def to_internal_value(self, data): + try: + return self.get_queryset().get(**dict(zip(self.slug_field, data.split(self.separator, len(self.slug_field))))) + except ObjectDoesNotExist: + self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data)) + except (TypeError, ValueError, AttributeError): + self.fail('invalid') + + def to_representation(self, obj): + return six.text_type(obj) + + class ManyRelatedField(Field): """ Relationships with `many=True` transparently get coerced into instead being diff --git a/tests/test_relations.py b/tests/test_relations.py index fbe176e24..c87d38f50 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -1,3 +1,4 @@ +import six from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset from django.core.exceptions import ImproperlyConfigured from django.utils.datastructures import MultiValueDict @@ -138,6 +139,40 @@ class TestSlugRelatedField(APISimpleTestCase): assert representation == self.instance.name +class TestMultipleSlugRelatedField(APISimpleTestCase): + def setUp(self): + self.queryset = MockQueryset([ + MockObject(pk=1, name='foo', code='hamster'), + MockObject(pk=2, name='bar', code='bazinga'), + MockObject(pk=3, name='bar', code='cheeseshop') + ]) + self.instance = self.queryset.items[2] + self.field = serializers.MultipleSlugRelatedField( + slug_field=('name', 'code'), queryset=self.queryset, separator='-' + ) + + def test_slug_related_lookup_exists(self): + data = '{}-{}'.format(self.instance.name, self.instance.code) + instance = self.field.to_internal_value(data) + assert instance is self.instance + + def test_slug_related_lookup_does_not_exist(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.field.to_internal_value('doesnotexist') + msg = excinfo.value.detail[0] + assert msg == 'Object with (\'name\', \'code\')=doesnotexist does not exist.' + + def test_slug_related_lookup_invalid_type(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.field.to_internal_value(BadType()) + msg = excinfo.value.detail[0] + assert msg == 'Invalid value.' + + def test_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == six.text_type(self.instance) + + class TestManyRelatedField(APISimpleTestCase): def setUp(self): self.instance = MockObject(pk=1, name='foo')