This commit is contained in:
Ryan P Kilby 2018-02-22 21:22:50 +00:00 committed by GitHub
commit d4c89fec6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 390 additions and 49 deletions

View File

@ -339,7 +339,8 @@ class HyperlinkedRelatedField(RelatedField):
self.view_name, request self.view_name, request
) )
except AttributeError: except AttributeError:
expected_viewname = self.view_name # by default, expect the 'rest_framework' namespace
expected_viewname = 'rest_framework:' + self.view_name
if match.view_name != expected_viewname: if match.view_name != expected_viewname:
self.fail('incorrect_match') self.fail('incorrect_match')

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,14 +75,33 @@ 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 {}
kwargs['format'] = format kwargs['format'] = format
if request:
extra.setdefault('current_app', current_app(request))
url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
if request: if request:
return request.build_absolute_uri(url) return request.build_absolute_uri(url)
return 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) reverse_lazy = lazy(reverse, six.text_type)

View File

@ -101,6 +101,10 @@ class BaseRouter(object):
self._urls = self.get_urls() self._urls = self.get_urls()
return self._urls return self._urls
@property
def urlpatterns(self):
return (self.urls, 'rest_framework')
class SimpleRouter(BaseRouter): class SimpleRouter(BaseRouter):
@ -305,10 +309,7 @@ class APIRootView(views.APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# Return a plain {"name": "hyperlink"} response. # Return a plain {"name": "hyperlink"} response.
ret = OrderedDict() ret = OrderedDict()
namespace = request.resolver_match.namespace
for key, url_name in self.api_root_dict.items(): for key, url_name in self.api_root_dict.items():
if namespace:
url_name = namespace + ':' + url_name
try: try:
ret[key] = reverse( ret[key] = reverse(
url_name, url_name,

View File

@ -96,7 +96,7 @@ def optional_login(request):
Include a login snippet if REST framework's login view is in the URLconf. Include a login snippet if REST framework's login view is in the URLconf.
""" """
try: try:
login_url = reverse('rest_framework:login') login_url = reverse('rest_framework_auth:login')
except NoReverseMatch: except NoReverseMatch:
return '' return ''
@ -112,7 +112,7 @@ def optional_docs_login(request):
Include a login snippet if REST framework's login view is in the URLconf. Include a login snippet if REST framework's login view is in the URLconf.
""" """
try: try:
login_url = reverse('rest_framework:login') login_url = reverse('rest_framework_auth:login')
except NoReverseMatch: except NoReverseMatch:
return 'log in' return 'log in'
@ -128,7 +128,7 @@ def optional_logout(request, user):
Include a logout snippet if REST framework's logout view is in the URLconf. Include a logout snippet if REST framework's logout view is in the URLconf.
""" """
try: try:
logout_url = reverse('rest_framework:logout') logout_url = reverse('rest_framework_auth:logout')
except NoReverseMatch: except NoReverseMatch:
snippet = format_html('<li class="navbar-text">{user}</li>', user=escape(user)) snippet = format_html('<li class="navbar-text">{user}</li>', user=escape(user))
return mark_safe(snippet) return mark_safe(snippet)

View File

@ -27,7 +27,7 @@ else:
logout = views.LogoutView.as_view() logout = views.LogoutView.as_view()
app_name = 'rest_framework' app_name = 'rest_framework_auth'
urlpatterns = [ urlpatterns = [
url(r'^login/$', login, login_kwargs, name='login'), url(r'^login/$', login, login_kwargs, name='login'),
url(r'^logout/$', logout, name='logout'), url(r'^logout/$', logout, name='logout'),

View File

@ -135,6 +135,13 @@ class NamespaceVersioning(BaseVersioning):
) )
def get_versioned_viewname(self, viewname, request): def get_versioned_viewname(self, viewname, request):
"""
The incoming `viewname` should be prefixed with the 'rest_framework'
application namespace. We want to replace this with the version
instance namespace.
"""
if viewname.startswith('rest_framework:'):
viewname = viewname[len('rest_framework:'):]
return request.version + ':' + viewname return request.version + ':' + viewname

View File

@ -1,4 +1,4 @@
from django.conf.urls import url from django.conf.urls import include, url
from django.db import models from django.db import models
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
@ -28,10 +28,14 @@ def dummy_view(request):
pass pass
urlpatterns = [ patterns = [
url(r'^example/(?P<pk>[0-9]+)/$', dummy_view, name='example-detail'), url(r'^example/(?P<pk>[0-9]+)/$', dummy_view, name='example-detail'),
] ]
urlpatterns = [
url(r'^', include((patterns, 'rest_framework')))
]
@override_settings(ROOT_URLCONF='tests.test_lazy_hyperlinks') @override_settings(ROOT_URLCONF='tests.test_lazy_hyperlinks')
class TestLazyHyperlinkNames(TestCase): class TestLazyHyperlinkNames(TestCase):

View File

@ -2,7 +2,7 @@ import uuid
import pytest import pytest
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from django.conf.urls import url from django.conf.urls import include, url
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings from django.test import override_settings
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@ -146,7 +146,9 @@ class TestProxiedPrimaryKeyRelatedField(APISimpleTestCase):
@override_settings(ROOT_URLCONF=[ @override_settings(ROOT_URLCONF=[
url(r'^example/(?P<name>.+)/$', lambda: None, name='example'), url(r'^', include(
([url(r'^example/(?P<name>.+)/$', lambda: None, name='example')], 'rest_framework')
)),
]) ])
class TestHyperlinkedRelatedField(APISimpleTestCase): class TestHyperlinkedRelatedField(APISimpleTestCase):
def setUp(self): def setUp(self):

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import include, url
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from rest_framework import serializers from rest_framework import serializers
@ -18,7 +18,7 @@ def dummy_view(request, pk):
pass pass
urlpatterns = [ patterns = [
url(r'^dummyurl/(?P<pk>[0-9]+)/$', dummy_view, name='dummy-url'), url(r'^dummyurl/(?P<pk>[0-9]+)/$', dummy_view, name='dummy-url'),
url(r'^manytomanysource/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanysource-detail'), url(r'^manytomanysource/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanysource-detail'),
url(r'^manytomanytarget/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanytarget-detail'), url(r'^manytomanytarget/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanytarget-detail'),
@ -29,6 +29,10 @@ urlpatterns = [
url(r'^nullableonetoonesource/(?P<pk>[0-9]+)/$', dummy_view, name='nullableonetoonesource-detail'), url(r'^nullableonetoonesource/(?P<pk>[0-9]+)/$', dummy_view, name='nullableonetoonesource-detail'),
] ]
urlpatterns = [
url(r'^', include((patterns, 'rest_framework')))
]
# ManyToMany # ManyToMany
class ManyToManyTargetSerializer(serializers.HyperlinkedModelSerializer): class ManyToManyTargetSerializer(serializers.HyperlinkedModelSerializer):
@ -449,3 +453,39 @@ class HyperlinkedNullableOneToOneTests(TestCase):
{'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None}, {'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None},
] ]
assert serializer.data == expected assert serializer.data == expected
class HyperlinkedNamespaceTests(TestCase):
def setUp(self):
target = ForeignKeyTarget(name='target-1')
target.save()
new_target = ForeignKeyTarget(name='target-2')
new_target.save()
for idx in range(1, 4):
source = ForeignKeySource(name='source-%d' % idx, target=target)
source.save()
def results(self):
queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request})
expected = [
{'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'},
{'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'},
{'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'}
]
with self.assertNumQueries(1):
assert serializer.data == expected
def test_foreign_key_retrieve_no_namespace(self):
url_conf = [
url(r'^', include((patterns, 'rest_framework'), namespace=None))
]
with override_settings(ROOT_URLCONF=url_conf):
self.results()
def test_foreign_key_retrieve_namespace(self):
url_conf = [
url(r'^', include((patterns, 'rest_framework'), namespace='api'))
]
with override_settings(ROOT_URLCONF=url_conf):
self.results()

