aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/routers.py
diff options
context:
space:
mode:
authorTom Christie2013-04-24 22:40:24 +0100
committerTom Christie2013-04-24 22:40:24 +0100
commitb94da2468cdda6b0ad491574d35097d0e336ea7f (patch)
treebfb05f85703a738918252c8968f9a460554cae3f /rest_framework/routers.py
parent835d3f89d37b873b2ef96dc7d71922b035b07328 (diff)
downloaddjango-rest-framework-b94da2468cdda6b0ad491574d35097d0e336ea7f.tar.bz2
Various clean up and lots of docs
Diffstat (limited to 'rest_framework/routers.py')
-rw-r--r--rest_framework/routers.py231
1 files changed, 174 insertions, 57 deletions
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index afc51f3b..febb02b3 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -1,81 +1,198 @@
+"""
+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 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
class BaseRouter(object):
def __init__(self):
self.registry = []
- def register(self, prefix, viewset, base_name):
- self.registry.append((prefix, viewset, base_name))
+ def register(self, prefix, viewset, basename):
+ self.registry.append((prefix, viewset, basename))
- def get_urlpatterns(self):
- raise NotImplemented('get_urlpatterns must be overridden')
+ def get_urls(self):
+ raise NotImplemented('get_urls must be overridden')
@property
- def urlpatterns(self):
- if not hasattr(self, '_urlpatterns'):
- self._urlpatterns = patterns('', *self.get_urlpatterns())
- return self._urlpatterns
-
-
-class DefaultRouter(BaseRouter):
- route_list = [
- (r'$', {
- 'get': 'list',
- 'post': 'create'
- }, 'list'),
- (r'(?P<pk>[^/]+)/$', {
- 'get': 'retrieve',
- 'put': 'update',
- 'patch': 'partial_update',
- 'delete': 'destroy'
- }, 'detail'),
+ def urls(self):
+ if not hasattr(self, '_urls'):
+ self._urls = patterns('', *self.get_urls())
+ return self._urls
+
+
+class SimpleRouter(BaseRouter):
+ routes = [
+ # List route.
+ (
+ r'^{prefix}/$',
+ {
+ 'get': 'list',
+ 'post': 'create'
+ },
+ '{basename}-list'
+ ),
+ # Detail route.
+ (
+ r'^{prefix}/{lookup}/$',
+ {
+ 'get': 'retrieve',
+ 'put': 'update',
+ 'patch': 'partial_update',
+ 'delete': 'destroy'
+ },
+ '{basename}-detail'
+ ),
+ # Dynamically generated routes.
+ # Generated using @action or @link decorators on methods of the viewset.
+ (
+ r'^{prefix}/{lookup}/{methodname}/$',
+ {
+ '{httpmethod}': '{methodname}',
+ },
+ '{basename}-{methodname}'
+ ),
]
- extra_routes = r'(?P<pk>[^/]+)/%s/$'
- name_format = '%s-%s'
- def get_urlpatterns(self):
+ def get_routes(self, viewset):
+ """
+ Augment `self.routes` with any dynamically generated routes.
+
+ Returns a list of 4-tuples, of the form:
+ `(url_format, method_map, name_format, extra_kwargs)`
+ """
+
+ # 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 prefix, viewset, base_name in self.registry:
- # Bind regular views
- if not getattr(viewset, '_is_viewset', False):
- regex = prefix
- view = viewset
- name = base_name
- ret.append(url(regex, view, name=name))
- continue
+ for url_format, method_map, name_format in self.routes:
+ if method_map == {'{httpmethod}': '{methodname}'}:
+ # Dynamic routes
+ for httpmethod, methodname in dynamic_routes.items():
+ extra_kwargs = getattr(viewset, methodname).kwargs
+ ret.append((
+ url_format.replace('{methodname}', methodname),
+ {httpmethod: methodname},
+ name_format.replace('{methodname}', methodname),
+ extra_kwargs
+ ))
+ else:
+ # Standard route
+ extra_kwargs = {}
+ ret.append((url_format, method_map, name_format, extra_kwargs))
- # Bind standard CRUD routes
- for suffix, action_mapping, action_name in self.route_list:
+ return ret
- # Only actions which actually exist on the viewset will be bound
- bound_actions = {}
- for method, action in action_mapping.items():
- if hasattr(viewset, action):
- bound_actions[method] = action
+ 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 = []
- # Build the url pattern
- regex = prefix + suffix
- view = viewset.as_view(bound_actions, name_suffix=action_name)
- name = self.name_format % (base_name, action_name)
- ret.append(url(regex, view, name=name))
+ for prefix, viewset, basename in self.registry:
+ lookup = self.get_lookup_regex(viewset)
+ routes = self.get_routes(viewset)
- # Bind any extra `@action` or `@link` routes
- for action_name in dir(viewset):
- func = getattr(viewset, action_name)
- http_method = getattr(func, 'bind_to_method', None)
+ for url_format, method_map, name_format, extra_kwargs in routes:
- # Skip if this is not an @action or @link method
- if not http_method:
+ # Only actions which actually exist on the viewset will be bound
+ method_map = self.get_method_map(viewset, method_map)
+ if not method_map:
continue
- suffix = self.extra_routes % action_name
-
# Build the url pattern
- regex = prefix + suffix
- view = viewset.as_view({http_method: action_name}, **func.kwargs)
- name = self.name_format % (base_name, action_name)
+ regex = url_format.format(prefix=prefix, lookup=lookup)
+ view = viewset.as_view(method_map, **extra_kwargs)
+ name = name_format.format(basename=basename)
ret.append(url(regex, view, name=name))
- # Return a list of url patterns
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][-1]
+ 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