mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
Factor view names/descriptions out of View class
This commit is contained in:
parent
9e24db022c
commit
f68721ade8
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 == '':
|
||||
|
|
77
rest_framework/utils/formatting.py
Normal file
77
rest_framework/utils/formatting.py
Normal file
|
@ -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)
|
|
@ -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],
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user