Refactored get_name()/get_description()

This commit is contained in:
Ben Timby 2012-01-23 13:32:37 -05:00
parent 2d8739115c
commit 7fa3a214fb
6 changed files with 119 additions and 156 deletions

View File

@ -12,10 +12,9 @@ from django.template import RequestContext, loader
from django.utils import simplejson as json
from djangorestframework.compat import apply_markdown, yaml
from djangorestframework.compat import yaml
from djangorestframework.utils import dict2xml, url_resolves
from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.utils.description import get_name, get_description
from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
from djangorestframework import VERSION
@ -226,6 +225,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
return content
def _get_form_instance(self, view, method):
"""
Get a form, possibly bound to either the input or output data.
@ -261,6 +261,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
return form_instance
def _get_generic_content_form(self, view):
"""
Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
@ -296,6 +297,20 @@ class DocumentingTemplateRenderer(BaseRenderer):
# Okey doke, let's do it
return GenericContentForm(view)
def get_name(self):
try:
return self.view.get_name()
except AttributeError:
return self.view.__doc__
def get_description(self, html=None):
if html is None:
html = bool('html' in self.format)
try:
return self.view.get_description(html)
except AttributeError:
return self.view.__doc__
def render(self, obj=None, media_type=None):
"""
Renders *obj* using the :attr:`template` set on the class.
@ -316,15 +331,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
login_url = None
logout_url = None
name = get_name(self.view)
description = get_description(self.view)
markeddown = None
if apply_markdown:
try:
markeddown = apply_markdown(description)
except AttributeError:
markeddown = None
name = self.get_name()
description = self.get_description()
breadcrumb_list = get_breadcrumbs(self.view.request.path)
@ -332,12 +340,11 @@ class DocumentingTemplateRenderer(BaseRenderer):
context = RequestContext(self.view.request, {
'content': content,
'view': self.view,
'request': self.view.request, # TODO: remove
'request': self.view.request, # TODO: remove
'response': self.view.response,
'description': description,
'name': name,
'version': VERSION,
'markeddown': markeddown,
'breadcrumblist': breadcrumb_list,
'available_formats': self.view._rendered_formats,
'put_form': put_form_instance,
@ -395,14 +402,12 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
template = 'renderer.txt'
DEFAULT_RENDERERS = (
JSONRenderer,
JSONPRenderer,
DocumentingHTMLRenderer,
DocumentingXHTMLRenderer,
DocumentingPlainTextRenderer,
XMLRenderer
)
DEFAULT_RENDERERS = ( JSONRenderer,
JSONPRenderer,
DocumentingHTMLRenderer,
DocumentingXHTMLRenderer,
DocumentingPlainTextRenderer,
XMLRenderer )
if YAMLRenderer:
DEFAULT_RENDERERS += (YAMLRenderer,)

View File

@ -11,13 +11,8 @@
/* Custom styles */
.version{font-size:8px;}
</style>
{% if ADMIN_MEDIA_PREFIX %}
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/>
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/>
{% else %}
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/base.css'/>
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/forms.css'/>
{% endif %}
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/>
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/>
<title>Django REST framework - {{ name }}</title>
</head>
<body>
@ -50,7 +45,7 @@
<div class='content-main'>
<h1>{{ name }}</h1>
<p>{% if markeddown %}{% autoescape off %}{{ markeddown }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %}</p>
<p>{% autoescape off %}{{ description }}{% endautoescape %}</p>
<div class='module'>
<pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}

View File

@ -1,7 +1,6 @@
from django.test import TestCase
from djangorestframework.views import View
from djangorestframework.compat import apply_markdown
from djangorestframework.utils.description import get_name, get_description
# We check that docstrings get nicely un-indented.
DESCRIPTION = """an example docstring
@ -51,15 +50,15 @@ class TestViewNamesAndDescriptions(TestCase):
"""Ensure Resource names are based on the classname by default."""
class MockView(View):
pass
self.assertEquals(get_name(MockView()), 'Mock')
self.assertEquals(MockView().get_name(), 'Mock')
# This has been turned off now.
#def test_resource_name_can_be_set_explicitly(self):
# """Ensure Resource names can be set using the 'name' class attribute."""
# example = 'Some Other Name'
# class MockView(View):
# name = example
# self.assertEquals(get_name(MockView()), example)
def test_resource_name_can_be_set_explicitly(self):
"""Ensure Resource names can be set using the 'get_name' method."""
example = 'Some Other Name'
class MockView(View):
def get_name(self):
return example
self.assertEquals(MockView().get_name(), example)
def test_resource_description_uses_docstring_by_default(self):
"""Ensure Resource names are based on the docstring by default."""
@ -79,29 +78,30 @@ class TestViewNamesAndDescriptions(TestCase):
# hash style header #"""
self.assertEquals(get_description(MockView()), DESCRIPTION)
self.assertEquals(MockView().get_description(), DESCRIPTION)
# This has been turned off now
#def test_resource_description_can_be_set_explicitly(self):
# """Ensure Resource descriptions can be set using the 'description' class attribute."""
# example = 'Some other description'
# class MockView(View):
# """docstring"""
# description = example
# self.assertEquals(get_description(MockView()), example)
def test_resource_description_can_be_set_explicitly(self):
"""Ensure Resource descriptions can be set using the 'get_description' method."""
example = 'Some other description'
class MockView(View):
"""docstring"""
def get_description(self):
return example
self.assertEquals(MockView().get_description(), example)
#def test_resource_description_does_not_require_docstring(self):
# """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute."""
# example = 'Some other description'
# class MockView(View):
# description = example
# self.assertEquals(get_description(MockView()), example)
def test_resource_description_does_not_require_docstring(self):
"""Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'get_description' method."""
example = 'Some other description'
class MockView(View):
def get_description(self):
return example
self.assertEquals(MockView().get_description(), example)
def test_resource_description_can_be_empty(self):
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string"""
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string."""
class MockView(View):
pass
self.assertEquals(get_description(MockView()), '')
self.assertEquals(MockView().get_description(), '')
def test_markdown(self):
"""Ensure markdown to HTML works as expected"""

