diff options
| author | Tom Christie | 2011-05-23 17:07:31 +0100 |
|---|---|---|
| committer | Tom Christie | 2011-05-23 17:07:31 +0100 |
| commit | c53175914752502629141556f3e001e9d2e9f7fa (patch) | |
| tree | d68bc4f0b2baeed2a90d6d20295423583603ba2b | |
| parent | e7f8c06dbbbc9e4ae91327ee02cd8147777b17e2 (diff) | |
| download | django-rest-framework-c53175914752502629141556f3e001e9d2e9f7fa.tar.bz2 | |
name and description
| -rw-r--r-- | .hgignore | 1 | ||||
| -rw-r--r-- | djangorestframework/mixins.py | 5 | ||||
| -rw-r--r-- | djangorestframework/tests/description.py | 42 | ||||
| -rw-r--r-- | djangorestframework/utils/description.py | 92 | ||||
| -rw-r--r-- | djangorestframework/views.py | 22 | ||||
| -rw-r--r-- | examples/blogpost/urls.py | 10 |
6 files changed, 125 insertions, 47 deletions
@@ -2,6 +2,7 @@ syntax: glob *.pyc *.db +assetplatform.egg-info/* *~ coverage.xml env diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 73ffbec2..62257592 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -467,12 +467,13 @@ class InstanceMixin(object): Store the callable object on the resource class that has been associated with this view. """ view = super(InstanceMixin, cls).as_view(**initkwargs) - if 'resource' in initkwargs: + resource = getattr(cls(**initkwargs), 'resource', None) + if resource: # We do a little dance when we store the view callable... # we need to store it wrapped in a 1-tuple, so that inspect will treat it # as a function when we later look it up (rather than turning it into a method). # This makes sure our URL reversing works ok. - initkwargs['resource'].view_callable = (view,) + resource.view_callable = (view,) return view diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index d5a1102f..6c494efa 100644 --- a/djangorestframework/tests/description.py +++ b/djangorestframework/tests/description.py @@ -37,14 +37,15 @@ class TestViewNamesAndDescriptions(TestCase): """Ensure Resource names are based on the classname by default.""" class MockView(BaseView): pass - self.assertEquals(get_name(MockView()), 'Mock View') + self.assertEquals(get_name(MockView()), 'Mock') - 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(BaseView): - name = example - self.assertEquals(get_name(MockView()), example) + # 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(BaseView): + # name = 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.""" @@ -66,20 +67,21 @@ class TestViewNamesAndDescriptions(TestCase): self.assertEquals(get_description(MockView()), DESCRIPTION) - 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(BaseView): - """docstring""" - description = example - self.assertEquals(get_description(MockView()), example) + # 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(BaseView): + # """docstring""" + # 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 'description' class attribute.""" - example = 'Some other description' - class MockView(BaseView): - 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 'description' class attribute.""" + # example = 'Some other description' + # class MockView(BaseView): + # description = 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 f7145c0f..25bef80b 100644 --- a/djangorestframework/utils/description.py +++ b/djangorestframework/utils/description.py @@ -1,37 +1,91 @@ -"""Get a descriptive name and description for a view, -based on class name and docstring, and override-able by 'name' and 'description' attributes""" +""" +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. + """ + 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 getattr(view, 'name', None) is not None: - return view.name + 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 getattr(view, '__name__', None) is not None: + # 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__ - elif getattr(view, '__class__', None) is not None: # TODO: should be able to get rid of this case once refactoring to 1.3 class views is complete + + # 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. + """ + Provide a description for the view. - By default this is the view's docstring with nice unindention applied.""" - if getattr(view, 'description', None) is not None: - return getattr(view, 'description') + 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 '' - if getattr(view, '__doc__', None) is not None: - whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in view.__doc__.splitlines()[1:] if line.lstrip()] + whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()] - if whitespace_counts: - whitespace_pattern = '^' + (' ' * min(whitespace_counts)) - return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', view.__doc__) + # unindent the docstring if needed + if whitespace_counts: + whitespace_pattern = '^' + (' ' * min(whitespace_counts)) + return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc) - return view.__doc__ + # otherwise return it as-is + return doc - return ''
\ No newline at end of file diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 81567e68..a0471166 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -51,6 +51,18 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View): name = None description = None + @classmethod + def as_view(cls, **initkwargs): + """ + Override the default :meth:`as_view` to store an instance of the view + as an attribute on the callable function. This allows us to discover + information about the view when we do URL reverse lookups. + """ + view = super(BaseView, cls).as_view(**initkwargs) + view.cls_instance = cls(**initkwargs) + return view + + @property def allowed_methods(self): """ @@ -122,12 +134,12 @@ class ModelView(BaseView): class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView): """A view which provides default operations for read/update/delete against a model instance.""" - pass + _suffix = 'Instance' class ListModelView(ListModelMixin, ModelView): - """A view which provides default operations for list, against a model in the database.""" - pass + """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.""" - pass + """A view which provides default operations for list and create, against a model in the database.""" + _suffix = 'List' diff --git a/examples/blogpost/urls.py b/examples/blogpost/urls.py index 130363b1..c677b8fa 100644 --- a/examples/blogpost/urls.py +++ b/examples/blogpost/urls.py @@ -6,7 +6,11 @@ from djangorestframework.resources import ModelResource from blogpost.models import BlogPost, Comment + class BlogPostResource(ModelResource): + """ + A Blog Post has a *title* and *content*, and can be associated with zero or more comments. + """ model = BlogPost fields = ('created', 'title', 'slug', 'content', 'url', 'comments') ordering = ('-created',) @@ -14,7 +18,11 @@ class BlogPostResource(ModelResource): def comments(self, instance): return reverse('comments', kwargs={'blogpost': instance.key}) + class CommentResource(ModelResource): + """ + A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*. + """ model = Comment fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') ordering = ('-created',) @@ -25,4 +33,4 @@ urlpatterns = patterns('', url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource)), url(r'^(?P<blogpost>[^/]+)/comments/$', ListOrCreateModelView.as_view(resource=CommentResource), name='comments'), url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)), -)
\ No newline at end of file +) |
