diff options
| -rw-r--r-- | djangorestframework/renderers.py | 47 | ||||
| -rw-r--r-- | djangorestframework/templates/renderer.html | 11 | ||||
| -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 | 73 |
6 files changed, 120 insertions, 157 deletions
diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index bb186b0a..5338c383 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 @@ -226,6 +225,7 @@ class DocumentingTemplateRenderer(BaseRenderer): return content + def _get_form_instance(self, view, method): """ Get a form, possibly bound to either the input or output data. @@ -261,6 +261,7 @@ class DocumentingTemplateRenderer(BaseRenderer): return form_instance + def _get_generic_content_form(self, view): """ Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms @@ -296,6 +297,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 +331,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) @@ -332,12 +340,11 @@ class DocumentingTemplateRenderer(BaseRenderer): context = RequestContext(self.view.request, { 'content': content, 'view': self.view, - 'request': self.view.request, # TODO: remove + 'request': self.view.request, # TODO: remove 'response': self.view.response, 'description': description, 'name': name, 'version': VERSION, - 'markeddown': markeddown, 'breadcrumblist': breadcrumb_list, 'available_formats': self.view._rendered_formats, 'put_form': put_form_instance, @@ -395,14 +402,12 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer): template = 'renderer.txt' -DEFAULT_RENDERERS = ( - JSONRenderer, - JSONPRenderer, - DocumentingHTMLRenderer, - DocumentingXHTMLRenderer, - DocumentingPlainTextRenderer, - XMLRenderer -) +DEFAULT_RENDERERS = ( JSONRenderer, + JSONPRenderer, + DocumentingHTMLRenderer, + DocumentingXHTMLRenderer, + DocumentingPlainTextRenderer, + XMLRenderer ) if YAMLRenderer: DEFAULT_RENDERERS += (YAMLRenderer,) diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html index 0532d515..3b33a6ff 100644 --- a/djangorestframework/templates/renderer.html +++ b/djangorestframework/templates/renderer.html @@ -11,13 +11,8 @@ /* Custom styles */ .version{font-size:8px;} </style> - {% if ADMIN_MEDIA_PREFIX %} - <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/> - <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/> - {% else %} - <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 %} + <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/> + <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/> <title>Django REST framework - {{ name }}</title> </head> <body> @@ -50,7 +45,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..9dd84bf2 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__ = ( @@ -25,6 +27,7 @@ __all__ = ( ) + class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): """ Handles incoming requests and maps them to REST operations. @@ -47,13 +50,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): List of parsers the resource can parse the request with. """ - authentication = (authentication.UserLoggedInAuthentication, - authentication.BasicAuthentication) + authentication = ( authentication.UserLoggedInAuthentication, + authentication.BasicAuthentication ) """ List of all authenticating methods to attempt. """ - permissions = (permissions.FullAnonAccess,) + permissions = ( permissions.FullAnonAccess, ) """ List of all permissions that must be checked. """ @@ -76,6 +79,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 +217,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, } @@ -184,21 +240,18 @@ class ModelView(View): """ resource = resources.ModelResource - class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView): """ A view which provides default operations for read/update/delete against a model instance. """ _suffix = 'Instance' - class ListModelView(ListModelMixin, ModelView): """ A view which provides default operations for list, against a model in the database. """ _suffix = 'List' - class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView): """ A view which provides default operations for list and create, against a model in the database. |
