diff options
| -rw-r--r-- | docs/api-guide/routers.md | 8 | ||||
| -rw-r--r-- | docs/api-guide/viewsets.md | 2 | ||||
| -rw-r--r-- | docs/tutorial/6-viewsets-and-routers.md | 6 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 2 | ||||
| -rw-r--r-- | rest_framework/routers.py | 72 | ||||
| -rw-r--r-- | rest_framework/utils/breadcrumbs.py | 3 | ||||
| -rw-r--r-- | rest_framework/utils/formatting.py | 7 | ||||
| -rw-r--r-- | rest_framework/viewsets.py | 10 | 
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 | 
