{{ name }}
-{% if markeddown %}{% autoescape off %}{{ markeddown }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %}
+{% autoescape off %}{{ description }}{% endautoescape %}
{{ response.status }} {{ response.status_text }}{% autoescape off %} {% for key, val in response.headers.items %}{{ key }}: {{ val|urlize_quoted_links }} diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index 56dcdfabb..212b4ca4d 100644 --- a/djangorestframework/tests/description.py +++ b/djangorestframework/tests/description.py @@ -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""" diff --git a/djangorestframework/utils/breadcrumbs.py b/djangorestframework/utils/breadcrumbs.py index cfc63a479..85e13a5a4 100644 --- a/djangorestframework/utils/breadcrumbs.py +++ b/djangorestframework/utils/breadcrumbs.py @@ -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 diff --git a/djangorestframework/utils/description.py b/djangorestframework/utils/description.py deleted file mode 100644 index 096cf57ff..000000000 --- a/djangorestframework/utils/description.py +++ /dev/null @@ -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 diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 9f53868b3..d89c2afd9 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -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', '
')) + 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, }