aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/routers.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/routers.py')
-rw-r--r--rest_framework/routers.py218
1 files changed, 218 insertions, 0 deletions
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
new file mode 100644
index 00000000..923405e8
--- /dev/null
+++ b/rest_framework/routers.py
@@ -0,0 +1,218 @@
+"""
+Routers provide a convenient and consistent way of automatically
+determining the URL conf for your API.
+
+They are used by simply instantiating a Router class, and then registering
+all the required ViewSets with that router.
+
+For example, you might have a `urls.py` that looks something like this:
+
+ router = routers.DefaultRouter()
+ router.register('users', UserViewSet, 'user')
+ router.register('accounts', AccountViewSet, 'account')
+
+ urlpatterns = router.urls
+"""
+from __future__ import unicode_literals
+
+from collections import namedtuple
+from django.conf.urls import url, patterns
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework.urlpatterns import format_suffix_patterns
+
+
+Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs'])
+
+
+def replace_methodname(format_string, methodname):
+ """
+ Partially format a format_string, swapping out any
+ '{methodname}' or '{methodnamehyphen}' components.
+ """
+ methodnamehyphen = methodname.replace('_', '-')
+ ret = format_string
+ ret = ret.replace('{methodname}', methodname)
+ ret = ret.replace('{methodnamehyphen}', methodnamehyphen)
+ return ret
+
+
+class BaseRouter(object):
+ def __init__(self):
+ self.registry = []
+
+ def register(self, prefix, viewset, name):
+ self.registry.append((prefix, viewset, name))
+
+ def get_urls(self):
+ raise NotImplemented('get_urls must be overridden')
+
+ @property
+ def urls(self):
+ if not hasattr(self, '_urls'):
+ self._urls = patterns('', *self.get_urls())
+ return self._urls
+
+
+class SimpleRouter(BaseRouter):
+ routes = [
+ # List route.
+ Route(
+ url=r'^{prefix}/$',
+ mapping={
+ 'get': 'list',
+ 'post': 'create'
+ },
+ name='{basename}-list',
+ initkwargs={'suffix': 'List'}
+ ),
+ # Detail route.
+ Route(
+ url=r'^{prefix}/{lookup}/$',
+ mapping={
+ 'get': 'retrieve',
+ 'put': 'update',
+ 'patch': 'partial_update',
+ 'delete': 'destroy'
+ },
+ name='{basename}-detail',
+ initkwargs={'suffix': 'Instance'}
+ ),
+ # Dynamically generated routes.
+ # Generated using @action or @link decorators on methods of the viewset.
+ Route(
+ url=r'^{prefix}/{lookup}/{methodname}/$',
+ mapping={
+ '{httpmethod}': '{methodname}',
+ },
+ name='{basename}-{methodnamehyphen}',
+ initkwargs={}
+ ),
+ ]
+
+ def get_routes(self, viewset):
+ """
+ Augment `self.routes` with any dynamically generated routes.
+
+ Returns a list of the Route namedtuple.
+ """
+
+ # Determine any `@action` or `@link` decorated methods on the viewset
+ dynamic_routes = {}
+ for methodname in dir(viewset):
+ attr = getattr(viewset, methodname)
+ httpmethod = getattr(attr, 'bind_to_method', None)
+ if httpmethod:
+ dynamic_routes[httpmethod] = methodname
+
+ ret = []
+ for route in self.routes:
+ if route.mapping == {'{httpmethod}': '{methodname}'}:
+ # Dynamic routes (@link or @action decorator)
+ for httpmethod, methodname in dynamic_routes.items():
+ initkwargs = route.initkwargs.copy()
+ initkwargs.update(getattr(viewset, methodname).kwargs)
+ ret.append(Route(
+ url=replace_methodname(route.url, methodname),
+ mapping={httpmethod: methodname},
+ name=replace_methodname(route.name, methodname),
+ initkwargs=initkwargs,
+ ))
+ else:
+ # Standard route
+ ret.append(route)
+
+ return ret
+
+ def get_method_map(self, viewset, method_map):
+ """
+ Given a viewset, and a mapping of http methods to actions,
+ return a new mapping which only includes any mappings that
+ are actually implemented by the viewset.
+ """
+ bound_methods = {}
+ for method, action in method_map.items():
+ if hasattr(viewset, action):
+ bound_methods[method] = action
+ return bound_methods
+
+ def get_lookup_regex(self, viewset):
+ """
+ Given a viewset, return the portion of URL regex that is used
+ to match against a single instance.
+ """
+ base_regex = '(?P<{lookup_field}>[^/]+)'
+ lookup_field = getattr(viewset, 'lookup_field', 'pk')
+ return base_regex.format(lookup_field=lookup_field)
+
+ def get_urls(self):
+ """
+ Use the registered viewsets to generate a list of URL patterns.
+ """
+ ret = []
+
+ for prefix, viewset, basename in self.registry:
+ lookup = self.get_lookup_regex(viewset)
+ routes = self.get_routes(viewset)
+
+ for route in routes:
+
+ # Only actions which actually exist on the viewset will be bound
+ mapping = self.get_method_map(viewset, route.mapping)
+ if not mapping:
+ continue
+
+ # Build the url pattern
+ regex = route.url.format(prefix=prefix, lookup=lookup)
+ view = viewset.as_view(mapping, **route.initkwargs)
+ name = route.name.format(basename=basename)
+ ret.append(url(regex, view, name=name))
+
+ return ret
+
+
+class DefaultRouter(SimpleRouter):
+ """
+ The default router extends the SimpleRouter, but also adds in a default
+ API root view, and adds format suffix patterns to the URLs.
+ """
+ include_root_view = True
+ include_format_suffixes = True
+
+ def get_api_root_view(self):
+ """
+ Return a view to use as the API root.
+ """
+ api_root_dict = {}
+ list_name = self.routes[0].name
+ for prefix, viewset, basename in self.registry:
+ api_root_dict[prefix] = list_name.format(basename=basename)
+
+ @api_view(('GET',))
+ def api_root(request, format=None):
+ ret = {}
+ for key, url_name in api_root_dict.items():
+ ret[key] = reverse(url_name, request=request, format=format)
+ return Response(ret)
+
+ return api_root
+
+ def get_urls(self):
+ """
+ Generate the list of URL patterns, including a default root view
+ for the API, and appending `.json` style format suffixes.
+ """
+ urls = []
+
+ if self.include_root_view:
+ root_url = url(r'^$', self.get_api_root_view(), name='api-root')
+ urls.append(root_url)
+
+ default_urls = super(DefaultRouter, self).get_urls()
+ urls.extend(default_urls)
+
+ if self.include_format_suffixes:
+ urls = format_suffix_patterns(urls)
+
+ return urls