Skip to content

Commit 49ce3d6

Browse files
Merge branch 'c17r-router-empty-prefix' into version-3-5
2 parents ee2b165 + c427144 commit 49ce3d6

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

rest_framework/routers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def urls(self):
8383

8484

8585
class SimpleRouter(BaseRouter):
86+
8687
routes = [
8788
# List route.
8889
Route(
@@ -258,6 +259,13 @@ def get_urls(self):
258259
trailing_slash=self.trailing_slash
259260
)
260261

262+
# If there is no prefix, the first part of the url is probably
263+
# controlled by project's urls.py and the router is in an app,
264+
# so a slash in the beginning will (A) cause Django to give
265+
# warnings and (B) generate URLS that will require using '//'.
266+
if not prefix and regex[:2] == '^/':
267+
regex = '^' + regex[2:]
268+
261269
view = viewset.as_view(mapping, **route.initkwargs)
262270
name = route.name.format(basename=basename)
263271
ret.append(url(regex, view, name=name))

tests/test_routers.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
import json
34
from collections import namedtuple
45

56
from django.conf.urls import include, url
@@ -47,6 +48,21 @@ class MockViewSet(viewsets.ModelViewSet):
4748
serializer_class = None
4849

4950

51+
class EmptyPrefixSerializer(serializers.HyperlinkedModelSerializer):
52+
class Meta:
53+
model = RouterTestModel
54+
fields = ('uuid', 'text')
55+
56+
57+
class EmptyPrefixViewSet(viewsets.ModelViewSet):
58+
queryset = [RouterTestModel(id=1, uuid='111', text='First'), RouterTestModel(id=2, uuid='222', text='Second')]
59+
serializer_class = EmptyPrefixSerializer
60+
61+
def get_object(self, *args, **kwargs):
62+
index = int(self.kwargs['pk']) - 1
63+
return self.queryset[index]
64+
65+
5066
notes_router = SimpleRouter()
5167
notes_router.register(r'notes', NoteViewSet)
5268

@@ -56,11 +72,19 @@ class MockViewSet(viewsets.ModelViewSet):
5672
namespaced_router = DefaultRouter()
5773
namespaced_router.register(r'example', MockViewSet, base_name='example')
5874

75+
empty_prefix_router = SimpleRouter()
76+
empty_prefix_router.register(r'', EmptyPrefixViewSet, base_name='empty_prefix')
77+
empty_prefix_urls = [
78+
url(r'^', include(empty_prefix_router.urls)),
79+
]
80+
5981
urlpatterns = [
6082
url(r'^non-namespaced/', include(namespaced_router.urls)),
6183
url(r'^namespaced/', include(namespaced_router.urls, namespace='example')),
6284
url(r'^example/', include(notes_router.urls)),
6385
url(r'^example2/', include(kwarged_notes_router.urls)),
86+
87+
url(r'^empty-prefix/', include(empty_prefix_urls)),
6488
]
6589

6690

@@ -384,3 +408,28 @@ def test_list_and_detail_route_decorators(self):
384408

385409
def test_inherited_list_and_detail_route_decorators(self):
386410
self._test_list_and_detail_route_decorators(SubDynamicListAndDetailViewSet)
411+
412+
413+
@override_settings(ROOT_URLCONF='tests.test_routers')
414+
class TestEmptyPrefix(TestCase):
415+
def test_empty_prefix_list(self):
416+
response = self.client.get('/empty-prefix/')
417+
self.assertEqual(200, response.status_code)
418+
self.assertEqual(
419+
json.loads(response.content.decode('utf-8')),
420+
[
421+
{'uuid': '111', 'text': 'First'},
422+
{'uuid': '222', 'text': 'Second'}
423+
]
424+
)
425+
426+
def test_empty_prefix_detail(self):
427+
response = self.client.get('/empty-prefix/1/')
428+
self.assertEqual(200, response.status_code)
429+
self.assertEqual(
430+
json.loads(response.content.decode('utf-8')),
431+
{
432+
'uuid': '111',
433+
'text': 'First'
434+
}
435+
)

0 commit comments

Comments
 (0)