mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-02 19:40:13 +03:00
Merge 00f2f72e0d
into d2994e0596
This commit is contained in:
commit
d4c89fec6b
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
127
tests/test_urlconf.py
Normal 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')
|
|
@ -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/'
|
||||||
|
|
|
@ -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)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user