Add default app namespace to drf reverse

This commit is contained in:
Ryan P Kilby 2017-11-18 17:27:33 -05:00
parent 4fbe3ecce6
commit ec7f358010
2 changed files with 49 additions and 10 deletions

View File

@ -34,10 +34,29 @@ def preserve_builtin_query_params(url, request=None):
def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 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 If versioning is being used then we pass any `reverse` calls through
to the versioning scheme instance, so that the resulting URL to the versioning scheme instance, so that the resulting URL
can be modified if needed. 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) scheme = getattr(request, 'versioning_scheme', None)
if scheme is not None: if scheme is not None:
try: 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 Same as `django.urls.reverse`, but optionally takes a request
and returns a fully qualified URL, using the request to get the base URL. 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: if format is not None:
kwargs = kwargs or {} kwargs = kwargs or {}

View File

@ -19,11 +19,16 @@ apppatterns = ([
url(r'^home$', mock_view, name='home'), url(r'^home$', mock_view, name='home'),
], 'app') ], 'app')
apipatterns = ([
url(r'^root$', mock_view, name='root'),
], 'rest_framework')
urlpatterns = [ urlpatterns = [
url(r'^view$', mock_view, name='view'), url(r'^view$', mock_view, name='view'),
url(r'^app2/', include(apppatterns, namespace='app2')), url(r'^app2/', include(apppatterns, namespace='app2')),
url(r'^app1/', include(apppatterns, namespace='app1')), url(r'^app1/', include(apppatterns, namespace='app1')),
url(r'^api/', include(apipatterns)),
] ]
@ -36,7 +41,7 @@ class MockVersioningScheme(object):
if self.raise_error: if self.raise_error:
raise NoReverseMatch() raise NoReverseMatch()
return 'http://scheme-reversed/view' return 'http://scheme-reversed/api/root'
@override_settings(ROOT_URLCONF='tests.test_reverse') @override_settings(ROOT_URLCONF='tests.test_reverse')
@ -44,24 +49,33 @@ class ReverseTests(TestCase):
""" """
Tests for fully qualified URLs when using `reverse`. 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): def test_reversed_urls_are_fully_qualified(self):
request = factory.get('/view') request = factory.get('/api/root')
url = reverse('view', request=request)
assert url == 'http://testserver/view' url = reverse('root', request=request)
assert url == 'http://testserver/api/root'
def test_reverse_with_versioning_scheme(self): def test_reverse_with_versioning_scheme(self):
request = factory.get('/view') request = factory.get('/api/root')
request.versioning_scheme = MockVersioningScheme() request.versioning_scheme = MockVersioningScheme()
url = reverse('view', request=request) url = reverse('root', request=request)
assert url == 'http://scheme-reversed/view' assert url == 'http://scheme-reversed/api/root'
def test_reverse_with_versioning_scheme_fallback_to_default_on_error(self): 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) request.versioning_scheme = MockVersioningScheme(raise_error=True)
url = reverse('view', request=request) url = reverse('root', request=request)
assert url == 'http://testserver/view' assert url == 'http://testserver/api/root'
@override_settings(ROOT_URLCONF='tests.test_reverse') @override_settings(ROOT_URLCONF='tests.test_reverse')
@ -76,6 +90,10 @@ class NamespaceTests(TestCase):
def request(self, url): def request(self, url):
return self.client.get(url).wsgi_request return self.client.get(url).wsgi_request
def test_default_namespace(self):
url = reverse('root')
assert url == '/api/root'
def test_application_namespace(self): def test_application_namespace(self):
url = reverse('app:home') url = reverse('app:home')
assert url == '/app1/home' assert url == '/app1/home'