aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2013-04-04 21:42:26 +0100
committerTom Christie2013-04-04 21:42:26 +0100
commitf68721ade8d66806296323116ff9a61773ad2be1 (patch)
treed9f44f935ad338f7accb9500383c15bf30bc3621 /rest_framework
parent9e24db022cd8da1a588dd43e6239e07798881c02 (diff)
downloaddjango-rest-framework-f68721ade8d66806296323116ff9a61773ad2be1.tar.bz2
Factor view names/descriptions out of View class
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/renderers.py11
-rw-r--r--rest_framework/routers.py34
-rw-r--r--rest_framework/utils/breadcrumbs.py5
-rw-r--r--rest_framework/utils/formatting.py77
-rw-r--r--rest_framework/views.py89
-rw-r--r--rest_framework/viewsets.py5
6 files changed, 117 insertions, 104 deletions
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 4c15e0db..752306ad 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -24,6 +24,7 @@ from rest_framework.settings import api_settings
from rest_framework.request import clone_request
from rest_framework.utils import encoders
from rest_framework.utils.breadcrumbs import get_breadcrumbs
+from rest_framework.utils.formatting import get_view_name, get_view_description
from rest_framework import exceptions, parsers, status, VERSION
@@ -438,16 +439,10 @@ class BrowsableAPIRenderer(BaseRenderer):
return GenericContentForm()
def get_name(self, view):
- try:
- return view.get_name()
- except AttributeError:
- return smart_text(view.__class__.__name__)
+ return get_view_name(view.__class__)
def get_description(self, view):
- try:
- return view.get_description(html=True)
- except AttributeError:
- return smart_text(view.__doc__ or '')
+ return get_view_description(view.__class__, html=True)
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 283add8d..c37909ff 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -14,23 +14,31 @@ class BaseRouter(object):
@property
def urlpatterns(self):
if not hasattr(self, '_urlpatterns'):
- print self.get_urlpatterns()
self._urlpatterns = patterns('', *self.get_urlpatterns())
return self._urlpatterns
class DefaultRouter(BaseRouter):
route_list = [
- (r'$', {'get': 'list', 'post': 'create'}, '%s-list'),
- (r'(?P<pk>[^/]+)/$', {'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}, '%s-detail'),
+ (r'$', {'get': 'list', 'post': 'create'}, 'list'),
+ (r'(?P<pk>[^/]+)/$', {'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}, 'detail'),
]
- extra_routes = (r'(?P<pk>[^/]+)/%s/$', '%s-%s')
+ extra_routes = r'(?P<pk>[^/]+)/%s/$'
+ name_format = '%s-%s'
def get_urlpatterns(self):
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
+
# Bind standard CRUD routes
- for suffix, action_mapping, name_format in self.route_list:
+ for suffix, action_mapping, action_name in self.route_list:
# Only actions which actually exist on the viewset will be bound
bound_actions = {}
@@ -40,25 +48,25 @@ class DefaultRouter(BaseRouter):
# Build the url pattern
regex = prefix + suffix
- view = viewset.as_view(bound_actions)
- name = name_format % base_name
+ 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))
# Bind any extra `@action` or `@link` routes
- for attr in dir(viewset):
- func = getattr(viewset, attr)
+ for action_name in dir(viewset):
+ func = getattr(viewset, action_name)
http_method = getattr(func, 'bind_to_method', None)
# Skip if this is not an @action or @link method
if not http_method:
continue
- regex_format, name_format = self.extra_routes
+ suffix = self.extra_routes % action_name
# Build the url pattern
- regex = regex_format % attr
- view = viewset.as_view({http_method: attr}, **func.kwargs)
- name = name_format % (base_name, attr)
+ regex = prefix + suffix
+ view = viewset.as_view({http_method: action_name}, **func.kwargs)
+ name = self.name_format % (base_name, action_name)
ret.append(url(regex, view, name=name))
# Return a list of url patterns
diff --git a/rest_framework/utils/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py
index af21ac79..18b3b207 100644
--- a/rest_framework/utils/breadcrumbs.py
+++ b/rest_framework/utils/breadcrumbs.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
from django.core.urlresolvers import resolve, get_script_prefix
+from rest_framework.utils.formatting import get_view_name
def get_breadcrumbs(url):
@@ -16,11 +17,11 @@ def get_breadcrumbs(url):
pass
else:
# Check if this is a REST framework view, and if so add it to the breadcrumbs
- if isinstance(getattr(view, 'cls_instance', None), APIView):
+ if issubclass(getattr(view, 'cls', None), APIView):
# 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, (view.cls_instance.get_name(), prefix + url))
+ breadcrumbs_list.insert(0, (get_view_name(view.cls), prefix + url))
seen.append(view)
if url == '':
diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py
new file mode 100644
index 00000000..79566db1
--- /dev/null
+++ b/rest_framework/utils/formatting.py
@@ -0,0 +1,77 @@
+"""
+Utility functions to return a formatted name and description for a given view.
+"""
+from __future__ import unicode_literals
+
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from rest_framework.compat import apply_markdown
+import re
+
+
+def _remove_trailing_string(content, trailing):
+ """
+ Strip trailing component `trailing` from `content` if it exists.
+ Used when generating names from view classes.
+ """
+ if content.endswith(trailing) and content != trailing:
+ return content[:-len(trailing)]
+ return content
+
+
+def _remove_leading_indent(content):
+ """
+ Remove leading indent from a block of text.
+ Used when generating descriptions from docstrings.
+ """
+ whitespace_counts = [len(line) - len(line.lstrip(' '))
+ for line in content.splitlines()[1:] if line.lstrip()]
+
+ # unindent the content if needed
+ if whitespace_counts:
+ whitespace_pattern = '^' + (' ' * min(whitespace_counts))
+ content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
+ content = content.strip('\n')
+ return content
+
+
+def _camelcase_to_spaces(content):
+ """
+ Translate 'CamelCaseNames' to 'Camel Case Names'.
+ Used when generating names from view classes.
+ """
+ camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
+ content = re.sub(camelcase_boundry, ' \\1', content).strip()
+ return ' '.join(content.split('_')).title()
+
+
+def get_view_name(cls):
+ """
+ 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)
+
+
+def get_view_description(cls, html=False):
+ """
+ Return a description for an `APIView` class or `@api_view` function.
+ """
+ description = cls.__doc__ or ''
+ description = _remove_leading_indent(description)
+ if html:
+ return markup_description(description)
+ return description
+
+
+def markup_description(description):
+ """
+ Apply HTML markup to the given description.
+ """
+ if apply_markdown:
+ description = apply_markdown(description)
+ else:
+ description = escape(description).replace('\n', '<br />')
+ return mark_safe(description)
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 81cbdcbb..12298ca5 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -4,51 +4,13 @@ Provides an APIView class that is used as the base of all class-based views.
from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
-from django.utils.html import escape
-from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
-from rest_framework.compat import View, apply_markdown
+from rest_framework.compat import View
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.settings import api_settings
-import re
-
-
-def _remove_trailing_string(content, trailing):
- """
- Strip trailing component `trailing` from `content` if it exists.
- Used when generating names from view classes.
- """
- if content.endswith(trailing) and content != trailing:
- return content[:-len(trailing)]
- return content
-
-
-def _remove_leading_indent(content):
- """
- Remove leading indent from a block of text.
- Used when generating descriptions from docstrings.
- """
- whitespace_counts = [len(line) - len(line.lstrip(' '))
- for line in content.splitlines()[1:] if line.lstrip()]
-
- # unindent the content if needed
- if whitespace_counts:
- whitespace_pattern = '^' + (' ' * min(whitespace_counts))
- content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
- content = content.strip('\n')
- return content
-
-
-def _camelcase_to_spaces(content):
- """
- Translate 'CamelCaseNames' to 'Camel Case Names'.
- Used when generating names from view classes.
- """
- camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
- content = re.sub(camelcase_boundry, ' \\1', content).strip()
- return ' '.join(content.split('_')).title()
+from rest_framework.utils.formatting import get_view_name, get_view_description
class APIView(View):
@@ -64,13 +26,13 @@ class APIView(View):
@classmethod
def as_view(cls, **initkwargs):
"""
- Override the default :meth:`as_view` to store an instance of the view
- as an attribute on the callable function. This allows us to discover
- information about the view when we do URL reverse lookups.
+ Store the original class on the view function.
+
+ This allows us to discover information about the view when we do URL
+ reverse lookups. Used for breadcrumb generation.
"""
- # TODO: deprecate?
view = super(APIView, cls).as_view(**initkwargs)
- view.cls_instance = cls(**initkwargs)
+ view.cls = cls
return view
@property
@@ -90,43 +52,10 @@ class APIView(View):
'Vary': 'Accept'
}
- def get_name(self):
- """
- Return the resource or view class name for use as this view's name.
- Override to customize.
- """
- # TODO: deprecate?
- name = self.__class__.__name__
- name = _remove_trailing_string(name, 'View')
- return _camelcase_to_spaces(name)
-
- def get_description(self, html=False):
- """
- Return the resource or view docstring for use as this view's description.
- Override to customize.
- """
- # TODO: deprecate?
- description = self.__doc__ or ''
- description = _remove_leading_indent(description)
- if html:
- return self.markup_description(description)
- return description
-
- def markup_description(self, description):
- """
- Apply HTML markup to the description of this view.
- """
- # TODO: deprecate?
- if apply_markdown:
- description = apply_markdown(description)
- else:
- description = escape(description).replace('\n', '<br />')
- return mark_safe(description)
-
def metadata(self, request):
return {
- 'name': self.get_name(),
- 'description': self.get_description(),
+ 'name': get_view_name(self.__class__),
+ 'description': get_view_description(self.__class__),
'renders': [renderer.media_type for renderer in self.renderer_classes],
'parses': [parser.media_type for parser in self.parser_classes],
}
diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py
index 887a9722..0818c0d9 100644
--- a/rest_framework/viewsets.py
+++ b/rest_framework/viewsets.py
@@ -15,9 +15,10 @@ class ViewSetMixin(object):
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
"""
+ _is_viewset = True
@classonlymethod
- def as_view(cls, actions=None, **initkwargs):
+ def as_view(cls, actions=None, name_suffix=None, **initkwargs):
"""
Main entry point for a request-response process.
@@ -57,6 +58,8 @@ class ViewSetMixin(object):
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
+
+ view.cls = cls
return view