Skip to content

Commit ee1a9fc

Browse files
authored
Merge pull request #5078 from rooterkyberian/issue-4748
add URL path unquote to HyperlinkedRelatedField.to_internal_value
2 parents 66e015c + 5e185aa commit ee1a9fc

File tree

3 files changed

+46
-3
lines changed

3 files changed

+46
-3
lines changed

rest_framework/relations.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from django.db.models import Manager
88
from django.db.models.query import QuerySet
99
from django.utils import six
10-
from django.utils.encoding import python_2_unicode_compatible, smart_text
10+
from django.utils.encoding import (
11+
python_2_unicode_compatible, smart_text, uri_to_iri
12+
)
1113
from django.utils.six.moves.urllib import parse as urlparse
1214
from django.utils.translation import ugettext_lazy as _
1315

@@ -324,6 +326,8 @@ def to_internal_value(self, data):
324326
if data.startswith(prefix):
325327
data = '/' + data[len(prefix):]
326328

329+
data = uri_to_iri(data)
330+
327331
try:
328332
match = resolve(data)
329333
except Resolver404:

tests/test_relations.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import uuid
22

33
import pytest
4+
from django.conf.urls import url
45
from django.core.exceptions import ImproperlyConfigured
6+
from django.test import override_settings
57
from django.utils.datastructures import MultiValueDict
68

79
from rest_framework import serializers
@@ -87,17 +89,42 @@ def test_pk_representation(self):
8789
assert representation == self.instance.pk.int
8890

8991

92+
@override_settings(ROOT_URLCONF=[
93+
url(r'^example/(?P<name>.+)/$', lambda: None, name='example'),
94+
])
9095
class TestHyperlinkedRelatedField(APISimpleTestCase):
9196
def setUp(self):
97+
self.queryset = MockQueryset([
98+
MockObject(pk=1, name='foobar'),
99+
MockObject(pk=2, name='baz qux'),
100+
])
92101
self.field = serializers.HyperlinkedRelatedField(
93-
view_name='example', read_only=True)
102+
view_name='example',
103+
lookup_field='name',
104+
lookup_url_kwarg='name',
105+
queryset=self.queryset,
106+
)
94107
self.field.reverse = mock_reverse
95108
self.field._context = {'request': True}
96109

97110
def test_representation_unsaved_object_with_non_nullable_pk(self):
98111
representation = self.field.to_representation(MockObject(pk=''))
99112
assert representation is None
100113

114+
def test_hyperlinked_related_lookup_exists(self):
115+
instance = self.field.to_internal_value('http://example.org/example/foobar/')
116+
assert instance is self.queryset.items[0]
117+
118+
def test_hyperlinked_related_lookup_url_encoded_exists(self):
119+
instance = self.field.to_internal_value('http://example.org/example/baz%20qux/')
120+
assert instance is self.queryset.items[1]
121+
122+
def test_hyperlinked_related_lookup_does_not_exist(self):
123+
with pytest.raises(serializers.ValidationError) as excinfo:
124+
self.field.to_internal_value('http://example.org/example/doesnotexist/')
125+
msg = excinfo.value.detail[0]
126+
assert msg == 'Invalid hyperlink - Object does not exist.'
127+
101128

102129
class TestHyperlinkedIdentityField(APISimpleTestCase):
103130
def setUp(self):

tests/test_routers.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class TestCustomLookupFields(TestCase):
156156
"""
157157
def setUp(self):
158158
RouterTestModel.objects.create(uuid='123', text='foo bar')
159+
RouterTestModel.objects.create(uuid='a b', text='baz qux')
159160

160161
def test_custom_lookup_field_route(self):
161162
detail_route = notes_router.urls[-1]
@@ -164,12 +165,19 @@ def test_custom_lookup_field_route(self):
164165

165166
def test_retrieve_lookup_field_list_view(self):
166167
response = self.client.get('/example/notes/')
167-
assert response.data == [{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}]
168+
assert response.data == [
169+
{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"},
170+
{"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"},
171+
]
168172

169173
def test_retrieve_lookup_field_detail_view(self):
170174
response = self.client.get('/example/notes/123/')
171175
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
172176

177+
def test_retrieve_lookup_field_url_encoded_detail_view_(self):
178+
response = self.client.get('/example/notes/a%20b/')
179+
assert response.data == {"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"}
180+
173181

174182
class TestLookupValueRegex(TestCase):
175183
"""
@@ -211,6 +219,10 @@ def test_retrieve_lookup_url_kwarg_detail_view(self):
211219
response = self.client.get('/example2/notes/fo/')
212220
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
213221

222+
def test_retrieve_lookup_url_encoded_kwarg_detail_view(self):
223+
response = self.client.get('/example2/notes/foo%20bar/')
224+
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
225+
214226

215227
class TestTrailingSlashIncluded(TestCase):
216228
def setUp(self):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy