From 5e185aa26b5a1f4f87eaac610bec85bcdb118bbb Mon Sep 17 00:00:00 2001 From: Maciej Urbanski Date: Fri, 14 Apr 2017 01:56:44 +0200 Subject: [PATCH] add URL path unquote to HyperlinkedRelatedField.to_internal_value --- rest_framework/relations.py | 6 +++++- tests/test_relations.py | 29 ++++++++++++++++++++++++++++- tests/test_routers.py | 14 +++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index eac9647b00..54e67cd16c 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -7,7 +7,9 @@ from django.db.models import Manager from django.db.models.query import QuerySet from django.utils import six -from django.utils.encoding import python_2_unicode_compatible, smart_text +from django.utils.encoding import ( + python_2_unicode_compatible, smart_text, uri_to_iri +) from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ @@ -324,6 +326,8 @@ def to_internal_value(self, data): if data.startswith(prefix): data = '/' + data[len(prefix):] + data = uri_to_iri(data) + try: match = resolve(data) except Resolver404: diff --git a/tests/test_relations.py b/tests/test_relations.py index a070ad6dec..c903ee5575 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -1,7 +1,9 @@ import uuid import pytest +from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured +from django.test import override_settings from django.utils.datastructures import MultiValueDict from rest_framework import serializers @@ -87,10 +89,21 @@ def test_pk_representation(self): assert representation == self.instance.pk.int +@override_settings(ROOT_URLCONF=[ + url(r'^example/(?P.+)/$', lambda: None, name='example'), +]) class TestHyperlinkedRelatedField(APISimpleTestCase): def setUp(self): + self.queryset = MockQueryset([ + MockObject(pk=1, name='foobar'), + MockObject(pk=2, name='baz qux'), + ]) self.field = serializers.HyperlinkedRelatedField( - view_name='example', read_only=True) + view_name='example', + lookup_field='name', + lookup_url_kwarg='name', + queryset=self.queryset, + ) self.field.reverse = mock_reverse self.field._context = {'request': True} @@ -98,6 +111,20 @@ def test_representation_unsaved_object_with_non_nullable_pk(self): representation = self.field.to_representation(MockObject(pk='')) assert representation is None + def test_hyperlinked_related_lookup_exists(self): + instance = self.field.to_internal_value('http://example.org/example/foobar/') + assert instance is self.queryset.items[0] + + def test_hyperlinked_related_lookup_url_encoded_exists(self): + instance = self.field.to_internal_value('http://example.org/example/baz%20qux/') + assert instance is self.queryset.items[1] + + def test_hyperlinked_related_lookup_does_not_exist(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.field.to_internal_value('http://example.org/example/doesnotexist/') + msg = excinfo.value.detail[0] + assert msg == 'Invalid hyperlink - Object does not exist.' + class TestHyperlinkedIdentityField(APISimpleTestCase): def setUp(self): diff --git a/tests/test_routers.py b/tests/test_routers.py index dc3df2e7ba..97f43b91a0 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -156,6 +156,7 @@ class TestCustomLookupFields(TestCase): """ def setUp(self): RouterTestModel.objects.create(uuid='123', text='foo bar') + RouterTestModel.objects.create(uuid='a b', text='baz qux') def test_custom_lookup_field_route(self): detail_route = notes_router.urls[-1] @@ -164,12 +165,19 @@ def test_custom_lookup_field_route(self): def test_retrieve_lookup_field_list_view(self): response = self.client.get('/example/notes/') - assert response.data == [{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}] + assert response.data == [ + {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}, + {"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"}, + ] def test_retrieve_lookup_field_detail_view(self): response = self.client.get('/example/notes/123/') assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"} + def test_retrieve_lookup_field_url_encoded_detail_view_(self): + response = self.client.get('/example/notes/a%20b/') + assert response.data == {"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"} + class TestLookupValueRegex(TestCase): """ @@ -211,6 +219,10 @@ def test_retrieve_lookup_url_kwarg_detail_view(self): response = self.client.get('/example2/notes/fo/') assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"} + def test_retrieve_lookup_url_encoded_kwarg_detail_view(self): + response = self.client.get('/example2/notes/foo%20bar/') + assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"} + class TestTrailingSlashIncluded(TestCase): def setUp(self):