mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-27 03:54:01 +03:00
Merge remote-tracking branch 'btimby/description'
This commit is contained in:
commit
f5e54c7c32
|
@ -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
|
||||
|
||||
|
@ -296,6 +295,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 +329,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)
|
||||
|
||||
|
@ -337,7 +343,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
'description': description,
|
||||
'name': name,
|
||||
'version': VERSION,
|
||||
'markeddown': markeddown,
|
||||
'breadcrumblist': breadcrumb_list,
|
||||
'available_formats': self.view._rendered_formats,
|
||||
'put_form': put_form_instance,
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<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 %}
|
||||
<title>Django REST framework - {{ name }}</title>
|
||||
<title>Django REST framework - {{ name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
|
@ -50,7 +50,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 }}
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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__ = (
|
||||
|
@ -48,7 +50,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
"""
|
||||
|
||||
authentication = (authentication.UserLoggedInAuthentication,
|
||||
authentication.BasicAuthentication)
|
||||
authentication.BasicAuthentication)
|
||||
"""
|
||||
List of all authenticating methods to attempt.
|
||||
"""
|
||||
|
@ -76,6 +78,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 +216,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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user