mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-23 10:03:57 +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 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 import dict2xml, url_resolves
|
||||||
from djangorestframework.utils.breadcrumbs import get_breadcrumbs
|
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.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
|
||||||
from djangorestframework import VERSION
|
from djangorestframework import VERSION
|
||||||
|
|
||||||
|
@ -296,6 +295,20 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
# Okey doke, let's do it
|
# Okey doke, let's do it
|
||||||
return GenericContentForm(view)
|
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):
|
def render(self, obj=None, media_type=None):
|
||||||
"""
|
"""
|
||||||
Renders *obj* using the :attr:`template` set on the class.
|
Renders *obj* using the :attr:`template` set on the class.
|
||||||
|
@ -316,15 +329,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
login_url = None
|
login_url = None
|
||||||
logout_url = None
|
logout_url = None
|
||||||
|
|
||||||
name = get_name(self.view)
|
name = self.get_name()
|
||||||
description = get_description(self.view)
|
description = self.get_description()
|
||||||
|
|
||||||
markeddown = None
|
|
||||||
if apply_markdown:
|
|
||||||
try:
|
|
||||||
markeddown = apply_markdown(description)
|
|
||||||
except AttributeError:
|
|
||||||
markeddown = None
|
|
||||||
|
|
||||||
breadcrumb_list = get_breadcrumbs(self.view.request.path)
|
breadcrumb_list = get_breadcrumbs(self.view.request.path)
|
||||||
|
|
||||||
|
@ -337,7 +343,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
'description': description,
|
'description': description,
|
||||||
'name': name,
|
'name': name,
|
||||||
'version': VERSION,
|
'version': VERSION,
|
||||||
'markeddown': markeddown,
|
|
||||||
'breadcrumblist': breadcrumb_list,
|
'breadcrumblist': breadcrumb_list,
|
||||||
'available_formats': self.view._rendered_formats,
|
'available_formats': self.view._rendered_formats,
|
||||||
'put_form': put_form_instance,
|
'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/base.css'/>
|
||||||
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/forms.css'/>
|
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/forms.css'/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<title>Django REST framework - {{ name }}</title>
|
<title>Django REST framework - {{ name }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
<div class='content-main'>
|
<div class='content-main'>
|
||||||
<h1>{{ name }}</h1>
|
<h1>{{ name }}</h1>
|
||||||
<p>{% if markeddown %}{% autoescape off %}{{ markeddown }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %}</p>
|
<p>{% autoescape off %}{{ description }}{% endautoescape %}</p>
|
||||||
<div class='module'>
|
<div class='module'>
|
||||||
<pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
|
<pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
|
||||||
{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
|
{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework.compat import apply_markdown
|
from djangorestframework.compat import apply_markdown
|
||||||
from djangorestframework.utils.description import get_name, get_description
|
|
||||||
|
|
||||||
# We check that docstrings get nicely un-indented.
|
# We check that docstrings get nicely un-indented.
|
||||||
DESCRIPTION = """an example docstring
|
DESCRIPTION = """an example docstring
|
||||||
|
@ -51,15 +50,15 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
"""Ensure Resource names are based on the classname by default."""
|
"""Ensure Resource names are based on the classname by default."""
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
pass
|
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):
|
||||||
#def test_resource_name_can_be_set_explicitly(self):
|
"""Ensure Resource names can be set using the 'get_name' method."""
|
||||||
# """Ensure Resource names can be set using the 'name' class attribute."""
|
example = 'Some Other Name'
|
||||||
# example = 'Some Other Name'
|
class MockView(View):
|
||||||
# class MockView(View):
|
def get_name(self):
|
||||||
# name = example
|
return example
|
||||||
# self.assertEquals(get_name(MockView()), example)
|
self.assertEquals(MockView().get_name(), example)
|
||||||
|
|
||||||
def test_resource_description_uses_docstring_by_default(self):
|
def test_resource_description_uses_docstring_by_default(self):
|
||||||
"""Ensure Resource names are based on the docstring by default."""
|
"""Ensure Resource names are based on the docstring by default."""
|
||||||
|
@ -79,29 +78,30 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
|
|
||||||
# hash style header #"""
|
# 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):
|
||||||
#def test_resource_description_can_be_set_explicitly(self):
|
"""Ensure Resource descriptions can be set using the 'get_description' method."""
|
||||||
# """Ensure Resource descriptions can be set using the 'description' class attribute."""
|
example = 'Some other description'
|
||||||
# example = 'Some other description'
|
class MockView(View):
|
||||||
# class MockView(View):
|
"""docstring"""
|
||||||
# """docstring"""
|
def get_description(self):
|
||||||
# description = example
|
return example
|
||||||
# self.assertEquals(get_description(MockView()), example)
|
self.assertEquals(MockView().get_description(), example)
|
||||||
|
|
||||||
#def test_resource_description_does_not_require_docstring(self):
|
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."""
|
"""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'
|
example = 'Some other description'
|
||||||
# class MockView(View):
|
class MockView(View):
|
||||||
# description = example
|
def get_description(self):
|
||||||
# self.assertEquals(get_description(MockView()), example)
|
return example
|
||||||
|
self.assertEquals(MockView().get_description(), example)
|
||||||
|
|
||||||
def test_resource_description_can_be_empty(self):
|
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):
|
class MockView(View):
|
||||||
pass
|
pass
|
||||||
self.assertEquals(get_description(MockView()), '')
|
self.assertEquals(MockView().get_description(), '')
|
||||||
|
|
||||||
def test_markdown(self):
|
def test_markdown(self):
|
||||||
"""Ensure markdown to HTML works as expected"""
|
"""Ensure markdown to HTML works as expected"""
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
from django.core.urlresolvers import resolve
|
from django.core.urlresolvers import resolve
|
||||||
from djangorestframework.utils.description import get_name
|
|
||||||
|
|
||||||
|
|
||||||
def get_breadcrumbs(url):
|
def get_breadcrumbs(url):
|
||||||
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, 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:
|
else:
|
||||||
# Check if this is a REST framework view, and if so add it to the breadcrumbs
|
# Check if this is a REST framework view, and if so add it to the breadcrumbs
|
||||||
if isinstance(getattr(view, 'cls_instance', None), View):
|
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 == '':
|
if url == '':
|
||||||
# All done
|
# 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.
|
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.core.urlresolvers import set_script_prefix, get_script_prefix
|
||||||
from django.http import HttpResponse
|
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 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.response import Response, ErrorResponse
|
||||||
from djangorestframework.mixins import *
|
from djangorestframework.mixins import *
|
||||||
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
|
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
|
||||||
from djangorestframework.utils.description import get_name, get_description
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -48,7 +50,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
authentication = (authentication.UserLoggedInAuthentication,
|
authentication = (authentication.UserLoggedInAuthentication,
|
||||||
authentication.BasicAuthentication)
|
authentication.BasicAuthentication)
|
||||||
"""
|
"""
|
||||||
List of all authenticating methods to attempt.
|
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)]
|
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):
|
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.
|
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):
|
def options(self, request, *args, **kwargs):
|
||||||
response_obj = {
|
response_obj = {
|
||||||
'name': get_name(self),
|
'name': self.get_name(),
|
||||||
'description': get_description(self),
|
'description': self.get_description(),
|
||||||
'renders': self._rendered_media_types,
|
'renders': self._rendered_media_types,
|
||||||
'parses': self._parsed_media_types,
|
'parses': self._parsed_media_types,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user