View File

@ -1,6 +1,4 @@
from django.core.urlresolvers import resolve
from djangorestframework.utils.description import get_name
def get_breadcrumbs(url):
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
@ -17,7 +15,7 @@ def get_breadcrumbs(url):
else:
# Check if this is a REST framework view, and if so add it to the breadcrumbs
if isinstance(getattr(view, 'cls_instance', None), View):
breadcrumbs_list.insert(0, (get_name(view), url))
breadcrumbs_list.insert(0, (view.cls_instance.get_name(), url))
if url == '':
# All done

View File

@ -1,88 +0,0 @@
"""
Get a descriptive name and description for a view.
"""
import re
from djangorestframework.resources import Resource, FormResource, ModelResource
# These a a bit Grungy, but they do the job.
def get_name(view):
"""
Return a name for the view.
If view has a name attribute, use that, otherwise use the view's class name, with 'CamelCaseNames' converted to 'Camel Case Names'.
"""
# If we're looking up the name of a view callable, as found by reverse,
# grok the class instance that we stored when as_view was called.
if getattr(view, 'cls_instance', None):
view = view.cls_instance
# If this view has a resource that's been overridden, then use that resource for the name
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
name = view.resource.__name__
# Chomp of any non-descriptive trailing part of the resource class name
if name.endswith('Resource') and name != 'Resource':
name = name[:-len('Resource')]
# If the view has a descriptive suffix, eg '*** List', '*** Instance'
if getattr(view, '_suffix', None):
name += view._suffix
# Otherwise if it's a function view use the function's name
elif getattr(view, '__name__', None) is not None:
name = view.__name__
# If it's a view class with no resource then grok the name from the class name
elif getattr(view, '__class__', None) is not None:
name = view.__class__.__name__
# Chomp of any non-descriptive trailing part of the view class name
if name.endswith('View') and name != 'View':
name = name[:-len('View')]
# I ain't got nuthin fo' ya
else:
return ''
return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', name).strip()
def get_description(view):
"""
Provide a description for the view.
By default this is the view's docstring with nice unindention applied.
"""
# If we're looking up the name of a view callable, as found by reverse,
# grok the class instance that we stored when as_view was called.
if getattr(view, 'cls_instance', None):
view = view.cls_instance
# If this view has a resource that's been overridden, then use the resource's doctring
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
doc = view.resource.__doc__
# Otherwise use the view doctring
elif getattr(view, '__doc__', None):
doc = view.__doc__
# I ain't got nuthin fo' ya
else:
return ''
if not doc:
return ''
whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()]
# unindent the docstring if needed
if whitespace_counts:
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc)
# otherwise return it as-is
return doc

