diff --git a/rest_framework/reverse.py b/rest_framework/reverse.py index d5123d351..2a119afd5 100644 --- a/rest_framework/reverse.py +++ b/rest_framework/reverse.py @@ -34,10 +34,29 @@ def preserve_builtin_query_params(url, request=None): def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): """ + Extends `django.urls.reverse` with behavior specific to rest framework. + + The `viewname` will be prepended with the 'rest_framework' application + namespace if no namspace is included in the `viewname` argument. The + framework fundamentally assumes that the router urls will be included with + the 'rest_framework' namespace, so ensure that your root url patterns are + configured accordingly. Assuming you use the default router, you can check + this with: + + from django.urls import reverse + + reverse('rest_framework:api-root') + If versioning is being used then we pass any `reverse` calls through to the versioning scheme instance, so that the resulting URL can be modified if needed. + + Optionally takes a `request` object (see `_reverse` for details). """ + # prepend the 'rest_framework' application namespace + if ':' not in viewname: + viewname = 'rest_framework:' + viewname + scheme = getattr(request, 'versioning_scheme', None) if scheme is not None: try: @@ -56,6 +75,8 @@ def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extr """ Same as `django.urls.reverse`, but optionally takes a request and returns a fully qualified URL, using the request to get the base URL. + + Additionally, the request is used to determine the `current_app` instance. """ if format is not None: kwargs = kwargs or {} diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 473a82ffd..673f46002 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -19,11 +19,16 @@ apppatterns = ([ url(r'^home$', mock_view, name='home'), ], 'app') +apipatterns = ([ + url(r'^root$', mock_view, name='root'), +], 'rest_framework') + urlpatterns = [ url(r'^view$', mock_view, name='view'), url(r'^app2/', include(apppatterns, namespace='app2')), url(r'^app1/', include(apppatterns, namespace='app1')), + url(r'^api/', include(apipatterns)), ] @@ -36,7 +41,7 @@ class MockVersioningScheme(object): if self.raise_error: raise NoReverseMatch() - return 'http://scheme-reversed/view' + return 'http://scheme-reversed/api/root' @override_settings(ROOT_URLCONF='tests.test_reverse') @@ -44,24 +49,33 @@ class ReverseTests(TestCase): """ Tests for fully qualified URLs when using `reverse`. """ + + def test_reverse_non_drf_view(self): + request = factory.get('/api/root') + + # DRF reverse should not match non-DRF views + with self.assertRaises(NoReverseMatch): + reverse('view', request=request) + def test_reversed_urls_are_fully_qualified(self): - request = factory.get('/view') - url = reverse('view', request=request) - assert url == 'http://testserver/view' + request = factory.get('/api/root') + + url = reverse('root', request=request) + assert url == 'http://testserver/api/root' def test_reverse_with_versioning_scheme(self): - request = factory.get('/view') + request = factory.get('/api/root') request.versioning_scheme = MockVersioningScheme() - url = reverse('view', request=request) - assert url == 'http://scheme-reversed/view' + url = reverse('root', request=request) + assert url == 'http://scheme-reversed/api/root' def test_reverse_with_versioning_scheme_fallback_to_default_on_error(self): - request = factory.get('/view') + request = factory.get('/api/root') request.versioning_scheme = MockVersioningScheme(raise_error=True) - url = reverse('view', request=request) - assert url == 'http://testserver/view' + url = reverse('root', request=request) + assert url == 'http://testserver/api/root' @override_settings(ROOT_URLCONF='tests.test_reverse') @@ -76,6 +90,10 @@ class NamespaceTests(TestCase): def request(self, url): return self.client.get(url).wsgi_request + def test_default_namespace(self): + url = reverse('root') + assert url == '/api/root' + def test_application_namespace(self): url = reverse('app:home') assert url == '/app1/home'