diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index 42d80bd4d..823743985 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -322,12 +322,21 @@ class DocumentingTemplateRenderer(BaseRenderer): name = get_name(self.view) description = get_description(self.view) - markeddown = None + markeddown = {} if apply_markdown: try: - markeddown = apply_markdown(description) + markeddown['view'] = apply_markdown(description) 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) diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html index 132bdb818..94ea3f966 100644 --- a/djangorestframework/templates/renderer.html +++ b/djangorestframework/templates/renderer.html @@ -45,7 +45,7 @@

{{ name }}

-

{% if markeddown %}{% autoescape off %}{{ markeddown }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %}

+

{% if markeddown.view %}{% autoescape off %}{{ markeddown.view }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %}

{{ response.status }} {{ response.status_text }}{% autoescape off %}
 {% for key, val in response.headers.items %}{{ key }}: {{ val|urlize_quoted_links }}
@@ -56,6 +56,7 @@
 			

GET {{ name }}

+ {% if markeddown.GET %}

{% autoescape off %}{{ markeddown.GET }}{% endautoescape %}

{% endif %}
GET {% for format in available_formats %} @@ -75,6 +76,7 @@

POST {{ name }}

+ {% if markeddown.POST %}

{% autoescape off %}{{ markeddown.POST }}{% endautoescape %}

{% endif %} {% csrf_token %} {{ post_form.non_field_errors }} {% for field in post_form %} @@ -96,6 +98,7 @@

PUT {{ name }}

+ {% if markeddown.PUT %}

{% autoescape off %}{{ markeddown.PUT }}{% endautoescape %}

{% endif %} {% csrf_token %} {{ put_form.non_field_errors }} @@ -118,6 +121,7 @@

DELETE {{ name }}

+ {% if markeddown.DELETE %}

{% autoescape off %}{{ markeddown.DELETE }}{% endautoescape %}

{% endif %} {% csrf_token %}
diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index 56dcdfabb..2b109841e 100644 --- a/djangorestframework/tests/description.py +++ b/djangorestframework/tests/description.py @@ -53,13 +53,13 @@ class TestViewNamesAndDescriptions(TestCase): pass self.assertEquals(get_name(MockView()), '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(get_name(MockView()), example) def test_resource_description_uses_docstring_by_default(self): """Ensure Resource names are based on the docstring by default.""" @@ -81,21 +81,22 @@ class TestViewNamesAndDescriptions(TestCase): self.assertEquals(get_description(MockView()), 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(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 '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(get_description(MockView()), 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""" diff --git a/djangorestframework/utils/description.py b/djangorestframework/utils/description.py index ce61e5580..c89f27efe 100644 --- a/djangorestframework/utils/description.py +++ b/djangorestframework/utils/description.py @@ -19,30 +19,14 @@ def get_name(view): 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 + # If this view provides a get_name method, try to use that: + if callable(getattr(view, 'get_name', None)): + name = view.get_name() # 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 '' @@ -63,10 +47,9 @@ def get_description(view): 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__ + # If this view provides a get_description method, try to use that: + if callable(getattr(view, 'get_description', None)): + doc = view.get_description() # Otherwise use the view doctring elif getattr(view, '__doc__', None): diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 9199d6b74..ca0692d36 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -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)] + 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): """ Return an HTTP 405 error if an operation is called which does not have a handler method.