django-rest-framework/rest_framework/decorators.py

262 lines
8.2 KiB
Python
Raw Normal View History

2013-04-25 15:47:34 +04:00
"""
2013-05-28 18:09:23 +04:00
The most important decorator in this module is `@api_view`, which is used
2013-04-25 15:47:34 +04:00
for writing function-based views with REST framework.
There are also various decorators for setting the API policies on function
based views, as well as the `@detail_route` and `@list_route` decorators, which are
2013-04-25 15:47:34 +04:00
used to annotate methods on viewsets that should be included by routers.
"""
from __future__ import unicode_literals
import types
import warnings
from django.forms.utils import pretty_name
from django.utils import six
from rest_framework.views import APIView
2012-09-14 19:07:07 +04:00
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
def api_view(http_method_names=None, exclude_from_schema=False):
2012-09-14 19:07:07 +04:00
"""
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
2012-09-14 19:07:07 +04:00
"""
2015-02-04 17:26:23 +03:00
http_method_names = ['GET'] if (http_method_names is None) else http_method_names
2012-09-14 19:07:07 +04:00
def decorator(func):
2012-10-29 18:41:33 +04:00
WrappedAPIView = type(
six.PY3 and 'WrappedAPIView' or b'WrappedAPIView',
2012-10-29 18:41:33 +04:00
(APIView,),
{'__doc__': func.__doc__}
)
# Note, the above allows us to set the docstring.
2012-11-14 22:36:29 +04:00
# It is the equivalent of:
2012-10-29 18:41:33 +04:00
#
# class WrappedAPIView(APIView):
# pass
# WrappedAPIView.__doc__ = func.doc <--- Not possible to do this
# api_view applied without (method_names)
assert not(isinstance(http_method_names, types.FunctionType)), \
'@api_view missing list of allowed HTTP methods'
# api_view applied with eg. string instead of list of strings
assert isinstance(http_method_names, (list, tuple)), \
2013-05-28 18:09:23 +04:00
'@api_view expected a list of strings, received %s' % type(http_method_names).__name__
allowed_methods = set(http_method_names) | {'options'}
2012-10-09 15:01:17 +04:00
WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
def handler(self, *args, **kwargs):
return func(*args, **kwargs)
for method in http_method_names:
setattr(WrappedAPIView, method.lower(), handler)
WrappedAPIView.__name__ = func.__name__
WrappedAPIView.__module__ = func.__module__
WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
APIView.renderer_classes)
WrappedAPIView.parser_classes = getattr(func, 'parser_classes',
APIView.parser_classes)
WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes',
APIView.authentication_classes)
WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes',
APIView.throttle_classes)
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
APIView.permission_classes)
WrappedAPIView.schema = getattr(func, 'schema',
APIView.schema)
if exclude_from_schema:
warnings.warn(
2018-04-03 16:35:26 +03:00
"The `exclude_from_schema` argument to `api_view` is deprecated. "
"Use the `schema` decorator instead, passing `None`.",
2018-04-03 16:35:26 +03:00
DeprecationWarning
)
WrappedAPIView.exclude_from_schema = exclude_from_schema
return WrappedAPIView.as_view()
2012-09-03 18:57:43 +04:00
return decorator
2012-09-14 19:07:07 +04:00
def renderer_classes(renderer_classes):
def decorator(func):
2012-09-26 23:18:57 +04:00
func.renderer_classes = renderer_classes
return func
return decorator
def parser_classes(parser_classes):
def decorator(func):
2012-09-26 23:18:57 +04:00
func.parser_classes = parser_classes
return func
return decorator
def authentication_classes(authentication_classes):
def decorator(func):
2012-09-26 23:18:57 +04:00
func.authentication_classes = authentication_classes
return func
return decorator
def throttle_classes(throttle_classes):
def decorator(func):
2012-09-26 23:18:57 +04:00
func.throttle_classes = throttle_classes
return func
return decorator
def permission_classes(permission_classes):
def decorator(func):
2012-09-26 23:18:57 +04:00
func.permission_classes = permission_classes
return func
return decorator
def schema(view_inspector):
def decorator(func):
func.schema = view_inspector
return func
return decorator
def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
"""
Mark a ViewSet method as a routable action.
Set the `detail` boolean to determine if this action should apply to
instance/detail requests or collection/list requests.
"""
2015-02-04 17:26:23 +03:00
methods = ['get'] if (methods is None) else methods
methods = [method.lower() for method in methods]
assert detail is not None, (
"@action() missing required argument: 'detail'"
)
2015-02-04 17:13:30 +03:00
# name and suffix are mutually exclusive
if 'name' in kwargs and 'suffix' in kwargs:
raise TypeError("`name` and `suffix` are mutually exclusive arguments.")
def decorator(func):
func.mapping = MethodMapper(func, methods)
func.detail = detail
func.url_path = url_path if url_path else func.__name__
func.url_name = url_name if url_name else func.__name__.replace('_', '-')
func.kwargs = kwargs
# Set descriptive arguments for viewsets
if 'name' not in kwargs and 'suffix' not in kwargs:
func.kwargs['name'] = pretty_name(func.__name__)
func.kwargs['description'] = func.__doc__ or None
return func
return decorator
class MethodMapper(dict):
"""
Enables mapping HTTP methods to different ViewSet methods for a single,
logical action.
Example usage:
class MyViewSet(ViewSet):
@action(detail=False)
def example(self, request, **kwargs):
...
@example.mapping.post
def create_example(self, request, **kwargs):
...
"""
def __init__(self, action, methods):
self.action = action
for method in methods:
self[method] = self.action.__name__
def _map(self, method, func):
assert method not in self, (
"Method '%s' has already been mapped to '.%s'." % (method, self[method]))
assert func.__name__ != self.action.__name__, (
"Method mapping does not behave like the property decorator. You "
"cannot use the same method name for each mapping declaration.")
self[method] = func.__name__
return func
def get(self, func):
return self._map('get', func)
def post(self, func):
return self._map('post', func)
def put(self, func):
return self._map('put', func)
def patch(self, func):
return self._map('patch', func)
def delete(self, func):
return self._map('delete', func)
def head(self, func):
return self._map('head', func)
def options(self, func):
return self._map('options', func)
def trace(self, func):
return self._map('trace', func)
def detail_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for detail requests.
"""
warnings.warn(
"`detail_route` is pending deprecation and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
PendingDeprecationWarning, stacklevel=2
)
def decorator(func):
func = action(methods, detail=True, **kwargs)(func)
if 'url_name' not in kwargs:
func.url_name = func.url_path.replace('_', '-')
return func
return decorator
def list_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for list requests.
"""
warnings.warn(
"`list_route` is pending deprecation and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
PendingDeprecationWarning, stacklevel=2
)
def decorator(func):
func = action(methods, detail=False, **kwargs)(func)
if 'url_name' not in kwargs:
func.url_name = func.url_path.replace('_', '-')
return func
return decorator