View File

@ -1,21 +1,34 @@
from __future__ import unicode_literals 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.test import TestCase, override_settings
from django.urls import NoReverseMatch 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 from rest_framework.test import APIRequestFactory
factory = APIRequestFactory() factory = APIRequestFactory()
def null_view(request): def mock_view(request):
pass return HttpResponse('')
apppatterns = ([
url(r'^home$', mock_view, name='home'),
], 'app')
apipatterns = ([
url(r'^root$', mock_view, name='root'),
], 'rest_framework')
urlpatterns = [ 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')),
url(r'^api/', include(apipatterns)),
] ]
@ -28,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')
@ -36,21 +49,116 @@ 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')
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_default_namespace(self):
url = reverse('root')
assert url == '/api/root'
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

View File

@ -145,13 +145,17 @@ class TestSimpleRouter(TestCase):
class TestRootView(URLPatternsTestCase, TestCase): class TestRootView(URLPatternsTestCase, TestCase):
urlpatterns = [ urlpatterns = [
url(r'^non-namespaced/', include(namespaced_router.urls)), url(r'^non-namespaced/', include(namespaced_router.urlpatterns)),
url(r'^namespaced/', include((namespaced_router.urls, 'namespaced'), namespace='namespaced')), url(r'^namespaced1/', include(namespaced_router.urlpatterns, namespace='namespaced1')),
url(r'^namespaced2/', include(namespaced_router.urlpatterns, namespace='namespaced2')),
] ]
def test_retrieve_namespaced_root(self): def test_retrieve_namespaced_root(self):
response = self.client.get('/namespaced/') response = self.client.get('/namespaced1/')
assert response.data == {"example": "http://testserver/namespaced/example/"} assert response.data == {"example": "http://testserver/namespaced1/example/"}
response = self.client.get('/namespaced2/')
assert response.data == {"example": "http://testserver/namespaced2/example/"}
def test_retrieve_non_namespaced_root(self): def test_retrieve_non_namespaced_root(self):
response = self.client.get('/non-namespaced/') response = self.client.get('/non-namespaced/')
@ -163,8 +167,8 @@ class TestCustomLookupFields(URLPatternsTestCase, TestCase):
Ensure that custom lookup fields are correctly routed. Ensure that custom lookup fields are correctly routed.
""" """
urlpatterns = [ urlpatterns = [
url(r'^example/', include(notes_router.urls)), url(r'^example/', include(notes_router.urlpatterns)),
url(r'^example2/', include(kwarged_notes_router.urls)), url(r'^example2/', include(kwarged_notes_router.urlpatterns)),
] ]
def setUp(self): def setUp(self):
@ -221,8 +225,8 @@ class TestLookupUrlKwargs(URLPatternsTestCase, TestCase):
Setup a deep lookup_field, but map it to a simple URL kwarg. Setup a deep lookup_field, but map it to a simple URL kwarg.
""" """
urlpatterns = [ urlpatterns = [
url(r'^example/', include(notes_router.urls)), url(r'^example/', include(notes_router.urlpatterns)),
url(r'^example2/', include(kwarged_notes_router.urls)), url(r'^example2/', include(kwarged_notes_router.urlpatterns)),
] ]
def setUp(self): def setUp(self):
@ -410,7 +414,7 @@ class TestDynamicListAndDetailRouter(TestCase):
class TestEmptyPrefix(URLPatternsTestCase, TestCase): class TestEmptyPrefix(URLPatternsTestCase, TestCase):
urlpatterns = [ urlpatterns = [
url(r'^empty-prefix/', include(empty_prefix_router.urls)), url(r'^empty-prefix/', include(empty_prefix_router.urlpatterns)),
] ]
def test_empty_prefix_list(self): def test_empty_prefix_list(self):
@ -427,7 +431,7 @@ class TestEmptyPrefix(URLPatternsTestCase, TestCase):
class TestRegexUrlPath(URLPatternsTestCase, TestCase): class TestRegexUrlPath(URLPatternsTestCase, TestCase):
urlpatterns = [ urlpatterns = [
url(r'^regex/', include(regex_url_path_router.urls)), url(r'^regex/', include(regex_url_path_router.urlpatterns)),
] ]
def test_regex_url_path_list(self): def test_regex_url_path_list(self):

