aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/api-guide/routers.md8
-rw-r--r--docs/api-guide/viewsets.md2
-rw-r--r--docs/tutorial/6-viewsets-and-routers.md6
-rw-r--r--rest_framework/renderers.py2
-rw-r--r--rest_framework/routers.py72
-rw-r--r--rest_framework/utils/breadcrumbs.py3
-rw-r--r--rest_framework/utils/formatting.py7
-rw-r--r--rest_framework/viewsets.py10
8 files changed, 64 insertions, 46 deletions
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 2fda5373..7b211bfd 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -15,15 +15,15 @@ REST framework adds support for automatic URL routing to Django, and provides yo
Here's an example of a simple URL conf, that uses `DefaultRouter`.
router = routers.SimpleRouter()
- router.register(r'users', UserViewSet, 'user')
- router.register(r'accounts', AccountViewSet, 'account')
+ router.register(r'users', UserViewSet, name='user')
+ router.register(r'accounts', AccountViewSet, name='account')
urlpatterns = router.urls
There are three arguments to the `register()` method:
* `prefix` - The URL prefix to use for this set of routes.
* `viewset` - The viewset class.
-* `basename` - The base to use for the URL names that are created.
+* `name` - The base to use for the URL names that are created.
The example above would generate the following URL patterns:
@@ -119,4 +119,4 @@ The following example will only route to the `list` and `retrieve` actions, and
If you want to provide totally custom behavior, you can override `BaseRouter` and override the `get_urls()` method. The method should insect the registered viewsets and return a list of URL patterns. The registered prefix, viewset and basename tuples may be inspected by accessing the `self.registry` attribute.
-[cite]: http://guides.rubyonrails.org/routing.html \ No newline at end of file
+[cite]: http://guides.rubyonrails.org/routing.html
diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md
index 36a4dbd5..8af35bb8 100644
--- a/docs/api-guide/viewsets.md
+++ b/docs/api-guide/viewsets.md
@@ -42,7 +42,7 @@ If we need to, we can bind this viewset into two seperate views, like so:
Typically we wouldn't do this, but would instead register the viewset with a router, and allow the urlconf to be automatically generated.
router = DefaultRouter()
- router.register(r'users', UserViewSet, 'user')
+ router.register(r'users', UserViewSet, name='user')
urlpatterns = router.urls
Rather than writing your own viewsets, you'll often want to use the existing base classes that provide a default set of behavior. For example:
diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md
index 876d89ac..25a974a1 100644
--- a/docs/tutorial/6-viewsets-and-routers.md
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -105,8 +105,8 @@ Here's our re-wired `urls.py` file.
# Create a router and register our viewsets with it.
router = DefaultRouter()
- router.register(r'snippets', views.SnippetViewSet, 'snippet')
- router.register(r'users', views.UserViewSet, 'user')
+ router.register(r'snippets', views.SnippetViewSet, name='snippet')
+ router.register(r'users', views.UserViewSet, name='user')
# The API URLs are now determined automatically by the router.
# Additionally, we include the login URLs for the browseable API.
@@ -148,4 +148,4 @@ We've reached the end of our tutorial. If you want to get more involved in the
[sandbox]: http://restframework.herokuapp.com/
[github]: https://github.com/tomchristie/django-rest-framework
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-[twitter]: https://twitter.com/_tomchristie \ No newline at end of file
+[twitter]: https://twitter.com/_tomchristie
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 752306ad..a0829c8f 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -439,7 +439,7 @@ class BrowsableAPIRenderer(BaseRenderer):
return GenericContentForm()
def get_name(self, view):
- return get_view_name(view.__class__)
+ return get_view_name(view.__class__, getattr(view, 'suffix', None))
def get_description(self, view):
return get_view_description(view.__class__, html=True)
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index b7052218..3a8c4508 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -13,6 +13,7 @@ For example, you might have a `urls.py` that looks something like this:
urlpatterns = router.urls
"""
+from collections import namedtuple
from django.conf.urls import url, patterns
from django.db import models
from rest_framework.decorators import api_view
@@ -22,6 +23,9 @@ from rest_framework.viewsets import ModelViewSet
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
@@ -38,8 +42,8 @@ class BaseRouter(object):
def __init__(self):
self.registry = []
- def register(self, prefix, viewset, basename):
- self.registry.append((prefix, viewset, basename))
+ def register(self, prefix, viewset, name):
+ self.registry.append((prefix, viewset, name))
def get_urls(self):
raise NotImplemented('get_urls must be overridden')
@@ -54,33 +58,36 @@ class BaseRouter(object):
class SimpleRouter(BaseRouter):
routes = [
# List route.
- (
- r'^{prefix}/$',
- {
+ Route(
+ url=r'^{prefix}/$',
+ mapping={
'get': 'list',
'post': 'create'
},
- '{basename}-list'
+ name='{basename}-list',
+ initkwargs={'suffix': 'List'}
),
# Detail route.
- (
- r'^{prefix}/{lookup}/$',
- {
+ Route(
+ url=r'^{prefix}/{lookup}/$',
+ mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
- '{basename}-detail'
+ name='{basename}-detail',
+ initkwargs={'suffix': 'Instance'}
),
# Dynamically generated routes.
# Generated using @action or @link decorators on methods of the viewset.
- (
- r'^{prefix}/{lookup}/{methodname}/$',
- {
+ Route(
+ url=r'^{prefix}/{lookup}/{methodname}/$',
+ mapping={
'{httpmethod}': '{methodname}',
},
- '{basename}-{methodnamehyphen}'
+ name='{basename}-{methodnamehyphen}',
+ initkwargs={}
),
]
@@ -88,8 +95,7 @@ class SimpleRouter(BaseRouter):
"""
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)`
+ Returns a list of the Route namedtuple.
"""
# Determine any `@action` or `@link` decorated methods on the viewset
@@ -101,21 +107,21 @@ class SimpleRouter(BaseRouter):
dynamic_routes[httpmethod] = methodname
ret = []
- for url_format, method_map, name_format in self.routes:
- if method_map == {'{httpmethod}': '{methodname}'}:
+ for route in self.routes:
+ if route.mapping == {'{httpmethod}': '{methodname}'}:
# Dynamic routes (@link or @action decorator)
for httpmethod, methodname in dynamic_routes.items():
- extra_kwargs = getattr(viewset, methodname).kwargs
- ret.append((
- replace_methodname(url_format, methodname),
- {httpmethod: methodname},
- replace_methodname(name_format, methodname),
- extra_kwargs
+ 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
- extra_kwargs = {}
- ret.append((url_format, method_map, name_format, extra_kwargs))
+ ret.append(route)
return ret
@@ -150,17 +156,17 @@ class SimpleRouter(BaseRouter):
lookup = self.get_lookup_regex(viewset)
routes = self.get_routes(viewset)
- for url_format, method_map, name_format, extra_kwargs in routes:
+ for route in routes:
# Only actions which actually exist on the viewset will be bound
- method_map = self.get_method_map(viewset, method_map)
- if not method_map:
+ mapping = self.get_method_map(viewset, route.mapping)
+ if not mapping:
continue
# Build the url pattern
- regex = url_format.format(prefix=prefix, lookup=lookup)
- view = viewset.as_view(method_map, **extra_kwargs)
- name = name_format.format(basename=basename)
+ 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
@@ -179,7 +185,7 @@ class DefaultRouter(SimpleRouter):
Return a view to use as the API root.
"""
api_root_dict = {}
- list_name = self.routes[0][-1]
+ list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
diff --git a/rest_framework/utils/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py
index 18b3b207..8f8e5710 100644
--- a/rest_framework/utils/breadcrumbs.py
+++ b/rest_framework/utils/breadcrumbs.py
@@ -21,7 +21,8 @@ def get_breadcrumbs(url):
# Don't list the same view twice in a row.
# Probably an optional trailing slash.
if not seen or seen[-1] != view:
- breadcrumbs_list.insert(0, (get_view_name(view.cls), prefix + url))
+ suffix = getattr(view, 'suffix', None)
+ breadcrumbs_list.insert(0, (get_view_name(view.cls, suffix), prefix + url))
seen.append(view)
if url == '':
diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py
index 79566db1..ebadb3a6 100644
--- a/rest_framework/utils/formatting.py
+++ b/rest_framework/utils/formatting.py
@@ -45,14 +45,17 @@ def _camelcase_to_spaces(content):
return ' '.join(content.split('_')).title()
-def get_view_name(cls):
+def get_view_name(cls, suffix=None):
"""
Return a formatted name for an `APIView` class or `@api_view` function.
"""
name = cls.__name__
name = _remove_trailing_string(name, 'View')
name = _remove_trailing_string(name, 'ViewSet')
- return _camelcase_to_spaces(name)
+ name = _camelcase_to_spaces(name)
+ if suffix:
+ name += ' ' + suffix
+ return name
def get_view_description(cls, html=False):
diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py
index 9133fd44..bd25df77 100644
--- a/rest_framework/viewsets.py
+++ b/rest_framework/viewsets.py
@@ -35,12 +35,16 @@ class ViewSetMixin(object):
"""
@classonlymethod
- def as_view(cls, actions=None, name_suffix=None, **initkwargs):
+ def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
+ # The suffix initkwarg is reserved for identifing the viewset type
+ # eg. 'List' or 'Instance'.
+ cls.suffix = None
+
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
@@ -74,7 +78,11 @@ class ViewSetMixin(object):
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
+ # We need to set these on the view function, so that breadcrumb
+ # generation can pick out these bits of information from a
+ # resolved URL.
view.cls = cls
+ view.suffix = initkwargs.get('suffix', None)
return view