mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-16 19:41:06 +03:00
name and description
This commit is contained in:
parent
e7f8c06dbb
commit
c531759147
|
@ -2,6 +2,7 @@ syntax: glob
|
||||||
|
|
||||||
*.pyc
|
*.pyc
|
||||||
*.db
|
*.db
|
||||||
|
assetplatform.egg-info/*
|
||||||
*~
|
*~
|
||||||
coverage.xml
|
coverage.xml
|
||||||
env
|
env
|
||||||
|
|
|
@ -467,12 +467,13 @@ class InstanceMixin(object):
|
||||||
Store the callable object on the resource class that has been associated with this view.
|
Store the callable object on the resource class that has been associated with this view.
|
||||||
"""
|
"""
|
||||||
view = super(InstanceMixin, cls).as_view(**initkwargs)
|
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 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
|
# 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).
|
# as a function when we later look it up (rather than turning it into a method).
|
||||||
# This makes sure our URL reversing works ok.
|
# This makes sure our URL reversing works ok.
|
||||||
initkwargs['resource'].view_callable = (view,)
|
resource.view_callable = (view,)
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,15 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
"""Ensure Resource names are based on the classname by default."""
|
"""Ensure Resource names are based on the classname by default."""
|
||||||
class MockView(BaseView):
|
class MockView(BaseView):
|
||||||
pass
|
pass
|
||||||
self.assertEquals(get_name(MockView()), 'Mock View')
|
self.assertEquals(get_name(MockView()), 'Mock')
|
||||||
|
|
||||||
def test_resource_name_can_be_set_explicitly(self):
|
# This has been turned off now.
|
||||||
"""Ensure Resource names can be set using the 'name' class attribute."""
|
#def test_resource_name_can_be_set_explicitly(self):
|
||||||
example = 'Some Other Name'
|
# """Ensure Resource names can be set using the 'name' class attribute."""
|
||||||
class MockView(BaseView):
|
# example = 'Some Other Name'
|
||||||
name = example
|
# class MockView(BaseView):
|
||||||
self.assertEquals(get_name(MockView()), example)
|
# name = example
|
||||||
|
# self.assertEquals(get_name(MockView()), example)
|
||||||
|
|
||||||
def test_resource_description_uses_docstring_by_default(self):
|
def test_resource_description_uses_docstring_by_default(self):
|
||||||
"""Ensure Resource names are based on the docstring by default."""
|
"""Ensure Resource names are based on the docstring by default."""
|
||||||
|
@ -66,20 +67,21 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
|
|
||||||
self.assertEquals(get_description(MockView()), DESCRIPTION)
|
self.assertEquals(get_description(MockView()), DESCRIPTION)
|
||||||
|
|
||||||
def test_resource_description_can_be_set_explicitly(self):
|
# This has been turned off now
|
||||||
"""Ensure Resource descriptions can be set using the 'description' class attribute."""
|
#def test_resource_description_can_be_set_explicitly(self):
|
||||||
example = 'Some other description'
|
# """Ensure Resource descriptions can be set using the 'description' class attribute."""
|
||||||
class MockView(BaseView):
|
# example = 'Some other description'
|
||||||
"""docstring"""
|
# class MockView(BaseView):
|
||||||
description = example
|
# """docstring"""
|
||||||
self.assertEquals(get_description(MockView()), example)
|
# description = example
|
||||||
|
# self.assertEquals(get_description(MockView()), example)
|
||||||
|
|
||||||
def test_resource_description_does_not_require_docstring(self):
|
#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."""
|
# """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'
|
# example = 'Some other description'
|
||||||
class MockView(BaseView):
|
# class MockView(BaseView):
|
||||||
description = example
|
# description = example
|
||||||
self.assertEquals(get_description(MockView()), example)
|
# self.assertEquals(get_description(MockView()), example)
|
||||||
|
|
||||||
def test_resource_description_can_be_empty(self):
|
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"""
|
||||||
|
|
|
@ -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
|
import re
|
||||||
|
from djangorestframework.resources import Resource, FormResource, ModelResource
|
||||||
|
|
||||||
|
|
||||||
|
# These a a bit Grungy, but they do the job.
|
||||||
|
|
||||||
def get_name(view):
|
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 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 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__
|
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__
|
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:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', name).strip()
|
return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', name).strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_description(view):
|
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."""
|
By default this is the view's docstring with nice unindention applied.
|
||||||
if getattr(view, 'description', None) is not None:
|
"""
|
||||||
return getattr(view, 'description')
|
|
||||||
|
|
||||||
if getattr(view, '__doc__', None) is not None:
|
# If we're looking up the name of a view callable, as found by reverse,
|
||||||
whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in view.__doc__.splitlines()[1:] if line.lstrip()]
|
# 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:
|
# If this view has a resource that's been overridden, then use the resource's doctring
|
||||||
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
|
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
|
||||||
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', view.__doc__)
|
doc = view.resource.__doc__
|
||||||
|
|
||||||
return view.__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
|
name = None
|
||||||
description = 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
|
@property
|
||||||
def allowed_methods(self):
|
def allowed_methods(self):
|
||||||
"""
|
"""
|
||||||
|
@ -122,12 +134,12 @@ class ModelView(BaseView):
|
||||||
|
|
||||||
class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
|
class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
|
||||||
"""A view which provides default operations for read/update/delete against a model instance."""
|
"""A view which provides default operations for read/update/delete against a model instance."""
|
||||||
pass
|
_suffix = 'Instance'
|
||||||
|
|
||||||
class ListModelView(ListModelMixin, ModelView):
|
class ListModelView(ListModelMixin, ModelView):
|
||||||
"""A view which provides default operations for list, against a model in the database."""
|
"""A view which provides default operations for list, against a model in the database."""
|
||||||
pass
|
_suffix = 'List'
|
||||||
|
|
||||||
class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
|
class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
|
||||||
"""A view which provides default operations for list and create, against a model in the database."""
|
"""A view which provides default operations for list and create, against a model in the database."""
|
||||||
pass
|
_suffix = 'List'
|
||||||
|
|
|
@ -6,7 +6,11 @@ from djangorestframework.resources import ModelResource
|
||||||
|
|
||||||
from blogpost.models import BlogPost, Comment
|
from blogpost.models import BlogPost, Comment
|
||||||
|
|
||||||
|
|
||||||
class BlogPostResource(ModelResource):
|
class BlogPostResource(ModelResource):
|
||||||
|
"""
|
||||||
|
A Blog Post has a *title* and *content*, and can be associated with zero or more comments.
|
||||||
|
"""
|
||||||
model = BlogPost
|
model = BlogPost
|
||||||
fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
|
fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
@ -14,7 +18,11 @@ class BlogPostResource(ModelResource):
|
||||||
def comments(self, instance):
|
def comments(self, instance):
|
||||||
return reverse('comments', kwargs={'blogpost': instance.key})
|
return reverse('comments', kwargs={'blogpost': instance.key})
|
||||||
|
|
||||||
|
|
||||||
class CommentResource(ModelResource):
|
class CommentResource(ModelResource):
|
||||||
|
"""
|
||||||
|
A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
|
||||||
|
"""
|
||||||
model = Comment
|
model = Comment
|
||||||
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
@ -25,4 +33,4 @@ urlpatterns = patterns('',
|
||||||
url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource)),
|
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/$', ListOrCreateModelView.as_view(resource=CommentResource), name='comments'),
|
||||||
url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)),
|
url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user