Skip to content

Commit a90dbe3

Browse files
committed
Merge pull request #2724 from delinhabit/namespaced-versioning-non-api-reversal
Handle reversal of non-API view_name in HyperLinkedRelatedField
2 parents f7cd7a1 + fac2785 commit a90dbe3

File tree

3 files changed

+78
-2
lines changed

3 files changed

+78
-2
lines changed

rest_framework/reverse.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
from __future__ import unicode_literals
55
from django.core.urlresolvers import reverse as django_reverse
6+
from django.core.urlresolvers import NoReverseMatch
67
from django.utils import six
78
from django.utils.functional import lazy
89

@@ -15,7 +16,13 @@ def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra
1516
"""
1617
scheme = getattr(request, 'versioning_scheme', None)
1718
if scheme is not None:
18-
return scheme.reverse(viewname, args, kwargs, request, format, **extra)
19+
try:
20+
return scheme.reverse(viewname, args, kwargs, request, format, **extra)
21+
except NoReverseMatch:
22+
# In case the versioning scheme reversal fails, fallback to the
23+
# default implementation
24+
pass
25+
1926
return _reverse(viewname, args, kwargs, request, format, **extra)
2027

2128

tests/test_reverse.py

+27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22
from django.conf.urls import patterns, url
3+
from django.core.urlresolvers import NoReverseMatch
34
from django.test import TestCase
45
from rest_framework.reverse import reverse
56
from rest_framework.test import APIRequestFactory
@@ -16,6 +17,18 @@ def null_view(request):
1617
)
1718

1819

20+
class MockVersioningScheme(object):
21+
22+
def __init__(self, raise_error=False):
23+
self.raise_error = raise_error
24+
25+
def reverse(self, *args, **kwargs):
26+
if self.raise_error:
27+
raise NoReverseMatch()
28+
29+
return 'http://scheme-reversed/view'
30+
31+
1932
class ReverseTests(TestCase):
2033
"""
2134
Tests for fully qualified URLs when using `reverse`.
@@ -26,3 +39,17 @@ def test_reversed_urls_are_fully_qualified(self):
2639
request = factory.get('/view')
2740
url = reverse('view', request=request)
2841
self.assertEqual(url, 'http://testserver/view')
42+
43+
def test_reverse_with_versioning_scheme(self):
44+
request = factory.get('/view')
45+
request.versioning_scheme = MockVersioningScheme()
46+
47+
url = reverse('view', request=request)
48+
self.assertEqual(url, 'http://scheme-reversed/view')
49+
50+
def test_reverse_with_versioning_scheme_fallback_to_default_on_error(self):
51+
request = factory.get('/view')
52+
request.versioning_scheme = MockVersioningScheme(raise_error=True)
53+
54+
url = reverse('view', request=request)
55+
self.assertEqual(url, 'http://testserver/view')

tests/test_versioning.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from rest_framework.reverse import reverse
88
from rest_framework.test import APIRequestFactory, APITestCase
99
from rest_framework.versioning import NamespaceVersioning
10+
from rest_framework.relations import PKOnlyObject
1011
import pytest
1112

1213

@@ -234,7 +235,7 @@ class FakeResolverMatch:
234235

235236
class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
236237
included = [
237-
url(r'^namespaced/(?P<pk>\d+)/$', dummy_view, name='namespaced'),
238+
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
238239
]
239240

240241
urlpatterns = [
@@ -262,3 +263,44 @@ def test_bug_2489(self):
262263
assert self.field.to_internal_value('/v1/namespaced/3/') == 'object 3'
263264
with pytest.raises(serializers.ValidationError):
264265
self.field.to_internal_value('/v2/namespaced/3/')
266+
267+
268+
class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(UsingURLPatterns, APITestCase):
269+
included = [
270+
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
271+
]
272+
273+
urlpatterns = [
274+
url(r'^v1/', include(included, namespace='v1')),
275+
url(r'^v2/', include(included, namespace='v2')),
276+
url(r'^non-api/(?P<pk>\d+)/$', dummy_pk_view, name='non-api-view')
277+
]
278+
279+
def _create_field(self, view_name, version):
280+
request = factory.get("/")
281+
request.versioning_scheme = NamespaceVersioning()
282+
request.version = version
283+
284+
field = serializers.HyperlinkedRelatedField(
285+
view_name=view_name,
286+
read_only=True)
287+
field._context = {'request': request}
288+
return field
289+
290+
def test_api_url_is_properly_reversed_with_v1(self):
291+
field = self._create_field('namespaced', 'v1')
292+
assert field.to_representation(PKOnlyObject(3)) == 'http://testserver/v1/namespaced/3/'
293+
294+
def test_api_url_is_properly_reversed_with_v2(self):
295+
field = self._create_field('namespaced', 'v2')
296+
assert field.to_representation(PKOnlyObject(5)) == 'http://testserver/v2/namespaced/5/'
297+
298+
def test_non_api_url_is_properly_reversed_regardless_of_the_version(self):
299+
"""
300+
Regression test for #2711
301+
"""
302+
field = self._create_field('non-api-view', 'v1')
303+
assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'
304+
305+
field = self._create_field('non-api-view', 'v2')
306+
assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'

0 commit comments

Comments
 (0)