Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc931677dc | |||
| 1904b0499e | |||
| 1e380fe68b | |||
| 6417bb3770 | |||
| e9f27442fc | |||
| cf8b912c10 | |||
| 3a37c4a019 | |||
| 8acab171ea | |||
| acc204e4ea |
+3
-3
@@ -13,8 +13,8 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
|
|||||||
|
|
||||||
Compatible with
|
Compatible with
|
||||||
|
|
||||||
- **Django Rest Framework**: 3.7.7, 3.8, 3.9
|
- **Django Rest Framework**: 3.8, 3.9
|
||||||
- **Django**: 1.11, 2.0, 2.1, 2.2
|
- **Django**: 1.11, 2.1, 2.2
|
||||||
- **Python**: 2.7, 3.5, 3.6, 3.7
|
- **Python**: 2.7, 3.5, 3.6, 3.7
|
||||||
|
|
||||||
Only the latest patch version of each ``major.minor`` series of Python, Django and Django REST Framework is supported.
|
Only the latest patch version of each ``major.minor`` series of Python, Django and Django REST Framework is supported.
|
||||||
@@ -235,7 +235,7 @@ Offline
|
|||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
If your schema is not accessible from the internet, you can run a local copy of
|
If your schema is not accessible from the internet, you can run a local copy of
|
||||||
`swagger-validator <https://hub.docker.com/r/swaggerapi/swagger-validator/>`_ and set the `VALIDATOR_URL` accordingly:
|
`swagger-validator <https://hub.docker.com/r/swaggerapi/swagger-validator/>`_ and set the ``VALIDATOR_URL`` accordingly:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ Changelog
|
|||||||
#########
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
**********
|
||||||
|
**1.16.1**
|
||||||
|
**********
|
||||||
|
|
||||||
|
*Release date: Jul 16, 2019*
|
||||||
|
|
||||||
|
- **FIXED:** fixed DRF 3.10 compatibility (:pr:`408`, :issue:`410`, :issue:`411`)
|
||||||
|
- **IMPROVED:** better enum type detection for nested `ChoiceField`s (:pr:`400`)
|
||||||
|
|
||||||
**********
|
**********
|
||||||
**1.16.0**
|
**1.16.0**
|
||||||
**********
|
**********
|
||||||
|
|||||||
@@ -87,14 +87,14 @@ 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, ``@action``\ s, ``@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based
|
Additionally, ``@action``\ s defined on the viewset, like function based api views, can respond to multiple HTTP
|
||||||
api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
|
methods and thus have multiple operations that must be decorated separately:
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
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 action only handles a single method (GET)
|
||||||
@swagger_auto_schema(operation_description='GET /articles/today/')
|
@swagger_auto_schema(operation_description='GET /articles/today/')
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def today(self, request):
|
def today(self, request):
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ inflection>=0.3.1
|
|||||||
six>=1.10.0
|
six>=1.10.0
|
||||||
uritemplate>=3.0.0
|
uritemplate>=3.0.0
|
||||||
|
|
||||||
djangorestframework>=3.7.7
|
djangorestframework>=3.8
|
||||||
Django>=1.11.7
|
Django>=1.11.7
|
||||||
|
|||||||
@@ -5,5 +5,7 @@ pytest-cov>=2.6.0
|
|||||||
pytest-xdist>=1.25.0
|
pytest-xdist>=1.25.0
|
||||||
pytest-django>=3.4.4
|
pytest-django>=3.4.4
|
||||||
datadiff==2.0.0
|
datadiff==2.0.0
|
||||||
|
psycopg2-binary==2.8.3
|
||||||
|
django-fake-model==0.1.4
|
||||||
|
|
||||||
-r testproj.txt
|
-r testproj.txt
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ Pillow>=4.3.0
|
|||||||
django-cors-headers>=2.1.0
|
django-cors-headers>=2.1.0
|
||||||
django-filter>=1.1.0,<2.0; python_version == "2.7"
|
django-filter>=1.1.0,<2.0; python_version == "2.7"
|
||||||
django-filter>=1.1.0; python_version >= "3.5"
|
django-filter>=1.1.0; python_version >= "3.5"
|
||||||
djangorestframework-camel-case>=0.2.0
|
#djangorestframework-camel-case>=0.2.0
|
||||||
|
# tempory replacement of broken lib
|
||||||
|
-e git+https://github.com/tfranzel/djangorestframework-camel-case.git@bd556d38fa7382acadfe91d93d92d99c663248a9#egg=djangorestframework_camel_case
|
||||||
djangorestframework-recursive>=0.1.2
|
djangorestframework-recursive>=0.1.2
|
||||||
dj-database-url>=0.4.2
|
dj-database-url>=0.4.2
|
||||||
user_agents>=1.1.0
|
user_agents>=1.1.0
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
python-3.7.1
|
python-3.7.3
|
||||||
|
|||||||
@@ -4,14 +4,23 @@ import re
|
|||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
|
||||||
import uritemplate
|
import uritemplate
|
||||||
|
import rest_framework
|
||||||
from coreapi.compat import urlparse
|
from coreapi.compat import urlparse
|
||||||
from rest_framework import versioning
|
from rest_framework import versioning
|
||||||
from rest_framework.compat import URLPattern, URLResolver, get_original_route
|
from rest_framework.compat import URLPattern, URLResolver, get_original_route
|
||||||
from rest_framework.schemas.generators import EndpointEnumerator as _EndpointEnumerator
|
from rest_framework.schemas.generators import EndpointEnumerator as _EndpointEnumerator
|
||||||
from rest_framework.schemas.generators import SchemaGenerator, endpoint_ordering, get_pk_name
|
from rest_framework.schemas.generators import endpoint_ordering, get_pk_name
|
||||||
from rest_framework.schemas.inspectors import get_pk_description
|
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
|
if Version(rest_framework.__version__) < Version('3.10'):
|
||||||
|
from rest_framework.schemas.generators import SchemaGenerator
|
||||||
|
from rest_framework.schemas.inspectors import get_pk_description
|
||||||
|
else:
|
||||||
|
from rest_framework.schemas import SchemaGenerator
|
||||||
|
from rest_framework.schemas.utils import get_pk_description
|
||||||
|
|
||||||
from . import openapi
|
from . import openapi
|
||||||
from .app_settings import swagger_settings
|
from .app_settings import swagger_settings
|
||||||
from .errors import SwaggerGenerationError
|
from .errors import SwaggerGenerationError
|
||||||
|
|||||||
@@ -656,7 +656,11 @@ class ChoiceFieldInspector(FieldInspector):
|
|||||||
serializer = get_parent_serializer(field)
|
serializer = get_parent_serializer(field)
|
||||||
if isinstance(serializer, serializers.ModelSerializer):
|
if isinstance(serializer, serializers.ModelSerializer):
|
||||||
model = getattr(getattr(serializer, 'Meta'), 'model')
|
model = getattr(getattr(serializer, 'Meta'), 'model')
|
||||||
model_field = get_model_field(model, field.source)
|
# Use the parent source for nested fields
|
||||||
|
model_field = get_model_field(model, field.source or field.parent.source)
|
||||||
|
# If the field has a base_field its type must be used
|
||||||
|
if getattr(model_field, "base_field", None):
|
||||||
|
model_field = model_field.base_field
|
||||||
if model_field:
|
if model_field:
|
||||||
model_type = get_basic_type_info(model_field)
|
model_type = get_basic_type_info(model_field)
|
||||||
if model_type:
|
if model_type:
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
|||||||
|
|
||||||
if len(available_http_methods) > 1:
|
if len(available_http_methods) > 1:
|
||||||
assert _methods, \
|
assert _methods, \
|
||||||
"on multi-method api_view, action, detail_route or list_route, you must specify " \
|
"on multi-method api_view or action, you must specify " \
|
||||||
"swagger_auto_schema on 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
|
||||||
@@ -179,8 +179,8 @@ 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 an action, detail_route or " \
|
"the methods argument should only be specified when decorating an action; " \
|
||||||
"list_route; you should also ensure that you put the swagger_auto_schema decorator " \
|
"you should also ensure that you put the swagger_auto_schema decorator " \
|
||||||
"AFTER (above) the _route 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
|
||||||
@@ -215,7 +215,7 @@ def is_list_view(path, method, view):
|
|||||||
:param APIView view: target view
|
:param APIView view: target view
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
# for ViewSets, it could be the default 'list' action, or a list_route
|
# for ViewSets, it could be the default 'list' action, or an @action(detail=False)
|
||||||
action = getattr(view, 'action', '')
|
action = getattr(view, 'action', '')
|
||||||
method = getattr(view, action, None) or method
|
method = getattr(view, action, None) or method
|
||||||
detail = getattr(method, 'detail', None)
|
detail = getattr(method, 'detail', None)
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import functools
|
|
||||||
|
|
||||||
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.filters import OrderingFilter
|
from rest_framework.filters import OrderingFilter
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
from rest_framework.parsers import FileUploadParser, MultiPartParser
|
from rest_framework.parsers import FileUploadParser, MultiPartParser
|
||||||
@@ -93,17 +91,10 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
swagger_schema = NoTitleAutoSchema
|
swagger_schema = NoTitleAutoSchema
|
||||||
|
|
||||||
try:
|
from rest_framework.decorators import action
|
||||||
from rest_framework.decorators import action
|
|
||||||
list_route = functools.partial(action, detail=False)
|
|
||||||
detail_route = functools.partial(action, detail=True)
|
|
||||||
except ImportError:
|
|
||||||
# TODO: remove when dropping support for DRF 3.7
|
|
||||||
action = None
|
|
||||||
from rest_framework.decorators import list_route, detail_route
|
|
||||||
|
|
||||||
@swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
|
@swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
|
||||||
@list_route(methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def today(self, request):
|
def today(self, request):
|
||||||
today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min)
|
today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min)
|
||||||
today_max = datetime.datetime.combine(datetime.date.today(), datetime.time.max)
|
today_max = datetime.datetime.combine(datetime.date.today(), datetime.time.max)
|
||||||
@@ -118,7 +109,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
|||||||
type=openapi.TYPE_INTEGER,
|
type=openapi.TYPE_INTEGER,
|
||||||
description="this should not crash (form parameter on DELETE method)"
|
description="this should not crash (form parameter on DELETE method)"
|
||||||
)])
|
)])
|
||||||
@detail_route(methods=['get', 'post', 'delete'], parser_classes=(MultiPartParser, FileUploadParser))
|
@action(detail=True, methods=['get', 'post', 'delete'], parser_classes=(MultiPartParser, FileUploadParser))
|
||||||
def image(self, request, slug=None):
|
def image(self, request, slug=None):
|
||||||
"""
|
"""
|
||||||
image method docstring
|
image method docstring
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.pagination import BasePagination
|
||||||
|
|
||||||
from .models import Identity, Person
|
from .models import Identity, Person
|
||||||
from .serializers import IdentitySerializer, PersonSerializer
|
from .serializers import IdentitySerializer, PersonSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownPagination(BasePagination):
|
||||||
|
paginator_query_args = ['unknown_paginator']
|
||||||
|
|
||||||
|
|
||||||
class PersonViewSet(viewsets.ModelViewSet):
|
class PersonViewSet(viewsets.ModelViewSet):
|
||||||
model = Person
|
model = Person
|
||||||
queryset = Person.objects
|
queryset = Person.objects
|
||||||
serializer_class = PersonSerializer
|
serializer_class = PersonSerializer
|
||||||
|
pagination_class = UnknownPagination
|
||||||
|
|
||||||
|
|
||||||
class IdentityViewSet(viewsets.ModelViewSet):
|
class IdentityViewSet(viewsets.ModelViewSet):
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
import rest_framework
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from packaging.version import Version
|
||||||
|
if Version(rest_framework.__version__) < Version('3.10'):
|
||||||
|
from rest_framework.compat import MaxLengthValidator, MinValueValidator
|
||||||
|
else:
|
||||||
|
from django.core.validators import MaxLengthValidator, MinValueValidator
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.compat import MaxLengthValidator, MinValueValidator
|
|
||||||
|
|
||||||
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer
|
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from drf_yasg import openapi
|
||||||
|
from drf_yasg.inspectors import NotHandled, PaginatorInspector
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownPaginatorInspector(PaginatorInspector):
|
||||||
|
def get_paginator_parameters(self, paginator):
|
||||||
|
if hasattr(paginator, 'paginator_query_args'):
|
||||||
|
return [openapi.Parameter(name=arg, in_=openapi.IN_QUERY, type=openapi.TYPE_STRING)
|
||||||
|
for arg in getattr(paginator, 'paginator_query_args')]
|
||||||
|
|
||||||
|
return NotHandled
|
||||||
@@ -140,7 +140,12 @@ SWAGGER_SETTINGS = {
|
|||||||
'clientId': OAUTH2_CLIENT_ID,
|
'clientId': OAUTH2_CLIENT_ID,
|
||||||
'clientSecret': OAUTH2_CLIENT_SECRET,
|
'clientSecret': OAUTH2_CLIENT_SECRET,
|
||||||
'appName': OAUTH2_APP_NAME,
|
'appName': OAUTH2_APP_NAME,
|
||||||
}
|
},
|
||||||
|
"DEFAULT_PAGINATOR_INSPECTORS": [
|
||||||
|
'testproj.inspectors.UnknownPaginatorInspector',
|
||||||
|
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||||
|
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
REDOC_SETTINGS = {
|
REDOC_SETTINGS = {
|
||||||
|
|||||||
@@ -276,7 +276,10 @@ paths:
|
|||||||
get:
|
get:
|
||||||
operationId: people_list
|
operationId: people_list
|
||||||
description: ''
|
description: ''
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: unknown_paginator
|
||||||
|
in: query
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
from django.contrib.postgres import fields as postgres_fields
|
||||||
|
from django.db import models
|
||||||
from django.utils.inspect import get_func_args
|
from django.utils.inspect import get_func_args
|
||||||
from rest_framework import routers, serializers, viewsets
|
from rest_framework import routers, serializers, viewsets
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from django_fake_model import models as fake_models
|
||||||
from drf_yasg import codecs, openapi
|
from drf_yasg import codecs, openapi
|
||||||
from drf_yasg.codecs import yaml_sane_load
|
from drf_yasg.codecs import yaml_sane_load
|
||||||
from drf_yasg.errors import SwaggerGenerationError
|
from drf_yasg.errors import SwaggerGenerationError
|
||||||
@@ -230,3 +233,43 @@ def test_choice_field(choices, expected_type):
|
|||||||
property_schema = swagger['definitions']['Detail']['properties']['detail']
|
property_schema = swagger['definitions']['Detail']['properties']['detail']
|
||||||
|
|
||||||
assert property_schema == openapi.Schema(title='Detail', type=expected_type, enum=choices)
|
assert property_schema == openapi.Schema(title='Detail', type=expected_type, enum=choices)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('choices, field, expected_type', [
|
||||||
|
([1, 2, 3], models.IntegerField, openapi.TYPE_INTEGER),
|
||||||
|
(["A", "B"], models.CharField, openapi.TYPE_STRING),
|
||||||
|
])
|
||||||
|
def test_nested_choice_in_array_field(choices, field, expected_type):
|
||||||
|
|
||||||
|
# Create a model class on the fly to avoid warnings about using the several
|
||||||
|
# model class name several times
|
||||||
|
model_class = type(
|
||||||
|
"%sModel" % field.__name__,
|
||||||
|
(fake_models.FakeModel,),
|
||||||
|
{
|
||||||
|
"array": postgres_fields.ArrayField(
|
||||||
|
field(choices=((i, "choice %s" % i) for i in choices))
|
||||||
|
),
|
||||||
|
"__module__": "test_models",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
class ArraySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = model_class
|
||||||
|
fields = ("array",)
|
||||||
|
|
||||||
|
class ArrayViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = ArraySerializer
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'arrays', ArrayViewSet, **_basename_or_base_name('arrays'))
|
||||||
|
|
||||||
|
generator = OpenAPISchemaGenerator(
|
||||||
|
info=openapi.Info(title='Test array model generator', default_version='v1'),
|
||||||
|
patterns=router.urls
|
||||||
|
)
|
||||||
|
|
||||||
|
swagger = generator.get_schema(None, True)
|
||||||
|
property_schema = swagger['definitions']['Array']['properties']['array']['items']
|
||||||
|
assert property_schema == openapi.Schema(title='Array', type=expected_type, enum=choices)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ isolated_build_env = .package
|
|||||||
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
|
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
|
||||||
envlist =
|
envlist =
|
||||||
py27-django111-drf39-typing,
|
py27-django111-drf39-typing,
|
||||||
py{27,35,36}-django111-drf{37,38,39},
|
py27-django111-drf{38,39},
|
||||||
py{35,36,37}-django{20,21,22}-drf{37,38,39},
|
py{35,36}-django{111,21,22}-drf{38,39},
|
||||||
|
py37-django{21,22}-drf{38,39},
|
||||||
|
py37-django{21,22}-drf310,
|
||||||
djmaster, lint, docs
|
djmaster, lint, docs
|
||||||
|
|
||||||
[testenv:.package]
|
[testenv:.package]
|
||||||
@@ -17,13 +19,12 @@ deps =
|
|||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
django111: Django>=1.11,<2.0
|
django111: Django>=1.11,<2.0
|
||||||
django20: Django>=2.0,<2.1
|
|
||||||
django21: Django>=2.1,<2.2
|
django21: Django>=2.1,<2.2
|
||||||
django22: Django>=2.2,<2.3
|
django22: Django>=2.2,<2.3
|
||||||
|
|
||||||
drf37: djangorestframework>=3.7.7,<3.8
|
drf38: djangorestframework>=3.8,<3.9
|
||||||
drf38: djangorestframework>=3.8.0,<3.9
|
|
||||||
drf39: djangorestframework>=3.9,<3.10
|
drf39: djangorestframework>=3.9,<3.10
|
||||||
|
drf310: djangorestframework>=3.10
|
||||||
|
|
||||||
typing: typing>=3.6.6
|
typing: typing>=3.6.6
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user