127
tests/test_urlconf.py Normal file
View File

@ -0,0 +1,127 @@
"""
Test rest framework assumptions about the configuration of the root urlconf.
"""
from django.conf.urls import include, url
from rest_framework import routers, viewsets
from rest_framework.reverse import NoReverseMatch, reverse
from rest_framework.test import APITestCase, URLPatternsTestCase
class MockViewSet(viewsets.ViewSet):
def list(self, request, *args, **kwargs):
pass
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'example', MockViewSet, base_name='example')
class TestUrlsAppNameRequired(APITestCase, URLPatternsTestCase):
urlpatterns = [
url(r'^api/', include(router.urls)),
]
def test_reverse(self):
"""
The 'rest_framework' namespace must be present.
"""
with self.assertRaises(NoReverseMatch):
reverse('example-list')
class TestUrlpatternsAppName(APITestCase, URLPatternsTestCase):
urlpatterns = [
url(r'^api/', include(router.urlpatterns)),
]
def test_reverse(self):
self.assertEqual(reverse('example-list'), '/api/example')
def test_app_name_reverse(self):
self.assertEqual(reverse('rest_framework:example-list'), '/api/example')
class TestUrlpatternsNamespace(APITestCase, URLPatternsTestCase):
urlpatterns = [
url(r'^api/v1/', include(router.urlpatterns, namespace='v1')),
url(r'^api/v2/', include(router.urlpatterns, namespace='v2')),
]
def test_reverse(self):
self.assertEqual(reverse('example-list'), '/api/v2/example')
def test_app_name_reverse(self):
self.assertEqual(reverse('rest_framework:example-list'), '/api/v2/example')
def test_namespace_reverse(self):
self.assertEqual(reverse('v1:example-list'), '/api/v1/example')
self.assertEqual(reverse('v2:example-list'), '/api/v2/example')
class TestAppUrlpatternsAppName(APITestCase, URLPatternsTestCase):
apppatterns = ([
url(r'^api/', include(router.urlpatterns)),
], 'api')
urlpatterns = [
url(r'^', include(apppatterns)),
]
def test_reverse(self):
"""
Nesting the router.urlpatterns in an app with an app_name will
break url resolution.
"""
with self.assertRaises(NoReverseMatch):
reverse('example-list')
class TestAppUrlpatterns(APITestCase, URLPatternsTestCase):
apppatterns = ([
url(r'^api/', include(router.urlpatterns)),
], None)
urlpatterns = [
url(r'^', include(apppatterns)),
]
def test_reverse(self):
self.assertEqual(reverse('example-list'), '/api/example')
class TestAppUrlsAppName(APITestCase, URLPatternsTestCase):
apppatterns = ([
url(r'^api/', include(router.urls)),
], 'rest_framework')
urlpatterns = [
url(r'^', include(apppatterns)),
]
def test_reverse(self):
self.assertEqual(reverse('example-list'), '/api/example')
def test_app_name_reverse(self):
self.assertEqual(reverse('rest_framework:example-list'), '/api/example')
class TestAppUrlsNamespace(APITestCase, URLPatternsTestCase):
apppatterns = ([
url(r'^', include(router.urls)),
], 'rest_framework')
urlpatterns = [
url(r'^api/v1/', include(apppatterns, namespace='v1')),
url(r'^api/v2/', include(apppatterns, namespace='v2')),
]
def test_reverse(self):
self.assertEqual(reverse('example-list'), '/api/v2/example')
def test_app_name_reverse(self):
self.assertEqual(reverse('rest_framework:example-list'), '/api/v2/example')
def test_namespace_reverse(self):
self.assertEqual(reverse('v1:example-list'), '/api/v1/example')
self.assertEqual(reverse('v2:example-list'), '/api/v2/example')