View File

@ -5,15 +5,17 @@ be subclassing in your implementation.
By setting or modifying class attributes on your view, you change it's predefined behaviour.
"""
import re
from django.core.urlresolvers import set_script_prefix, get_script_prefix
from django.http import HttpResponse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View as DjangoView
from djangorestframework.compat import View as DjangoView, apply_markdown
from djangorestframework.response import Response, ErrorResponse
from djangorestframework.mixins import *
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
from djangorestframework.utils.description import get_name, get_description
__all__ = (
@ -25,6 +27,7 @@ __all__ = (
)
class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
"""
Handles incoming requests and maps them to REST operations.
@ -47,13 +50,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
List of parsers the resource can parse the request with.
"""
authentication = (authentication.UserLoggedInAuthentication,
authentication.BasicAuthentication)
authentication = ( authentication.UserLoggedInAuthentication,
authentication.BasicAuthentication )
"""
List of all authenticating methods to attempt.
"""
permissions = (permissions.FullAnonAccess,)
permissions = ( permissions.FullAnonAccess, )
"""
List of all permissions that must be checked.
"""
@ -76,6 +79,59 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
"""
return [method.upper() for method in self.http_method_names if hasattr(self, method)]
def get_name(self):
"""
Return the resource or view class name for use as this view's name.
Override to customize.
"""
# If this view has a resource that's been overridden, then use that resource for the name
if getattr(self, 'resource', None) not in (None, resources.Resource, resources.FormResource, resources.ModelResource):
name = self.resource.__name__
# Chomp of any non-descriptive trailing part of the resource class name
if name.endswith('Resource') and name != 'Resource':
name = name[:-len('Resource')]
# If the view has a descriptive suffix, eg '*** List', '*** Instance'
if getattr(self, '_suffix', None):
name += self._suffix
# If it's a view class with no resource then grok the name from the class name
elif getattr(self, '__class__', None) is not None:
name = self.__class__.__name__
# Chomp of any non-descriptive trailing part of the view class name
if name.endswith('View') and name != 'View':
name = name[:-len('View')]
else:
name = ''
return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', name).strip()
def get_description(self, html=False):
"""
Return the resource or view docstring for use as this view's description.
Override to customize.
"""
# If this view has a resource that's been overridden, then use the resource's doctring
if getattr(self, 'resource', None) not in (None, resources.Resource, resources.FormResource, resources.ModelResource):
doc = self.resource.__doc__
# Otherwise use the view doctring
elif getattr(self, '__doc__', None):
doc = self.__doc__
else:
doc = ''
whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()]
# unindent the docstring if needed
if whitespace_counts:
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
doc = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc)
if doc and html:
if apply_markdown:
doc = apply_markdown(doc)
else:
doc = escape(doc)
doc = mark_safe(doc.replace('\n', '<br />'))
return doc
def http_method_not_allowed(self, request, *args, **kwargs):
"""
Return an HTTP 405 error if an operation is called which does not have a handler method.
@ -161,8 +217,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
def options(self, request, *args, **kwargs):
response_obj = {
'name': get_name(self),
'description': get_description(self),
'name': self.get_name(),
'description': self.get_description(),
'renders': self._rendered_media_types,
'parses': self._parsed_media_types,
}
@ -184,21 +240,18 @@ class ModelView(View):
"""
resource = resources.ModelResource
class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
"""
A view which provides default operations for read/update/delete against a model instance.
"""
_suffix = 'Instance'
class ListModelView(ListModelMixin, ModelView):
"""
A view which provides default operations for list, against a model in the database.
"""
_suffix = 'List'
class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
"""
A view which provides default operations for list and create, against a model in the database.