diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/decorators.py | 107 |
1 files changed, 77 insertions, 30 deletions
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 9836c966..1483cb56 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -1,5 +1,4 @@ from functools import wraps -from django.http import Http404 from django.utils.decorators import available_attrs from django.core.exceptions import PermissionDenied from rest_framework import exceptions @@ -7,47 +6,95 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.request import Request from rest_framework.settings import api_settings +from rest_framwork.views import APIView -def api_view(allowed_methods): +class LazyViewCreator(object): + """ - Decorator for function based views. + This class is responsible for dynamically creating an APIView subclass that + will wrap a function-based view. Instances of this class are created + by the function-based view decorators (below), and each decorator is + responsible for setting attributes on the instance that will eventually be + copied onto the final class-based view. The CBV gets created lazily the first + time it's needed, and then cached for future use. - @api_view(['GET', 'POST']) - def my_view(request): - # request will be an instance of `Request` - # `Response` objects will have .request set automatically - # APIException instances will be handled + This is done so that the ordering of stacked decorators is irrelevant. """ - allowed_methods = [method.upper() for method in allowed_methods] - def decorator(func): - @wraps(func, assigned=available_attrs(func)) - def inner(request, *args, **kwargs): - try: + def __init__(self, wrapped_view): + + self.wrapped_view = wrapped_view + + # Each item in this dictionary will be copied onto the final + # class-based view that gets created when this object is called + self.final_view_attrs = { + 'http_method_names': APIView.http_method_names, + 'renderer_classes': APIView.renderer_classes, + 'parser_classes': APIView.parser_classes, + 'authentication_classes': APIView.authentication_classes, + 'throttle_classes': APIView.throttle_classes, + 'permission_classes': APIView.permission_classes, + } + self._cached_view = None + + def handler(self, *args, **kwargs): + return self.wrapped_view(*args, **kwargs) + + @property + def view(self): + """ + Accessor for the dynamically created class-based view. This will + be created if necessary and cached for next time. + """ - request = Request(request) + if self._cached_view is None: - if request.method not in allowed_methods: - raise exceptions.MethodNotAllowed(request.method) + class WrappedAPIView(APIView): + pass - response = func(request, *args, **kwargs) + for attr, value in self.final_view_attrs.items(): + setattr(WrappedAPIView, attr, value) - if isinstance(response, Response): - response.request = request - if api_settings.FORMAT_SUFFIX_KWARG: - response.format = kwargs.get(api_settings.FORMAT_SUFFIX_KWARG, None) - return response + # Attach the wrapped view function for each of the + # allowed HTTP methods + for method in WrappedAPIView.http_method_names: + setattr(WrappedAPIView, method.lower(), self.handler) - except exceptions.APIException as exc: - return Response({'detail': exc.detail}, status=exc.status_code) + self._cached_view = WrappedAPIView.as_view() - except Http404 as exc: - return Response({'detail': 'Not found'}, - status=status.HTTP_404_NOT_FOUND) + return self._cached_view - except PermissionDenied as exc: - return Response({'detail': 'Permission denied'}, - status=status.HTTP_403_FORBIDDEN) + def __call__(self, *args, **kwargs): + """ + This is the actual code that gets run per-request + """ + return self.view(*args, **kwargs) + + @staticmethod + def maybe_create(func_or_instance): + """ + If the argument is already an instance of LazyViewCreator, + just return it. Otherwise, create a new one. + """ + if isinstance(func_or_instance, LazyViewCreator): + return func_or_instance + return LazyViewCreator(func_or_instance) + + +def _create_attribute_setting_decorator(attribute, filter=lambda item: item): + def decorator(value): + def inner(func): + wrapper = LazyViewCreator.maybe_create(func) + wrapper.final_view_attrs[attribute] = filter(value) + return wrapper return inner return decorator + + +api_view = _create_attribute_setting_decorator('http_method_names', filter=lambda methods: [method.lower() for method in methods]) +renderer_classes = _create_attribute_setting_decorator('renderer_classes') +parser_classes = _create_attribute_setting_decorator('parser_classes') +authentication_classes = _create_attribute_setting_decorator('authentication_classes') +throttle_classes = _create_attribute_setting_decorator('throttle_classes') +permission_classes = _create_attribute_setting_decorator('permission_classes') |