View File

@ -143,17 +143,21 @@ class TestRequestVersion:
class TestURLReversing(URLPatternsTestCase, APITestCase): class TestURLReversing(URLPatternsTestCase, APITestCase):
included = [ versioned = [
url(r'^namespaced/$', dummy_view, name='another'), url(r'^namespaced/$', dummy_view, name='another'),
url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail') url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail')
] ]
urlpatterns = [ included = [
url(r'^v1/', include((included, 'v1'), namespace='v1')),
url(r'^another/$', dummy_view, name='another'), url(r'^another/$', dummy_view, name='another'),
url(r'^(?P<version>[v1|v2]+)/another/$', dummy_view, name='another'), url(r'^(?P<version>[v1|v2]+)/another/$', dummy_view, name='another'),
] ]
urlpatterns = [
url(r'^v1/', include((versioned, 'rest_framework'), namespace='v1')),
url(r'^', include((included, 'rest_framework'))),
]
def test_reverse_unversioned(self): def test_reverse_unversioned(self):
view = ReverseView.as_view() view = ReverseView.as_view()
@ -314,8 +318,8 @@ class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase):
] ]
urlpatterns = [ urlpatterns = [
url(r'^v1/', include((included, 'v1'), namespace='v1')), url(r'^v1/', include((included, 'rest_framework'), namespace='v1')),
url(r'^v2/', include((included, 'v2'), namespace='v2')) url(r'^v2/', include((included, 'rest_framework'), namespace='v2'))
] ]
def setUp(self): def setUp(self):
@ -344,17 +348,21 @@ class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase,
nested = [ nested = [
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='nested'), url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='nested'),
] ]
included = [ versioned = [
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'), url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
url(r'^nested/', include((nested, 'nested-namespace'), namespace='nested-namespace')) url(r'^nested/', include((nested, 'nested-namespace'), namespace='nested-namespace'))
] ]
urlpatterns = [ included = [
url(r'^v1/', include((included, 'restframeworkv1'), namespace='v1')),
url(r'^v2/', include((included, 'restframeworkv2'), namespace='v2')),
url(r'^non-api/(?P<pk>\d+)/$', dummy_pk_view, name='non-api-view') url(r'^non-api/(?P<pk>\d+)/$', dummy_pk_view, name='non-api-view')
] ]
urlpatterns = [
url(r'^v1/', include((versioned, 'rest_framework'), namespace='v1')),
url(r'^v2/', include((versioned, 'rest_framework'), namespace='v2')),
url(r'^', include((included, 'rest_framework'))),
]
def _create_field(self, view_name, version): def _create_field(self, view_name, version):
request = factory.get("/") request = factory.get("/")
request.versioning_scheme = NamespaceVersioning() request.versioning_scheme = NamespaceVersioning()
@ -381,6 +389,7 @@ class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase,
def test_non_api_url_is_properly_reversed_regardless_of_the_version(self): def test_non_api_url_is_properly_reversed_regardless_of_the_version(self):
""" """
Regression test for #2711 Regression test for #2711
Note: non-api-views still need to be included in the 'rest_framework' namespace
""" """
field = self._create_field('non-api-view', 'v1') field = self._create_field('non-api-view', 'v1')
assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/' assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'

View File

@ -62,7 +62,7 @@ router.register(r'actions-alt', ActionViewSet, base_name='actions-alt')
urlpatterns = [ urlpatterns = [
url(r'^api/', include(router.urls)), url(r'^api/', include(router.urlpatterns)),
] ]