From d8a7e65514f00d19bfe7d9902be2290c6beb0c10 Mon Sep 17 00:00:00 2001 From: Rocky Meza Date: Wed, 24 Dec 2014 19:37:13 -0700 Subject: [PATCH] Add support for namespaces in APIRoot. --- rest_framework/compat.py | 9 +++++++++ rest_framework/routers.py | 12 +++++++++++- tests/router_test_urls.py | 32 ++++++++++++++++++++++++++++++++ tests/test_routers.py | 38 +++++++++++++++++++++++++------------- 4 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 tests/router_test_urls.py diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 69fdd7936..b947302fc 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -189,6 +189,15 @@ class RequestFactory(DjangoRequestFactory): return self.request(**r) +# request only provides `resolver_match` from 1.5 onwards. +def get_resolver_match(request): + try: + return request.resolver_match + except AttributeError: # Django < 1.5 + from django.core.urlresolvers import resolve + return resolve(request.path_info) + + # Markdown is optional try: import markdown diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 1cb65b1c0..98458482d 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -21,7 +21,7 @@ from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch from rest_framework import views -from rest_framework.compat import OrderedDict +from rest_framework.compat import OrderedDict, get_resolver_match from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns @@ -290,9 +290,19 @@ class DefaultRouter(SimpleRouter): class APIRoot(views.APIView): _ignore_model_permissions = True + def get_namespace(self): + """ + Attempt to retrieve the namespace of the current router. + """ + resolver_match = get_resolver_match(self.request) + return resolver_match.namespace + def get(self, request, *args, **kwargs): ret = OrderedDict() + namespace = self.get_namespace() for key, url_name in api_root_dict.items(): + if namespace: + url_name = namespace + ':' + url_name try: ret[key] = reverse( url_name, diff --git a/tests/router_test_urls.py b/tests/router_test_urls.py new file mode 100644 index 000000000..57339c0f2 --- /dev/null +++ b/tests/router_test_urls.py @@ -0,0 +1,32 @@ +from django.conf.urls import url, include + +from rest_framework import viewsets, mixins, routers + +from .test_routers import APIRootTestModel + + +class APIRootTestViewSet(viewsets.ModelViewSet): + model = APIRootTestModel + + +class ListlessViewSet(mixins.RetrieveModelMixin, + viewsets.GenericViewSet): + model = APIRootTestModel + + +router = routers.DefaultRouter() +router.register(r'test-model', APIRootTestViewSet) + + +listless_router = routers.DefaultRouter() +# Avoid conflict with the api/ route. +listless_router.root_view_name = 'listless-api-root' +listless_router.register(r'full', APIRootTestViewSet, 'full') +listless_router.register(r'listless', ListlessViewSet, 'listless') + + +urlpatterns = [ + url(r'^api/', include(router.urls)), + url(r'^namespaced-api/', include(router.urls, namespace='api-namespace')), + url(r'^listless/', include(listless_router.urls)), +] diff --git a/tests/test_routers.py b/tests/test_routers.py index 06ab8103a..5462c5212 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -3,7 +3,8 @@ from django.conf.urls import patterns, url, include from django.db import models from django.test import TestCase from django.core.exceptions import ImproperlyConfigured -from rest_framework import serializers, viewsets, mixins, permissions +from django.core import urlresolvers +from rest_framework import serializers, viewsets, permissions from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter @@ -307,17 +308,28 @@ class TestDynamicListAndDetailRouter(TestCase): self.assertEqual(route.mapping[method_map], method_name) -class TestRootWithAListlessViewset(TestCase): - def setUp(self): - class NoteViewSet(mixins.RetrieveModelMixin, - viewsets.GenericViewSet): - model = RouterTestModel +# APIRoot +class APIRootTestModel(models.Model): + pass - self.router = DefaultRouter() - self.router.register(r'notes', NoteViewSet) - self.view = self.router.urls[0].callback - def test_api_root(self): - request = factory.get('/') - response = self.view(request) - self.assertEqual(response.data, {}) +class TestAPIRootView(TestCase): + urls = 'tests.router_test_urls' + + def test_listless(self): + url = urlresolvers.reverse('listless-api-root') + response = self.client.get(url) + self.assertIn('full', response.data) + self.assertNotIn('listless', response.data) + + def test_normal_api_root_contains_routes(self): + url = urlresolvers.reverse('api-root') + response = self.client.get(url) + self.assertIn('test-model', response.data) + self.assertEqual(response.data['test-model'], 'http://testserver/api/test-model/') + + def test_namespaced_api_root_contains_routes(self): + url = urlresolvers.reverse('api-namespace:api-root') + response = self.client.get(url) + self.assertIn('test-model', response.data) + self.assertEqual(response.data['test-model'], 'http://testserver/namespaced-api/test-model/')