mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-09 08:00:52 +03:00
Merge pull request #2010 from tanwanirahul/master
Ability to customize method names without creating a custom router
This commit is contained in:
commit
d109ae0a2e
|
@ -68,6 +68,24 @@ The following URL pattern would additionally be generated:
|
||||||
|
|
||||||
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'`
|
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'`
|
||||||
|
|
||||||
|
If you do not want to use the default URL generated for your custom action, you can instead use the url_path parameter to customize it.
|
||||||
|
|
||||||
|
For example, if you want to change the URL for our custom action to `^users/{pk}/change-password/$`, you could write:
|
||||||
|
|
||||||
|
from myapp.permissions import IsAdminOrIsSelf
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
|
||||||
|
class UserViewSet(ModelViewSet):
|
||||||
|
...
|
||||||
|
|
||||||
|
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password')
|
||||||
|
def set_password(self, request, pk=None):
|
||||||
|
...
|
||||||
|
|
||||||
|
The above example would now generate the following URL pattern:
|
||||||
|
|
||||||
|
* URL pattern: `^users/{pk}/change-password/$` Name: `'user-change-password'`
|
||||||
|
|
||||||
For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
|
For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
|
||||||
|
|
||||||
# API Guide
|
# API Guide
|
||||||
|
|
|
@ -53,6 +53,8 @@ Notice that we've also used the `@detail_route` decorator to create a custom act
|
||||||
|
|
||||||
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
|
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
|
||||||
|
|
||||||
|
The URLs for custom actions by default depend on the method name itself. If you want to change the way url should be constructed, you can include url_path as a decorator keyword argument.
|
||||||
|
|
||||||
## Binding ViewSets to URLs explicitly
|
## Binding ViewSets to URLs explicitly
|
||||||
|
|
||||||
The handler methods only get bound to the actions when we define the URLConf.
|
The handler methods only get bound to the actions when we define the URLConf.
|
||||||
|
|
|
@ -176,23 +176,27 @@ class SimpleRouter(BaseRouter):
|
||||||
if isinstance(route, DynamicDetailRoute):
|
if isinstance(route, DynamicDetailRoute):
|
||||||
# Dynamic detail routes (@detail_route decorator)
|
# Dynamic detail routes (@detail_route decorator)
|
||||||
for httpmethods, methodname in detail_routes:
|
for httpmethods, methodname in detail_routes:
|
||||||
|
method_kwargs = getattr(viewset, methodname).kwargs
|
||||||
|
url_path = method_kwargs.pop("url_path", None) or methodname
|
||||||
initkwargs = route.initkwargs.copy()
|
initkwargs = route.initkwargs.copy()
|
||||||
initkwargs.update(getattr(viewset, methodname).kwargs)
|
initkwargs.update(method_kwargs)
|
||||||
ret.append(Route(
|
ret.append(Route(
|
||||||
url=replace_methodname(route.url, methodname),
|
url=replace_methodname(route.url, url_path),
|
||||||
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
|
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
|
||||||
name=replace_methodname(route.name, methodname),
|
name=replace_methodname(route.name, url_path),
|
||||||
initkwargs=initkwargs,
|
initkwargs=initkwargs,
|
||||||
))
|
))
|
||||||
elif isinstance(route, DynamicListRoute):
|
elif isinstance(route, DynamicListRoute):
|
||||||
# Dynamic list routes (@list_route decorator)
|
# Dynamic list routes (@list_route decorator)
|
||||||
for httpmethods, methodname in list_routes:
|
for httpmethods, methodname in list_routes:
|
||||||
|
method_kwargs = getattr(viewset, methodname).kwargs
|
||||||
|
url_path = method_kwargs.pop("url_path", None) or methodname
|
||||||
initkwargs = route.initkwargs.copy()
|
initkwargs = route.initkwargs.copy()
|
||||||
initkwargs.update(getattr(viewset, methodname).kwargs)
|
initkwargs.update(method_kwargs)
|
||||||
ret.append(Route(
|
ret.append(Route(
|
||||||
url=replace_methodname(route.url, methodname),
|
url=replace_methodname(route.url, url_path),
|
||||||
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
|
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
|
||||||
name=replace_methodname(route.name, methodname),
|
name=replace_methodname(route.name, url_path),
|
||||||
initkwargs=initkwargs,
|
initkwargs=initkwargs,
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -8,6 +8,7 @@ from rest_framework.decorators import detail_route, list_route
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.routers import SimpleRouter, DefaultRouter
|
from rest_framework.routers import SimpleRouter, DefaultRouter
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
@ -261,6 +262,14 @@ class DynamicListAndDetailViewSet(viewsets.ViewSet):
|
||||||
def detail_route_get(self, request, *args, **kwargs):
|
def detail_route_get(self, request, *args, **kwargs):
|
||||||
return Response({'method': 'link2'})
|
return Response({'method': 'link2'})
|
||||||
|
|
||||||
|
@list_route(url_path="list_custom-route")
|
||||||
|
def list_custom_route_get(self, request, *args, **kwargs):
|
||||||
|
return Response({'method': 'link1'})
|
||||||
|
|
||||||
|
@detail_route(url_path="detail_custom-route")
|
||||||
|
def detail_custom_route_get(self, request, *args, **kwargs):
|
||||||
|
return Response({'method': 'link2'})
|
||||||
|
|
||||||
|
|
||||||
class TestDynamicListAndDetailRouter(TestCase):
|
class TestDynamicListAndDetailRouter(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -269,22 +278,33 @@ class TestDynamicListAndDetailRouter(TestCase):
|
||||||
def test_list_and_detail_route_decorators(self):
|
def test_list_and_detail_route_decorators(self):
|
||||||
routes = self.router.get_routes(DynamicListAndDetailViewSet)
|
routes = self.router.get_routes(DynamicListAndDetailViewSet)
|
||||||
decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))]
|
decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))]
|
||||||
|
|
||||||
|
MethodNamesMap = namedtuple('MethodNamesMap', 'method_name url_path')
|
||||||
# Make sure all these endpoints exist and none have been clobbered
|
# Make sure all these endpoints exist and none have been clobbered
|
||||||
for i, endpoint in enumerate(['list_route_get', 'list_route_post', 'detail_route_get', 'detail_route_post']):
|
for i, endpoint in enumerate([MethodNamesMap('list_custom_route_get', 'list_custom-route'),
|
||||||
|
MethodNamesMap('list_route_get', 'list_route_get'),
|
||||||
|
MethodNamesMap('list_route_post', 'list_route_post'),
|
||||||
|
MethodNamesMap('detail_custom_route_get', 'detail_custom-route'),
|
||||||
|
MethodNamesMap('detail_route_get', 'detail_route_get'),
|
||||||
|
MethodNamesMap('detail_route_post', 'detail_route_post')
|
||||||
|
]):
|
||||||
route = decorator_routes[i]
|
route = decorator_routes[i]
|
||||||
# check url listing
|
# check url listing
|
||||||
if endpoint.startswith('list_'):
|
method_name = endpoint.method_name
|
||||||
|
url_path = endpoint.url_path
|
||||||
|
|
||||||
|
if method_name.startswith('list_'):
|
||||||
self.assertEqual(route.url,
|
self.assertEqual(route.url,
|
||||||
'^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint))
|
'^{{prefix}}/{0}{{trailing_slash}}$'.format(url_path))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(route.url,
|
self.assertEqual(route.url,
|
||||||
'^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint))
|
'^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(url_path))
|
||||||
# check method to function mapping
|
# check method to function mapping
|
||||||
if endpoint.endswith('_post'):
|
if method_name.endswith('_post'):
|
||||||
method_map = 'post'
|
method_map = 'post'
|
||||||
else:
|
else:
|
||||||
method_map = 'get'
|
method_map = 'get'
|
||||||
self.assertEqual(route.mapping[method_map], endpoint)
|
self.assertEqual(route.mapping[method_map], method_name)
|
||||||
|
|
||||||
|
|
||||||
class TestRootWithAListlessViewset(TestCase):
|
class TestRootWithAListlessViewset(TestCase):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user