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
openapi3
Cristi Vîjdea 2018-04-04 22:59:57 +03:00 committed by GitHub
parent 6f7d14fdb2
commit 7270154828
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 69 additions and 32 deletions

2
.gitignore vendored
View File

@ -159,3 +159,5 @@ com_crashlytics_export_strings.xml
crashlytics.properties crashlytics.properties
crashlytics-build.properties crashlytics-build.properties
fabric.properties fabric.properties
\.pytest_cache/

View File

@ -16,6 +16,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/docs/_build" />
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" />

View File

@ -10,11 +10,12 @@
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions"> <option name="ourVersions">
<value> <value>
<list size="4"> <list size="5">
<item index="0" class="java.lang.String" itemvalue="2.7" /> <item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.4" /> <item index="1" class="java.lang.String" itemvalue="3.4" />
<item index="2" class="java.lang.String" itemvalue="3.5" /> <item index="2" class="java.lang.String" itemvalue="3.5" />
<item index="3" class="java.lang.String" itemvalue="3.6" /> <item index="3" class="java.lang.String" itemvalue="3.6" />
<item index="4" class="java.lang.String" itemvalue="3.7" />
</list> </list>
</value> </value>
</option> </option>

View File

@ -70,4 +70,7 @@
</LinkMapSettings> </LinkMapSettings>
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (drf-yasg)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (drf-yasg)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project> </project>

View File

@ -9,6 +9,7 @@ python:
env: env:
- DRF=3.7 - DRF=3.7
- DRF=3.8
jobs: jobs:
include: include:

View File

@ -11,7 +11,7 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
Compatible with Compatible with
- **Django Rest Framework**: 3.7.7 - **Django Rest Framework**: 3.7.7, 3.8.x
- **Django**: 1.11.x, 2.0.x - **Django**: 1.11.x, 2.0.x
- **Python**: 2.7, 3.4, 3.5, 3.6 - **Python**: 2.7, 3.4, 3.5, 3.6

View File

@ -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 * 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| 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 Additionally, ``@action``\ s, `@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based
respond to multiple HTTP methods and thus have multiple operations that must be decorated separately: api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
.. code-block:: python .. code-block:: python
@ -96,13 +96,13 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
class ArticleViewSet(viewsets.ModelViewSet): class ArticleViewSet(viewsets.ModelViewSet):
# method or 'methods' can be skipped because the list_route only handles a single method (GET) # method or 'methods' can be skipped because the list_route only handles a single method (GET)
@swagger_auto_schema(operation_description='GET /articles/today/') @swagger_auto_schema(operation_description='GET /articles/today/')
@list_route(methods=['get']) @action(detail=False, methods=['get'])
def today(self, request): def today(self, request):
... ...
@swagger_auto_schema(method='get', operation_description="GET /articles/{id}/image/") @swagger_auto_schema(method='get', operation_description="GET /articles/{id}/image/")
@swagger_auto_schema(method='post', operation_description="POST /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): def image(self, request, id=None):
... ...

View File

@ -1,5 +1,5 @@
# requirements for building and running tox # requirements for building and running tox
tox>=2.9.1 tox>=2.9.1,<3.0.0
detox>=0.11 detox>=0.11
-r setup.txt -r setup.txt

View File

@ -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 # no overrides to set, no use in doing more work
return 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', []) 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 # if the method is actually a function based view (@api_view), it will have a 'cls' attribute
view_cls = getattr(view_method, 'cls', None) 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 _methods = methods
if methods or method: if methods or method:
assert available_methods or http_method_names, "`method` or `methods` can only be specified " \ 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 bool(methods) != bool(method), "specify either method or methods"
assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \ assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \
" use `method` for a single argument" " 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" assert not any(mth in existing_data for mth in _methods), "http method defined multiple times"
if available_methods: 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" assert bool(http_method_names) != bool(bind_to_methods), "this should never happen"
if len(available_methods) > 1: if len(available_methods) > 1:
assert _methods, \ assert _methods, \
"on multi-method api_view, detail_route or list_route, you must specify swagger_auto_schema on " \ "on multi-method api_view, action, detail_route or list_route, you must specify " \
"a per-method basis using one of the `method` or `methods` arguments" "swagger_auto_schema on a per-method basis using one of the `method` or `methods` arguments"
else: else:
# for a single-method view we assume that single method as the decorator target # for a single-method view we assume that single method as the decorator target
_methods = _methods or available_methods _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 view_method._swagger_auto_schema = existing_data
else: else:
assert not _methods, \ assert not _methods, \
"the methods argument should only be specified when decorating a detail_route or list_route; you " \ "the methods argument should only be specified when decorating an action, detail_route or " \
"should also ensure that you put the swagger_auto_schema decorator AFTER (above) the _route decorator" "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" assert not existing_data, "swagger_auto_schema applied twice to method"
view_method._swagger_auto_schema = data view_method._swagger_auto_schema = data
@ -183,7 +184,7 @@ def is_list_view(path, method, view):
return True return True
if action in ('retrieve', 'update', 'partial_update', 'destroy') or detail is True or suffix == 'Instance': 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 return False
# for GenericAPIView, if it's a detail view it can't also be a list view # for GenericAPIView, if it's a detail view it can't also be a list view

View File

@ -3,6 +3,7 @@ import datetime
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
# noinspection PyDeprecation
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
from rest_framework.filters import OrderingFilter from rest_framework.filters import OrderingFilter
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
@ -89,6 +90,30 @@ class ArticleViewSet(viewsets.ModelViewSet):
swagger_schema = NoTitleAutoSchema swagger_schema = NoTitleAutoSchema
try:
from rest_framework.decorators import action
@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]) @swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
@list_route(methods=['get']) @list_route(methods=['get'])
def today(self, request): def today(self, request):
@ -98,6 +123,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
serializer = self.serializer_class(articles, many=True) serializer = self.serializer_class(articles, many=True)
return Response(serializer.data) return Response(serializer.data)
# noinspection PyDeprecation
@swagger_auto_schema(method='get', operation_description="image GET description override") @swagger_auto_schema(method='get', operation_description="image GET description override")
@swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer) @swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer)
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,)) @detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))

View File

@ -1,13 +1,14 @@
[tox] [tox]
envlist = envlist =
py27-django111-drf37, py27-django111-drf37,
py{34,35,36}-django{111,20}-drf37, py{34,35,36}-django{111,20}-drf{37,38},
py36-django20-drfmaster, py36-django20-drfmaster,
lint, docs lint, docs
[travis:env] [travis:env]
DRF = DRF =
3.7: drf37 3.7: drf37
3.8: drf38
master: drfmaster master: drfmaster
[testenv] [testenv]
@ -16,6 +17,7 @@ deps =
django20: Django>=2.0,<2.1 django20: Django>=2.0,<2.1
drf37: djangorestframework>=3.7.7,<3.8 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 # 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 drfmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz