diff options
| author | Tom Christie | 2012-01-25 19:53:04 +0000 |
|---|---|---|
| committer | Tom Christie | 2012-01-25 19:53:04 +0000 |
| commit | f5e54c7c32e8f42c55947e53ce841622177f6c95 (patch) | |
| tree | cc1fa8faf7719ce5d068c78517019c325301d550 | |
| parent | 2c3f03c7cbecab59287788895a37272094ea49d3 (diff) | |
| parent | d3ec860dd1fedf27042b3d08bf6c7e4f4adbddc9 (diff) | |
| download | django-rest-framework-f5e54c7c32e8f42c55947e53ce841622177f6c95.tar.bz2 | |
Merge remote-tracking branch 'btimby/description'
| -rw-r--r-- | djangorestframework/renderers.py | 29 | ||||
| -rw-r--r-- | djangorestframework/templates/renderer.html | 4 | ||||
| -rw-r--r-- | djangorestframework/tests/description.py | 54 | ||||
| -rw-r--r-- | djangorestframework/utils/breadcrumbs.py | 4 | ||||
| -rw-r--r-- | djangorestframework/utils/description.py | 88 | ||||
| -rw-r--r-- | djangorestframework/views.py | 65 |
6 files changed, 107 insertions, 137 deletions
diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index bb186b0a..bb0f789a 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -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, diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html index 0532d515..ff761023 100644 --- a/djangorestframework/templates/renderer.html +++ b/djangorestframework/templates/renderer.html @@ -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 }} diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index 56dcdfab..212b4ca4 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) - - # 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) + self.assertEquals(MockView().get_description(), DESCRIPTION) - #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_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 '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 cfc63a47..85e13a5a 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 096cf57f..00000000 --- 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 9f53868b..d89c2afd 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', '<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, } |
