Implement docstring rendering for HTTP methods. Refactored get_name/get_description to use View methods for overriding.

This commit is contained in:
Ben Timby 2012-01-19 15:07:24 -05:00
parent 0a5ca000ed
commit 1db6b7cae4
5 changed files with 87 additions and 48 deletions

View File

@ -322,12 +322,21 @@ class DocumentingTemplateRenderer(BaseRenderer):
name = get_name(self.view) name = get_name(self.view)
description = get_description(self.view) description = get_description(self.view)
markeddown = None markeddown = {}
if apply_markdown: if apply_markdown:
try: try:
markeddown = apply_markdown(description) markeddown['view'] = apply_markdown(description)
except AttributeError: except AttributeError:
markeddown = None markeddown.pop('view', None)
for method in self.view.allowed_methods:
methodfunc = getattr(self.view, method.lower(), None)
if methodfunc is None:
continue
methoddesc = get_description(methodfunc)
try:
markeddown[method] = apply_markdown(methoddesc)
except AttributeError:
markeddown.pop(method, None)
breadcrumb_list = get_breadcrumbs(self.view.request.path) breadcrumb_list = get_breadcrumbs(self.view.request.path)

View File

@ -45,7 +45,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>{% if markeddown.view %}{% autoescape off %}{{ markeddown.view }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %}</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 }}
@ -56,6 +56,7 @@
<form> <form>
<fieldset class='module aligned'> <fieldset class='module aligned'>
<h2>GET {{ name }}</h2> <h2>GET {{ name }}</h2>
{% if markeddown.GET %}<p>{% autoescape off %}{{ markeddown.GET }}{% endautoescape %}</p>{% endif %}
<div class='submit-row' style='margin: 0; border: 0'> <div class='submit-row' style='margin: 0; border: 0'>
<a href='{{ request.get_full_path }}' rel="nofollow" style='float: left'>GET</a> <a href='{{ request.get_full_path }}' rel="nofollow" style='float: left'>GET</a>
{% for format in available_formats %} {% for format in available_formats %}
@ -75,6 +76,7 @@
<form action="{{ request.get_full_path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}> <form action="{{ request.get_full_path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
<fieldset class='module aligned'> <fieldset class='module aligned'>
<h2>POST {{ name }}</h2> <h2>POST {{ name }}</h2>
{% if markeddown.POST %}<p>{% autoescape off %}{{ markeddown.POST }}{% endautoescape %}</p>{% endif %}
{% csrf_token %} {% csrf_token %}
{{ post_form.non_field_errors }} {{ post_form.non_field_errors }}
{% for field in post_form %} {% for field in post_form %}
@ -96,6 +98,7 @@
<form action="{{ request.get_full_path }}" method="post" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %}> <form action="{{ request.get_full_path }}" method="post" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
<fieldset class='module aligned'> <fieldset class='module aligned'>
<h2>PUT {{ name }}</h2> <h2>PUT {{ name }}</h2>
{% if markeddown.PUT %}<p>{% autoescape off %}{{ markeddown.PUT }}{% endautoescape %}</p>{% endif %}
<input type="hidden" name="{{ METHOD_PARAM }}" value="PUT" /> <input type="hidden" name="{{ METHOD_PARAM }}" value="PUT" />
{% csrf_token %} {% csrf_token %}
{{ put_form.non_field_errors }} {{ put_form.non_field_errors }}
@ -118,6 +121,7 @@
<form action="{{ request.get_full_path }}" method="post"> <form action="{{ request.get_full_path }}" method="post">
<fieldset class='module aligned'> <fieldset class='module aligned'>
<h2>DELETE {{ name }}</h2> <h2>DELETE {{ name }}</h2>
{% if markeddown.DELETE %}<p>{% autoescape off %}{{ markeddown.DELETE }}{% endautoescape %}</p>{% endif %}
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="{{ METHOD_PARAM }}" value="DELETE" /> <input type="hidden" name="{{ METHOD_PARAM }}" value="DELETE" />
<div class='submit-row' style='margin: 0; border: 0'> <div class='submit-row' style='margin: 0; border: 0'>

View File

@ -53,13 +53,13 @@ class TestViewNamesAndDescriptions(TestCase):
pass pass
self.assertEquals(get_name(MockView()), 'Mock') self.assertEquals(get_name(MockView()), '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(get_name(MockView()), 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."""
@ -81,21 +81,22 @@ class TestViewNamesAndDescriptions(TestCase):
self.assertEquals(get_description(MockView()), DESCRIPTION) self.assertEquals(get_description(MockView()), 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(get_description(MockView()), 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(get_description(MockView()), 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"""

View File

@ -19,30 +19,14 @@ def get_name(view):
if getattr(view, 'cls_instance', None): if getattr(view, 'cls_instance', None):
view = view.cls_instance view = view.cls_instance
# If this view has a resource that's been overridden, then use that resource for the name # If this view provides a get_name method, try to use that:
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource): if callable(getattr(view, 'get_name', None)):
name = view.resource.__name__ name = view.get_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 # Otherwise if it's a function view use the function's name
elif getattr(view, '__name__', None) is not None: elif getattr(view, '__name__', None) is not None:
name = view.__name__ 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 # I ain't got nuthin fo' ya
else: else:
return '' return ''
@ -63,10 +47,9 @@ def get_description(view):
if getattr(view, 'cls_instance', None): if getattr(view, 'cls_instance', None):
view = view.cls_instance view = view.cls_instance
# If this view provides a get_description method, try to use that:
# If this view has a resource that's been overridden, then use the resource's doctring if callable(getattr(view, 'get_description', None)):
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource): doc = view.get_description()
doc = view.resource.__doc__
# Otherwise use the view doctring # Otherwise use the view doctring
elif getattr(view, '__doc__', None): elif getattr(view, '__doc__', None):

View File

@ -77,6 +77,48 @@ 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 name
def get_description(self):
"""
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 = ''
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.