From 727015482888be8acc2ed7037789f2b90f78d736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=20V=C3=AEjdea?= Date: Wed, 4 Apr 2018 22:59:57 +0300 Subject: [PATCH] Test with Django Rest Framework 3.8 (#96) * Add djangorestframework 3.8 to tox and travis * Add @action tests * Limit tox to <3.0.0 --- .gitignore | 2 + .idea/drf-yasg.iml | 1 + .idea/inspectionProfiles/Project_Default.xml | 3 +- .idea/misc.xml | 3 + .travis.yml | 1 + README.rst | 2 +- docs/custom_spec.rst | 8 +-- requirements/tox.txt | 2 +- src/drf_yasg/utils.py | 17 +++--- testproj/articles/views.py | 58 ++++++++++++++------ tox.ini | 4 +- 11 files changed, 69 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 0dffda8..205c9f1 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,5 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties + +\.pytest_cache/ diff --git a/.idea/drf-yasg.iml b/.idea/drf-yasg.iml index 3bbd482..2ecb6ac 100644 --- a/.idea/drf-yasg.iml +++ b/.idea/drf-yasg.iml @@ -16,6 +16,7 @@ + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 0aad453..9f03014 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -10,11 +10,12 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 940f30f..822f657 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -70,4 +70,7 @@ + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 9232afe..778e198 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: env: - DRF=3.7 + - DRF=3.8 jobs: include: diff --git a/README.rst b/README.rst index c0696fd..4908cfd 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor Compatible with -- **Django Rest Framework**: 3.7.7 +- **Django Rest Framework**: 3.7.7, 3.8.x - **Django**: 1.11.x, 2.0.x - **Python**: 2.7, 3.4, 3.5, 3.6 diff --git a/docs/custom_spec.rst b/docs/custom_spec.rst index 3c9938d..0c08ca3 100644 --- a/docs/custom_spec.rst +++ b/docs/custom_spec.rst @@ -87,8 +87,8 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora * for ``ViewSet``, ``GenericViewSet``, ``ModelViewSet``, because each viewset corresponds to multiple **paths**, you have to decorate the *action methods*, i.e. ``list``, ``create``, ``retrieve``, etc. |br| - Additionally, ``@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based api views, can - respond to multiple HTTP methods and thus have multiple operations that must be decorated separately: + Additionally, ``@action``\ s, `@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based + api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately: .. code-block:: python @@ -96,13 +96,13 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora class ArticleViewSet(viewsets.ModelViewSet): # method or 'methods' can be skipped because the list_route only handles a single method (GET) @swagger_auto_schema(operation_description='GET /articles/today/') - @list_route(methods=['get']) + @action(detail=False, methods=['get']) def today(self, request): ... @swagger_auto_schema(method='get', operation_description="GET /articles/{id}/image/") @swagger_auto_schema(method='post', operation_description="POST /articles/{id}/image/") - @detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,)) + @action(detail=True, methods=['get', 'post'], parser_classes=(MultiPartParser,)) def image(self, request, id=None): ... diff --git a/requirements/tox.txt b/requirements/tox.txt index f1661a0..722c32b 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,5 +1,5 @@ # requirements for building and running tox -tox>=2.9.1 +tox>=2.9.1,<3.0.0 detox>=0.11 -r setup.txt diff --git a/src/drf_yasg/utils.py b/src/drf_yasg/utils.py index 3aa894b..9e1c5ed 100644 --- a/src/drf_yasg/utils.py +++ b/src/drf_yasg/utils.py @@ -114,7 +114,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo # no overrides to set, no use in doing more work return - # if the method is a detail_route or list_route, it will have a bind_to_methods attribute + # if the method is an @action, it will have a bind_to_methods attribute bind_to_methods = getattr(view_method, 'bind_to_methods', []) # if the method is actually a function based view (@api_view), it will have a 'cls' attribute view_cls = getattr(view_method, 'cls', None) @@ -126,7 +126,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo _methods = methods if methods or method: assert available_methods or http_method_names, "`method` or `methods` can only be specified " \ - "on @detail_route or @api_view views" + "on @action or @api_view views" assert bool(methods) != bool(method), "specify either method or methods" assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \ " use `method` for a single argument" @@ -138,13 +138,13 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo assert not any(mth in existing_data for mth in _methods), "http method defined multiple times" if available_methods: - # detail_route, list_route or api_view + # action or api_view assert bool(http_method_names) != bool(bind_to_methods), "this should never happen" if len(available_methods) > 1: assert _methods, \ - "on multi-method api_view, detail_route or list_route, you must specify swagger_auto_schema on " \ - "a per-method basis using one of the `method` or `methods` arguments" + "on multi-method api_view, action, detail_route or list_route, you must specify " \ + "swagger_auto_schema on a per-method basis using one of the `method` or `methods` arguments" else: # for a single-method view we assume that single method as the decorator target _methods = _methods or available_methods @@ -156,8 +156,9 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo view_method._swagger_auto_schema = existing_data else: assert not _methods, \ - "the methods argument should only be specified when decorating a detail_route or list_route; you " \ - "should also ensure that you put the swagger_auto_schema decorator AFTER (above) the _route decorator" + "the methods argument should only be specified when decorating an action, detail_route or " \ + "list_route; you should also ensure that you put the swagger_auto_schema decorator " \ + "AFTER (above) the _route decorator" assert not existing_data, "swagger_auto_schema applied twice to method" view_method._swagger_auto_schema = data @@ -183,7 +184,7 @@ def is_list_view(path, method, view): return True if action in ('retrieve', 'update', 'partial_update', 'destroy') or detail is True or suffix == 'Instance': - # a detail_route is surely not a list route + # a detail action is surely not a list route return False # for GenericAPIView, if it's a detail view it can't also be a list view diff --git a/testproj/articles/views.py b/testproj/articles/views.py index 57cf910..9d91983 100644 --- a/testproj/articles/views.py +++ b/testproj/articles/views.py @@ -3,6 +3,7 @@ import datetime from django.utils.decorators import method_decorator from django_filters.rest_framework import DjangoFilterBackend from rest_framework import viewsets +# noinspection PyDeprecation from rest_framework.decorators import detail_route, list_route from rest_framework.filters import OrderingFilter from rest_framework.pagination import LimitOffsetPagination @@ -89,23 +90,48 @@ class ArticleViewSet(viewsets.ModelViewSet): swagger_schema = NoTitleAutoSchema - @swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector]) - @list_route(methods=['get']) - def today(self, request): - today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min) - today_max = datetime.datetime.combine(datetime.date.today(), datetime.time.max) - articles = self.get_queryset().filter(date_created__range=(today_min, today_max)).all() - serializer = self.serializer_class(articles, many=True) - return Response(serializer.data) + try: + from rest_framework.decorators import action - @swagger_auto_schema(method='get', operation_description="image GET description override") - @swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer) - @detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,)) - def image(self, request, slug=None): - """ - image method docstring - """ - pass + @swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector]) + @action(detail=False, methods=['get']) + def today(self, request): + today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min) + today_max = datetime.datetime.combine(datetime.date.today(), datetime.time.max) + articles = self.get_queryset().filter(date_created__range=(today_min, today_max)).all() + serializer = self.serializer_class(articles, many=True) + return Response(serializer.data) + + @swagger_auto_schema(method='get', operation_description="image GET description override") + @swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer) + @action(detail=True, methods=['get', 'post'], parser_classes=(MultiPartParser,)) + def image(self, request, slug=None): + """ + image method docstring + """ + pass + except ImportError: + action = None + + # noinspection PyDeprecation + @swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector]) + @list_route(methods=['get']) + def today(self, request): + today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min) + today_max = datetime.datetime.combine(datetime.date.today(), datetime.time.max) + articles = self.get_queryset().filter(date_created__range=(today_min, today_max)).all() + serializer = self.serializer_class(articles, many=True) + return Response(serializer.data) + + # noinspection PyDeprecation + @swagger_auto_schema(method='get', operation_description="image GET description override") + @swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer) + @detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,)) + def image(self, request, slug=None): + """ + image method docstring + """ + pass def update(self, request, *args, **kwargs): """update method docstring""" diff --git a/tox.ini b/tox.ini index 714a3be..ef97c6b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,14 @@ [tox] envlist = py27-django111-drf37, - py{34,35,36}-django{111,20}-drf37, + py{34,35,36}-django{111,20}-drf{37,38}, py36-django20-drfmaster, lint, docs [travis:env] DRF = 3.7: drf37 + 3.8: drf38 master: drfmaster [testenv] @@ -16,6 +17,7 @@ deps = django20: Django>=2.0,<2.1 drf37: djangorestframework>=3.7.7,<3.8 + drf38: djangorestframework>=3.8.0,<3.9 # test with the latest build of django-rest-framework to get early warning of compatibility issues drfmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz