From 4fbe3ecce654614232f94362d51184e241ac07c6 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Sat, 18 Nov 2017 13:54:48 -0500 Subject: [PATCH] Add request.current_app handling to drf reverse --- rest_framework/reverse.py | 17 +++++++ tests/test_reverse.py | 100 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/rest_framework/reverse.py b/rest_framework/reverse.py index 54c463553..d5123d351 100644 --- a/rest_framework/reverse.py +++ b/rest_framework/reverse.py @@ -60,10 +60,27 @@ def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extr if format is not None: kwargs = kwargs or {} kwargs['format'] = format + if request: + extra.setdefault('current_app', current_app(request)) url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) if request: return request.build_absolute_uri(url) return url +def current_app(request): + """ + Get the current app for the request. + + This code is copied from the URL tag. + """ + try: + return request.current_app + except AttributeError: + try: + return request.resolver_match.namespace + except AttributeError: + return None + + reverse_lazy = lazy(reverse, six.text_type) diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 145b1a54f..473a82ffd 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -1,21 +1,29 @@ from __future__ import unicode_literals -from django.conf.urls import url +from django.conf.urls import include, url +from django.http import HttpResponse from django.test import TestCase, override_settings from django.urls import NoReverseMatch -from rest_framework.reverse import reverse +from rest_framework.reverse import current_app, reverse from rest_framework.test import APIRequestFactory factory = APIRequestFactory() -def null_view(request): - pass +def mock_view(request): + return HttpResponse('') + + +apppatterns = ([ + url(r'^home$', mock_view, name='home'), +], 'app') urlpatterns = [ - url(r'^view$', null_view, name='view'), + url(r'^view$', mock_view, name='view'), + url(r'^app2/', include(apppatterns, namespace='app2')), + url(r'^app1/', include(apppatterns, namespace='app1')), ] @@ -54,3 +62,85 @@ class ReverseTests(TestCase): url = reverse('view', request=request) assert url == 'http://testserver/view' + + +@override_settings(ROOT_URLCONF='tests.test_reverse') +class NamespaceTests(TestCase): + """ + Ensure reverse can handle namespaces. + + Note: It's necessary to use self.client() here, as the + RequestFactory does not setup the resolver_match. + """ + + def request(self, url): + return self.client.get(url).wsgi_request + + def test_application_namespace(self): + url = reverse('app:home') + assert url == '/app1/home' + + # instance namespace provided by current_app + url = reverse('app:home', current_app='app2') + assert url == '/app2/home' + + def test_instance_namespace(self): + url = reverse('app1:home') + assert url == '/app1/home' + + url = reverse('app2:home') + assert url == '/app2/home' + + def test_application_namespace_with_request(self): + # request's current app affects result + request1 = self.request('/app1/home') + request2 = self.request('/app2/home') + + # sanity check + assert current_app(request1) == 'app1' + assert current_app(request2) == 'app2' + + assert reverse('app:home', request=request1) == 'http://testserver/app1/home' + assert reverse('app:home', request=request2) == 'http://testserver/app2/home' + + def test_instance_namespace_with_request(self): + # request's current app is not relevant + request1 = self.request('/app1/home') + request2 = self.request('/app2/home') + + # sanity check + assert current_app(request1) == 'app1' + assert current_app(request2) == 'app2' + + assert reverse('app1:home', request=request1) == 'http://testserver/app1/home' + assert reverse('app2:home', request=request1) == 'http://testserver/app2/home' + assert reverse('app1:home', request=request2) == 'http://testserver/app1/home' + assert reverse('app2:home', request=request2) == 'http://testserver/app2/home' + + +@override_settings(ROOT_URLCONF='tests.test_reverse') +class CurrentAppTests(TestCase): + """ + Test current_app() function. + + Note: It's necessary to use self.client() here, as the + RequestFactory does not setup the resolver_match. + """ + + def request(self, url): + return self.client.get(url).wsgi_request + + def test_no_namespace(self): + request = self.request('/view') + assert current_app(request) == '' + + def test_namespace(self): + request = self.request('/app1/home') + assert current_app(request) == 'app1' + + request = self.request('/app2/home') + assert current_app(request) == 'app2' + + def test_factory_incompatibility(self): + request = factory.get('/app1/home') + assert current_app(request) is None