Skip to content

Commit 7e67ad6

Browse files
Merge pull request encode#953 from j4mie/prevent-duplicate-routing
Prevent dynamically routing to a method that is already routed to.
2 parents af2fdc0 + c127e63 commit 7e67ad6

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

rest_framework/routers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"""
1616
from __future__ import unicode_literals
1717

18+
import itertools
1819
from collections import namedtuple
20+
from django.core.exceptions import ImproperlyConfigured
1921
from rest_framework import views
2022
from rest_framework.compat import patterns, url
2123
from rest_framework.response import Response
@@ -38,6 +40,13 @@ def replace_methodname(format_string, methodname):
3840
return ret
3941

4042

43+
def flatten(list_of_lists):
44+
"""
45+
Takes an iterable of iterables, returns a single iterable containing all items
46+
"""
47+
return itertools.chain(*list_of_lists)
48+
49+
4150
class BaseRouter(object):
4251
def __init__(self):
4352
self.registry = []
@@ -130,12 +139,17 @@ def get_routes(self, viewset):
130139
Returns a list of the Route namedtuple.
131140
"""
132141

142+
known_actions = flatten([route.mapping.values() for route in self.routes])
143+
133144
# Determine any `@action` or `@link` decorated methods on the viewset
134145
dynamic_routes = []
135146
for methodname in dir(viewset):
136147
attr = getattr(viewset, methodname)
137148
httpmethods = getattr(attr, 'bind_to_methods', None)
138149
if httpmethods:
150+
if methodname in known_actions:
151+
raise ImproperlyConfigured('Cannot use @action or @link decorator on '
152+
'method "%s" as it is an existing route' % methodname)
139153
httpmethods = [method.lower() for method in httpmethods]
140154
dynamic_routes.append((httpmethods, methodname))
141155

rest_framework/tests/test_routers.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.db import models
33
from django.test import TestCase
44
from django.test.client import RequestFactory
5+
from django.core.exceptions import ImproperlyConfigured
56
from rest_framework import serializers, viewsets, permissions
67
from rest_framework.compat import include, patterns, url
78
from rest_framework.decorators import link, action
@@ -191,3 +192,24 @@ def test_action_kwargs(self):
191192
response.data,
192193
{'permission_classes': [permissions.AllowAny]}
193194
)
195+
196+
class TestActionAppliedToExistingRoute(TestCase):
197+
"""
198+
Ensure `@action` decorator raises an except when applied
199+
to an existing route
200+
"""
201+
202+
def test_exception_raised_when_action_applied_to_existing_route(self):
203+
class TestViewSet(viewsets.ModelViewSet):
204+
205+
@action()
206+
def retrieve(self, request, *args, **kwargs):
207+
return Response({
208+
'hello': 'world'
209+
})
210+
211+
self.router = SimpleRouter()
212+
self.router.register(r'test', TestViewSet, base_name='test')
213+
214+
with self.assertRaises(ImproperlyConfigured):
215+
self.router.urls

0 commit comments

Comments
 (0)