mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 09:36:49 +03:00
name and description
This commit is contained in:
parent
e7f8c06dbb
commit
c531759147
|
@ -2,6 +2,7 @@ syntax: glob
|
|||
|
||||
*.pyc
|
||||
*.db
|
||||
assetplatform.egg-info/*
|
||||
*~
|
||||
coverage.xml
|
||||
env
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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 getattr(view, '__name__', None) is not None:
|
||||
# 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__
|
||||
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 getattr(view, '__doc__', None) is not None:
|
||||
whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in view.__doc__.splitlines()[1:] if line.lstrip()]
|
||||
# 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 whitespace_counts:
|
||||
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
|
||||
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', view.__doc__)
|
||||
|
||||
return view.__doc__
|
||||
# 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
|
||||
|
||||
return ''
|
|
@ -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'
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user