Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add URL path unquote to HyperlinkedRelatedField.to_internal_value #5078

Merged
merged 1 commit into from Apr 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion rest_framework/relations.py
Expand Up @@ -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 _

Expand Down Expand Up @@ -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:
Expand Down
29 changes: 28 additions & 1 deletion 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
Expand Down Expand Up @@ -87,17 +89,42 @@ def test_pk_representation(self):
assert representation == self.instance.pk.int


@override_settings(ROOT_URLCONF=[
url(r'^example/(?P<name>.+)/$', 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}

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):
Expand Down
14 changes: 13 additions & 1 deletion tests/test_routers.py
Expand Up @@ -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]
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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):
Expand Down