diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 45226d57b..8359a7338 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -217,6 +217,21 @@ You may pass `None` in order to exclude the view from schema generation. return Response({"message": "Will not appear in schema!"}) +## View extra attrs decorator + +To set custom extra attribute on function-based view, you may use the `@extra_attrs` decorator. +This must come *after* (below) the `@api_view` decorator. For example: + + from rest_framework.decorators import api_view, extra_attrs + + class ExtraClass: + pass + + @api_view(['GET']) + @extra_attrs(extra_class=ExtraClass) + def view(request): + return Response({"message": "Hello for today! See you tomorrow!"}) + [cite]: https://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html [cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html [settings]: settings.md diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index eb1cad9e4..0f5a97ebc 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -72,6 +72,11 @@ def api_view(http_method_names=None): WrappedAPIView.schema = getattr(func, 'schema', APIView.schema) + for extra_attr in getattr(func, 'extra_attrs', []): + assert not (hasattr(WrappedAPIView, extra_attr)), \ + '{} already has attribute {}'.format(func.__name__, extra_attr) + setattr(WrappedAPIView, extra_attr, + getattr(func, extra_attr, None)) return WrappedAPIView.as_view() @@ -120,6 +125,15 @@ def schema(view_inspector): return decorator +def extra_attrs(**kwargs): + def decorator(func): + func.extra_attrs = list(kwargs.keys()) + for extra_attr, val in kwargs.items(): + setattr(func, extra_attr, val) + 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. diff --git a/tests/test_decorators.py b/tests/test_decorators.py index e10f0e5c5..d6c1336ca 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -4,7 +4,7 @@ from django.test import TestCase from rest_framework import status from rest_framework.authentication import BasicAuthentication from rest_framework.decorators import ( - action, api_view, authentication_classes, parser_classes, + action, api_view, authentication_classes, extra_attrs, parser_classes, permission_classes, renderer_classes, schema, throttle_classes ) from rest_framework.parsers import JSONParser @@ -166,6 +166,25 @@ class DecoratorTestCase(TestCase): assert isinstance(view.cls.schema, CustomSchema) + def test_extra_attrs(self): + """ + Checks Custom extra attrs is set on view + """ + + def extra_func(): + pass + + class ExtraClass: + pass + + @api_view(['get']) + @extra_attrs(extra_func=extra_func, extra_class=ExtraClass) + def view(request): + return Response({}) + + assert view.cls.extra_func is extra_func + assert view.cls.extra_class is ExtraClass + class ActionDecoratorTestCase(TestCase):