Compare commits
101 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
728c02356c | |
|
|
9ccf24c27a | |
|
|
8aa255cf56 | |
|
|
7491d330a8 | |
|
|
ebe21b77c6 | |
|
|
17da098940 | |
|
|
a872eb66d6 | |
|
|
6a1166deb5 | |
|
|
b700191f46 | |
|
|
5c25ecd8f2 | |
|
|
8fd27664f1 | |
|
|
456b697ca2 | |
|
|
9966297f87 | |
|
|
27007a9cf4 | |
|
|
a72e5b2899 | |
|
|
13311582ea | |
|
|
9a89d8ccb0 | |
|
|
16f67cd8c2 | |
|
|
99fa7c25ca | |
|
|
97e70d9d16 | |
|
|
ee086a6eec | |
|
|
4af38c970a | |
|
|
95337f85ad | |
|
|
1352c2a23b | |
|
|
8578b93eba | |
|
|
212891b1b8 | |
|
|
ab6444a32e | |
|
|
2e0f9a19a9 | |
|
|
cda808fe11 | |
|
|
e6219ab8b7 | |
|
|
bc931677dc | |
|
|
1904b0499e | |
|
|
1e380fe68b | |
|
|
6417bb3770 | |
|
|
e9f27442fc | |
|
|
cf8b912c10 | |
|
|
3a37c4a019 | |
|
|
8acab171ea | |
|
|
acc204e4ea | |
|
|
1635e5e095 | |
|
|
753be1a8bd | |
|
|
2656696a0f | |
|
|
a083d3cf7c | |
|
|
eed8a8d3ec | |
|
|
d04f27f40f | |
|
|
db154d196a | |
|
|
60e1346150 | |
|
|
69b628a7af | |
|
|
64d9d42aa9 | |
|
|
e9d5344de3 | |
|
|
b5aba7243d | |
|
|
91ef83e830 | |
|
|
0991c806c7 | |
|
|
59e86ff72f | |
|
|
548489a539 | |
|
|
887b53300a | |
|
|
f692fe7c98 | |
|
|
017ae3d240 | |
|
|
b57413023b | |
|
|
4014c69689 | |
|
|
7bb4700003 | |
|
|
86c1675c58 | |
|
|
81f0b1a2ea | |
|
|
298a9745df | |
|
|
652a33a54d | |
|
|
340a60324c | |
|
|
f348084d85 | |
|
|
904c43a167 | |
|
|
4c78a683f4 | |
|
|
4da09830ac | |
|
|
34ed1e20a2 | |
|
|
62d97a80bc | |
|
|
e108ddbb48 | |
|
|
75a5d866be | |
|
|
f189426901 | |
|
|
b4900ebd6a | |
|
|
c593b3fcfb | |
|
|
9caeed781e | |
|
|
3377ef08ea | |
|
|
5c2c39c82d | |
|
|
e538e0713a | |
|
|
76c8fe0646 | |
|
|
3d43ee6748 | |
|
|
583e404ed8 | |
|
|
d62243599b | |
|
|
6df3523675 | |
|
|
d2bd838325 | |
|
|
bacab20f0b | |
|
|
df82fe59d7 | |
|
|
7c5a0b7176 | |
|
|
69a1e62ed3 | |
|
|
3806d6efd5 | |
|
|
58e6dae548 | |
|
|
8e2228fe5f | |
|
|
762467285c | |
|
|
0e62fd6f2b | |
|
|
e266eeda60 | |
|
|
4b1098369c | |
|
|
bda545e85f | |
|
|
c1d3d4fe3c | |
|
|
7f3ffe80a9 |
|
|
@ -3,6 +3,7 @@ testproj/db.sqlite3
|
|||
testproj/staticfiles
|
||||
\.pytest_cache/
|
||||
docs/\.doctrees/
|
||||
pip-wheel-metadata/
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Python template
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/.cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.eggs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tox" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/docs/.doctrees" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
requirements_file: requirements/docs.txt
|
||||
|
||||
build:
|
||||
image: latest
|
||||
|
||||
python:
|
||||
version: 3.6
|
||||
setup_py_install: false
|
||||
pip_install: true # need this for correct pyproject.toml handling
|
||||
extra_requirements:
|
||||
- validation
|
||||
|
||||
# extra formats in addition to the default HTML web docs
|
||||
formats:
|
||||
- pdf
|
||||
34
.travis.yml
34
.travis.yml
|
|
@ -1,26 +1,20 @@
|
|||
language: python
|
||||
sudo: false
|
||||
python:
|
||||
- '2.7'
|
||||
- '3.4'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
|
||||
dist: xenial
|
||||
|
||||
cache: pip
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
include:
|
||||
- # workaround for python 3.7 on travis https://github.com/travis-ci/travis-ci/issues/9815#issuecomment-401756442
|
||||
stage: test
|
||||
python: '3.7'
|
||||
dist: xenial
|
||||
sudo: required
|
||||
- python: '3.6'
|
||||
env: TOXENV=djmaster
|
||||
- # readthedocs uses python 3.5 for building
|
||||
python: '3.5'
|
||||
env: TOXENV=docs
|
||||
- python: '3.6'
|
||||
- python: '3.7'
|
||||
env: TOXENV=djmaster
|
||||
- python: '3.7'
|
||||
env: TOXENV=lint
|
||||
|
||||
- stage: publish
|
||||
|
|
@ -59,22 +53,16 @@ script:
|
|||
after_success:
|
||||
- |
|
||||
if [[ -z "$TOXENV" && -z "$PYPI_DEPLOY" ]]; then
|
||||
chmod +x coverage.sh
|
||||
./coverage.sh
|
||||
coverage combine || true
|
||||
coverage report
|
||||
codecov
|
||||
fi
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^release\/.*$/
|
||||
- /^v?\d+\.\d+(\.\d+)?(-?\S+)?$/
|
||||
|
||||
stages:
|
||||
- test
|
||||
- name: publish
|
||||
if: tag IS present
|
||||
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: always
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
Contributing
|
||||
############
|
||||
|
||||
Contributions are always welcome and appreciated! Here are some ways you can contribut.
|
||||
Contributions are always welcome and appreciated! Here are some ways you can contribute.
|
||||
|
||||
******
|
||||
Issues
|
||||
|
|
@ -57,7 +57,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
|
||||
.. code:: console
|
||||
|
||||
(venv) $ python testproj/manage.py generate_swagger ../tests/reference.yaml --overwrite --user admin --url http://test.local:8002/
|
||||
(venv) $ python testproj/manage.py generate_swagger tests/reference.yaml --overwrite --user admin --url http://test.local:8002/
|
||||
|
||||
After checking the git diff to verify that no unexpected changes appeared, you should commit the new
|
||||
``reference.yaml`` together with your changes.
|
||||
|
|
@ -95,7 +95,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
|
||||
#. **Your code must pass all the required travis jobs before it is merged**
|
||||
|
||||
As of now, this consists of running on Python 2.7, 3.4, 3.5 and 3.6, and building the docs succesfully.
|
||||
As of now, this consists of running on Python 2.7, 3.5, 3.6 and 3.7, and building the docs succesfully.
|
||||
|
||||
******************
|
||||
Maintainer's notes
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ License
|
|||
BSD 3-Clause License
|
||||
********************
|
||||
|
||||
Copyright (c) 2018, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
|
||||
Copyright (c) 2017 - 2019, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
|
|
|||
68
README.rst
68
README.rst
|
|
@ -7,13 +7,15 @@ drf-yasg - Yet another Swagger generator
|
|||
|
||||
|travis| |nbsp| |codecov| |nbsp| |rtd-badge| |nbsp| |pypi-version|
|
||||
|
||||
|bmac-button|
|
||||
|
||||
Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framework API.
|
||||
|
||||
Compatible with
|
||||
|
||||
- **Django Rest Framework**: 3.7.7, 3.8, 3.9
|
||||
- **Django**: 1.11, 2.0, 2.1
|
||||
- **Python**: 2.7, 3.4, 3.5, 3.6, 3.7
|
||||
- **Django Rest Framework**: 3.8, 3.9, 3.10, 3.11
|
||||
- **Django**: 1.11, 2.2, 3.0
|
||||
- **Python**: 2.7, 3.6, 3.7, 3.8
|
||||
|
||||
Only the latest patch version of each ``major.minor`` series of Python, Django and Django REST Framework is supported.
|
||||
|
||||
|
|
@ -29,9 +31,7 @@ Resources:
|
|||
* **Changelog**: https://drf-yasg.readthedocs.io/en/stable/changelog.html
|
||||
* **Live demo**: https://drf-yasg-demo.herokuapp.com/
|
||||
|
||||
.. image:: https://www.herokucdn.com/deploy/button.svg
|
||||
:target: https://heroku.com/deploy?template=https://github.com/axnsan12/drf-yasg
|
||||
:alt: heroku deploy button
|
||||
|heroku-button|
|
||||
|
||||
********
|
||||
Features
|
||||
|
|
@ -47,8 +47,7 @@ Features
|
|||
`redoc <https://github.com/Rebilly/ReDoc>`_ for viewing the generated documentation
|
||||
- schema view is cacheable out of the box
|
||||
- generated Swagger schema can be automatically validated by
|
||||
`swagger-spec-validator <https://github.com/Yelp/swagger_spec_validator>`_ or
|
||||
`flex <https://github.com/pipermerriam/flex>`_
|
||||
`swagger-spec-validator <https://github.com/Yelp/swagger_spec_validator>`_
|
||||
- supports Django REST Framework API versioning with ``URLPathVersioning`` and ``NamespaceVersioning``; other DRF
|
||||
or custom versioning schemes are not currently supported
|
||||
|
||||
|
|
@ -166,7 +165,7 @@ a. ``get_schema_view`` parameters
|
|||
- ``patterns`` - passed to SchemaGenerator
|
||||
- ``urlconf`` - passed to SchemaGenerator
|
||||
- ``public`` - if False, includes only endpoints the current user has access to
|
||||
- ``validators`` - a list of validator names to apply on the generated schema; allowed values are ``flex``, ``ssv``
|
||||
- ``validators`` - a list of validator names to apply on the generated schema; only ``ssv`` is currently supported
|
||||
- ``generator_class`` - schema generator class to use; should be a subclass of ``OpenAPISchemaGenerator``
|
||||
- ``authentication_classes`` - authentication classes for the schema view itself
|
||||
- ``permission_classes`` - permission classes for the schema view itself
|
||||
|
|
@ -213,7 +212,7 @@ caching the schema view in-memory, with some sane defaults:
|
|||
|
||||
Given the numerous methods to manually customize the generated schema, it makes sense to validate the result to ensure
|
||||
it still conforms to OpenAPI 2.0. To this end, validation is provided at the generation point using python swagger
|
||||
libraries, and can be activated by passing :python:`validators=['flex', 'ssv']` to ``get_schema_view``; if the generated
|
||||
libraries, and can be activated by passing :python:`validators=['ssv']` to ``get_schema_view``; if the generated
|
||||
schema is not valid, a :python:`SwaggerValidationError` is raised by the handling codec.
|
||||
|
||||
**Warning:** This internal validation can slow down your server.
|
||||
|
|
@ -236,7 +235,7 @@ Offline
|
|||
^^^^^^^
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -307,45 +306,6 @@ For additional usage examples, you can take a look at the test project in the ``
|
|||
(venv) $ python manage.py runserver
|
||||
(venv) $ firefox localhost:8000/swagger/
|
||||
|
||||
**********
|
||||
Background
|
||||
**********
|
||||
|
||||
``OpenAPI 2.0``/``Swagger`` is a format designed to encode information about a Web API into an easily parsable schema
|
||||
that can then be used for rendering documentation, generating code, etc.
|
||||
|
||||
More details are available on `swagger.io <https://swagger.io/>`__ and on the `OpenAPI 2.0 specification
|
||||
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
|
||||
|
||||
From here on, the terms “OpenAPI” and “Swagger” are used interchangeably.
|
||||
|
||||
Swagger in Django Rest Framework
|
||||
================================
|
||||
|
||||
Since Django Rest Framework 3.7, there is now `built in support <http://www.django-rest-framework.org/api-guide/schemas/>`__
|
||||
for automatic OpenAPI 2.0 schema generation. However, this generation is based on the `coreapi <http://www.coreapi.org/>`__
|
||||
standard, which for the moment is vastly inferior to OpenAPI in both features and tooling support. In particular,
|
||||
the OpenAPI codec/compatibility layer provided has a few major problems:
|
||||
|
||||
* there is no support for documenting response schemas and status codes
|
||||
* nested schemas do not work properly
|
||||
* does not handle more complex fields such as ``FileField``, ``ChoiceField``, …
|
||||
|
||||
In short this makes the generated schema unusable for code generation, and mediocre at best for documentation.
|
||||
|
||||
Other libraries
|
||||
===============
|
||||
|
||||
There are currently two decent Swagger schema generators that I could find for django-rest-framework:
|
||||
|
||||
* `django-rest-swagger <https://github.com/marcgibbons/django-rest-swagger>`__
|
||||
* `drf-openapi <https://github.com/limdauto/drf_openapi>`__
|
||||
|
||||
``django-rest-swagger`` is just a wrapper around DRF 3.7 schema generation with an added UI, and
|
||||
thus presents the same problems, while also being unmaintained. ``drf-openapi`` was
|
||||
`discontinued by the author <https://github.com/limdauto/drf_openapi/commit/1673c6e039eec7f089336a83bdc31613f32f7e21>`_
|
||||
on April 3rd, 2018.
|
||||
|
||||
************************
|
||||
Third-party integrations
|
||||
************************
|
||||
|
|
@ -379,5 +339,13 @@ provided out of the box - if you have ``djangorestframework-recursive`` installe
|
|||
:target: https://drf-yasg.readthedocs.io/
|
||||
:alt: ReadTheDocs
|
||||
|
||||
.. |bmac-button| image:: https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png
|
||||
:target: https://www.buymeacoffee.com/cvijdea
|
||||
:alt: Buy Me A Coffee
|
||||
|
||||
.. |heroku-button| image:: https://www.herokucdn.com/deploy/button.svg
|
||||
:target: https://heroku.com/deploy?template=https://github.com/axnsan12/drf-yasg
|
||||
:alt: Heroku deploy button
|
||||
|
||||
.. |nbsp| unicode:: 0xA0
|
||||
:trim:
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
coverage combine || true
|
||||
coverage report
|
||||
codecov
|
||||
|
|
@ -3,6 +3,99 @@ Changelog
|
|||
#########
|
||||
|
||||
|
||||
**********
|
||||
**1.17.1**
|
||||
**********
|
||||
|
||||
*Release date: Feb 17, 2020*
|
||||
|
||||
- **FIXED:** fixed compatibility issue with CurrentUserDefault in Django Rest Framework 3.11
|
||||
- **FIXED:** respect `USERNAME_FIELD` in `generate_swagger` command (:pr:`486`)
|
||||
|
||||
**Support was dropped for Python 3.5, Django 2.0, Django 2.1, DRF 3.7**
|
||||
|
||||
**********
|
||||
**1.17.0**
|
||||
**********
|
||||
|
||||
*Release date: Oct 03, 2019*
|
||||
|
||||
- **ADDED:** added `JSONFieldInspector` for `JSONField` support (:pr:`417`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.23.11
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.14 (:issue:`398`)
|
||||
- **FIXED:** fixed a type hint support issue (:pr:`428`, :issue:`450`)
|
||||
- **FIXED:** fixed packaging issue caused by a missing requirement (:issue:`412`)
|
||||
|
||||
**********
|
||||
**1.16.1**
|
||||
**********
|
||||
|
||||
*Release date: Jul 16, 2019*
|
||||
|
||||
- **IMPROVED:** better enum type detection for nested `ChoiceField`\ s (:pr:`400`)
|
||||
- **FIXED:** fixed DRF 3.10 compatibility (:pr:`408`, :issue:`410`, :issue:`411`)
|
||||
|
||||
**********
|
||||
**1.16.0**
|
||||
**********
|
||||
|
||||
*Release date: Jun 13, 2019*
|
||||
|
||||
- **ADDED:** added `reference_resolver_class` attribute hook to `SwaggerAutoSchema` (:pr:`350`)
|
||||
- **ADDED:** added `operation_keys` attribute to `SwaggerAutoSchema`, along with `__init__` parameter (:pr:`355`)
|
||||
- **FIXED:** fixed potential crash on `issubclass` check without `isclass` check
|
||||
|
||||
**********
|
||||
**1.15.1**
|
||||
**********
|
||||
|
||||
*Release date: Jun 13, 2019*
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.22.3
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.8-1
|
||||
- **FIXED:** fixed an issue with inspection of typing hints on Python 2.7 (:issue:`363`)
|
||||
- **FIXED:** fixed an issue with inspection of typing hints on Python 3.7 (:issue:`371`)
|
||||
|
||||
**Python 3.4 support has been dropped!**
|
||||
|
||||
**********
|
||||
**1.15.0**
|
||||
**********
|
||||
|
||||
*Release date: Apr 01, 2019*
|
||||
|
||||
- **ADDED:** added ``is_list_view`` and ``has_list_response`` extension points to ``SwaggerAutoSchema`` (:issue:`331`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.22.0
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.4
|
||||
- **FIXED:** ``ListModelMixin`` will now always be treated as a list view (:issue:`306`)
|
||||
- **FIXED:** non-primtive values in field ``choices`` will now be handled properly (:issue:`340`)
|
||||
|
||||
**********
|
||||
**1.14.0**
|
||||
**********
|
||||
|
||||
*Release date: Mar 04, 2019*
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.21.0
|
||||
- **FIXED:** implicit ``ref_name`` collisions will now throw an exception
|
||||
- **FIXED:** ``RecursiveField`` will now also work as a child of ``ListSerializer`` (:pr:`321`)
|
||||
- **FIXED:** fixed ``minLength`` and ``maxLength`` for ``ListSerializer`` and ``ListField``
|
||||
- **FIXED:** the ``items`` property of ``Schema``, ``Parameter`` and ``Items`` objects was renamed to ``items_``; this
|
||||
is a *mildly breaking change* and was needed to fix the collision with the ``items`` method of ``dict`` (:pr:`308`)
|
||||
- **REMOVED:** the ``get_summary`` and ``get_description`` methods have been removed (previously deprecated in 1.12.0)
|
||||
|
||||
**********
|
||||
**1.13.0**
|
||||
**********
|
||||
|
||||
*Release date: Jan 29, 2019*
|
||||
|
||||
- **IMPROVED:** type hint inspection is now supported for collections and ``Optional`` (:pr:`272`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.20.5
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.2
|
||||
- **DEPRECATED:** quietly dropped support for the ``flex`` validator; it will still work if the library is installed,
|
||||
but the setup.py requirement was removed and the validator will be silently skipped if not installed (:issue:`285`)
|
||||
|
||||
**********
|
||||
**1.12.1**
|
||||
**********
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ author = 'Cristi V.'
|
|||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = get_distribution('drf_yasg').version
|
||||
if 'noscm' in release:
|
||||
raise AssertionError('Invalid package version string: %s. \n'
|
||||
'The documentation must be built with drf_yasg installed from a distribution package, '
|
||||
'which must have been built with a proper version number (i.e. from a full source checkout).'
|
||||
% (release,))
|
||||
|
||||
# The short X.Y.Z version.
|
||||
version = '.'.join(release.split('.')[:3])
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ some properties of the generated :class:`.Operation`. For example, in a ``ViewSe
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
"""partial_update method docstring"""
|
||||
|
|
@ -87,14 +89,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
|
||||
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
|
||||
api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
|
||||
Additionally, ``@action``\ 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
|
||||
|
||||
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/')
|
||||
@action(detail=False, methods=['get'])
|
||||
def today(self, request):
|
||||
|
|
@ -210,7 +212,8 @@ Schema generation of ``serializers.SerializerMethodField`` is supported in two w
|
|||
Serializer ``Meta`` nested class
|
||||
********************************
|
||||
|
||||
You can define some per-serializer options by adding a ``Meta`` class to your serializer, e.g.:
|
||||
You can define some per-serializer or per-field options by adding a ``Meta`` class to your ``Serializer`` or
|
||||
serializer ``Field``, e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -234,6 +237,64 @@ The available options are:
|
|||
which are converted to Swagger ``Schema`` attribute names according to :func:`.make_swagger_name`.
|
||||
Attribute names and values must conform to the `OpenAPI 2.0 specification <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject>`_.
|
||||
|
||||
Suppose you wanted to model an email using a `JSONField` to store the subject and body for performance reasons:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
|
||||
class Email(models.Model):
|
||||
# Store data as JSON, but the data should be made up of
|
||||
# an object that has two properties, "subject" and "body"
|
||||
# Example:
|
||||
# {
|
||||
# "subject": "My Title",
|
||||
# "body": "The body of the message.",
|
||||
# }
|
||||
message = JSONField()
|
||||
|
||||
To instruct ``drf-yasg`` to output an OpenAPI schema that matches this, create a custom ``JSONField``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class EmailMessageField(serializers.JSONField):
|
||||
class Meta:
|
||||
swagger_schema_fields = {
|
||||
"type": openapi.TYPE_OBJECT,
|
||||
"title": "Email",
|
||||
"properties": {
|
||||
"subject": openapi.Schema(
|
||||
title="Email subject",
|
||||
type=openapi.TYPE_STRING,
|
||||
),
|
||||
"body": openapi.Schema(
|
||||
title="Email body",
|
||||
type=openapi.TYPE_STRING,
|
||||
),
|
||||
},
|
||||
"required": ["subject", "body"],
|
||||
}
|
||||
|
||||
class EmailSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Email
|
||||
fields = "__all__"
|
||||
|
||||
message = EmailMessageField()
|
||||
|
||||
.. Warning::
|
||||
|
||||
Overriding a default ``Field`` generated by a ``ModelSerializer`` will also override automatically
|
||||
generated validators for that ``Field``. To add ``Serializer`` validation back in manually, see the relevant
|
||||
`DRF Validators`_ and `DRF Fields`_ documentation.
|
||||
|
||||
One example way to do this is to set the ``default_validators`` attribute on a field.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class EmailMessageField(serializers.JSONField):
|
||||
default_validators = [my_custom_email_validator]
|
||||
...
|
||||
|
||||
*************************
|
||||
Subclassing and extending
|
||||
|
|
@ -376,7 +437,7 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
|||
|
||||
|
||||
class AnotherSerializer(serializers.ModelSerializer):
|
||||
chilf = OneSerializer()
|
||||
child = OneSerializer()
|
||||
|
||||
class Meta:
|
||||
model = SomeParentModel
|
||||
|
|
@ -387,3 +448,5 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
|||
|
||||
|
||||
.. _Python 3 type hinting: https://docs.python.org/3/library/typing.html
|
||||
.. _DRF Validators: https://www.django-rest-framework.org/api-guide/validators/
|
||||
.. _DRF Fields: https://www.django-rest-framework.org/api-guide/fields/#validators
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ This library generates OpenAPI 2.0 documents. The authoritative specification fo
|
|||
be the official documentation over at `swagger.io <https://swagger.io/>`__ and the `OpenAPI 2.0 specification
|
||||
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
|
||||
|
||||
Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
|
||||
Because the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
|
||||
is structured, starting from the root ``Swagger`` object.
|
||||
|
||||
* :class:`.Swagger` object
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ A very simple working configuration was provided by :ghuser:`Vigrond`, originall
|
|||
'type': 'oauth2',
|
||||
'authorizationUrl': '/yourapp/o/authorize',
|
||||
'tokenUrl': '/yourapp/o/token/',
|
||||
'flow": "accessCode',
|
||||
'flow': 'accessCode',
|
||||
'scopes': {
|
||||
'read:groups': 'read groups',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ to this list.
|
|||
:class:`'drf_yasg.inspectors.ChoiceFieldInspector' <.inspectors.ChoiceFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.JSONFieldInspector' <.inspectors.JSONFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.SerializerMethodFieldInspector' <.inspectors.SerializerMethodFieldInspector>`, |br| \
|
||||
|
|
@ -340,7 +341,7 @@ values for Parameters.
|
|||
OAUTH2_REDIRECT_URL
|
||||
-------------------
|
||||
|
||||
Used when OAuth2 authenitcation of API requests via swagger-ui is desired. If ``None`` is passed, the
|
||||
Used when OAuth2 authentication of API requests via swagger-ui is desired. If ``None`` is passed, the
|
||||
``oauth2RedirectUrl`` parameter will be set to ``{% static 'drf-yasg/swagger-ui-dist/oauth2-redirect.html' %}``. This
|
||||
is the default `https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html <oauth2-redirect>`_
|
||||
file provided by ``swagger-ui``.
|
||||
|
|
@ -351,7 +352,7 @@ file provided by ``swagger-ui``.
|
|||
OAUTH2_CONFIG
|
||||
-------------
|
||||
|
||||
Used when OAuth2 authenitcation of API requests via swagger-ui is desired. Provides OAuth2 configuration parameters
|
||||
Used when OAuth2 authentication of API requests via swagger-ui is desired. Provides OAuth2 configuration parameters
|
||||
to the ``SwaggerUIBundle#initOAuth`` method, and must be a dictionary. See
|
||||
`OAuth2 configuration <https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md>`_.
|
||||
|
||||
|
|
@ -454,7 +455,7 @@ FETCH_SCHEMA_WITH_QUERY
|
|||
|
||||
Fetch the OpenAPI document using the query parameters passed to the ReDoc page request.
|
||||
|
||||
**Default**: :python:`'True` |br|
|
||||
**Default**: :python:`True` |br|
|
||||
*Maps to parameter*: -
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"redoc": "^2.0.0-rc.0",
|
||||
"swagger-ui-dist": "^3.20.4"
|
||||
"redoc": "^2.0.0-rc.14",
|
||||
"swagger-ui-dist": "^3.23.11"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ ruamel.yaml>=0.15.34
|
|||
inflection>=0.3.1
|
||||
six>=1.10.0
|
||||
uritemplate>=3.0.0
|
||||
packaging
|
||||
|
||||
djangorestframework>=3.7.7
|
||||
djangorestframework>=3.8
|
||||
Django>=1.11.7
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
-r lint.txt
|
||||
|
||||
tox-battery>=0.5
|
||||
django-oauth-toolkit
|
||||
|
|
|
|||
|
|
@ -5,5 +5,7 @@ pytest-cov>=2.6.0
|
|||
pytest-xdist>=1.25.0
|
||||
pytest-django>=3.4.4
|
||||
datadiff==2.0.0
|
||||
psycopg2-binary==2.8.3
|
||||
django-fake-model==0.1.4
|
||||
|
||||
-r testproj.txt
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
# test project requirements
|
||||
Pillow>=4.3.0
|
||||
pygments>=2.2.0
|
||||
django-cors-headers>=2.1.0
|
||||
django-filter>=1.1.0,<2.0; python_version == "2.7"
|
||||
django-filter>=1.1.0; python_version >= "3.4"
|
||||
djangorestframework-camel-case>=0.2.0
|
||||
django-filter>=1.1.0; python_version >= "3.5"
|
||||
djangorestframework-camel-case>=1.1.2
|
||||
djangorestframework-recursive>=0.1.2
|
||||
dj-database-url>=0.4.2
|
||||
user_agents>=1.1.0
|
||||
# django-oauth-toolkit 1.2 does not support Django 1.11
|
||||
django-oauth-toolkit>=1.1.0,<1.2.0
|
||||
django-cors-headers
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
# requirements for the validation feature
|
||||
flex>=6.11.1
|
||||
swagger-spec-validator>=2.1.0
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
python-3.7.1
|
||||
python-3.7.3
|
||||
|
|
|
|||
25
setup.py
25
setup.py
|
|
@ -19,6 +19,18 @@ with io.open('README.rst', encoding='utf-8') as readme:
|
|||
requirements = read_req('base.txt')
|
||||
requirements_validation = read_req('validation.txt')
|
||||
|
||||
py3_supported_range = (5, 8)
|
||||
|
||||
# convert inclusive range to exclusive range
|
||||
py3_supported_range = (py3_supported_range[0], py3_supported_range[1] + 1)
|
||||
python_requires = ", ".join([">=2.7"] + ["!=3.{}.*".format(v) for v in range(0, py3_supported_range[0])])
|
||||
python_classifiers = [
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
] + ['Programming Language :: Python :: 3.{}'.format(v) for v in range(*py3_supported_range)]
|
||||
|
||||
|
||||
def drf_yasg_setup(**kwargs):
|
||||
setup(
|
||||
|
|
@ -38,28 +50,21 @@ def drf_yasg_setup(**kwargs):
|
|||
author_email='cristi@cvjd.me',
|
||||
keywords='drf django django-rest-framework schema swagger openapi codegen swagger-codegen '
|
||||
'documentation drf-yasg django-rest-swagger drf-openapi',
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
python_requires=python_requires,
|
||||
classifiers=[
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Operating System :: OS Independent',
|
||||
'Environment :: Web Environment',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.0',
|
||||
'Framework :: Django :: 2.1',
|
||||
'Framework :: Django :: 2.2',
|
||||
'Topic :: Documentation',
|
||||
'Topic :: Software Development :: Code Generators',
|
||||
],
|
||||
] + python_classifiers,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ SWAGGER_DEFAULTS = {
|
|||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||
'drf_yasg.inspectors.FileFieldInspector',
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.JSONFieldInspector',
|
||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.SerializerMethodFieldInspector',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from six import raise_from
|
||||
from six import binary_type, raise_from, text_type
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
|
@ -15,8 +15,12 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def _validate_flex(spec):
|
||||
from flex.core import parse as validate_flex
|
||||
from flex.exceptions import ValidationError
|
||||
try:
|
||||
from flex.core import parse as validate_flex
|
||||
from flex.exceptions import ValidationError
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
try:
|
||||
validate_flex(spec)
|
||||
except ValidationError as ex:
|
||||
|
|
@ -172,7 +176,14 @@ class SaneYamlDumper(yaml.SafeDumper):
|
|||
node.flow_style = best_style
|
||||
return node
|
||||
|
||||
def represent_text(self, text):
|
||||
if "\n" in text:
|
||||
return self.represent_scalar('tag:yaml.org,2002:str', text, style='|')
|
||||
return self.represent_scalar('tag:yaml.org,2002:str', text)
|
||||
|
||||
|
||||
SaneYamlDumper.add_representer(binary_type, SaneYamlDumper.represent_text)
|
||||
SaneYamlDumper.add_representer(text_type, SaneYamlDumper.represent_text)
|
||||
SaneYamlDumper.add_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||
SaneYamlDumper.add_multi_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ import logging
|
|||
import re
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import rest_framework
|
||||
import uritemplate
|
||||
from coreapi.compat import urlparse
|
||||
from packaging.version import Version
|
||||
from rest_framework import versioning
|
||||
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 SchemaGenerator, endpoint_ordering, get_pk_name
|
||||
from rest_framework.schemas.inspectors import get_pk_description
|
||||
from rest_framework.schemas.generators import endpoint_ordering, get_pk_name
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
from . import openapi
|
||||
|
|
@ -19,6 +20,14 @@ from .inspectors.field import get_basic_type_info, get_queryset_field, get_query
|
|||
from .openapi import ReferenceResolver, SwaggerDict
|
||||
from .utils import force_real_str, get_consumes, get_produces
|
||||
|
||||
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
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PATH_PARAMETER_RE = re.compile(r'{(?P<parameter>\w+)}')
|
||||
|
|
@ -160,6 +169,7 @@ class OpenAPISchemaGenerator(object):
|
|||
Method implementations shamelessly stolen and adapted from rest-framework ``SchemaGenerator``.
|
||||
"""
|
||||
endpoint_enumerator_class = EndpointEnumerator
|
||||
reference_resolver_class = ReferenceResolver
|
||||
|
||||
def __init__(self, info, version='', url=None, patterns=None, urlconf=None):
|
||||
"""
|
||||
|
|
@ -230,7 +240,7 @@ class OpenAPISchemaGenerator(object):
|
|||
def get_schema(self, request=None, public=False):
|
||||
"""Generate a :class:`.Swagger` object representing the API schema.
|
||||
|
||||
:param request: the request used for filtering accesible endpoints and finding the spec URI
|
||||
:param request: the request used for filtering accessible endpoints and finding the spec URI
|
||||
:type request: rest_framework.request.Request or None
|
||||
:param bool public: if True, all endpoints are included regardless of access through `request`
|
||||
|
||||
|
|
@ -238,7 +248,7 @@ class OpenAPISchemaGenerator(object):
|
|||
:rtype: openapi.Swagger
|
||||
"""
|
||||
endpoints = self.get_endpoints(request)
|
||||
components = ReferenceResolver(openapi.SCHEMA_DEFINITIONS)
|
||||
components = self.reference_resolver_class(openapi.SCHEMA_DEFINITIONS, force_init=True)
|
||||
self.consumes = get_consumes(api_settings.DEFAULT_PARSER_CLASSES)
|
||||
self.produces = get_produces(api_settings.DEFAULT_RENDERER_CLASSES)
|
||||
paths, prefix = self.get_paths(endpoints, components, request, public)
|
||||
|
|
@ -440,7 +450,7 @@ class OpenAPISchemaGenerator(object):
|
|||
if view_inspector_cls is None:
|
||||
return None
|
||||
|
||||
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
|
||||
view_inspector = view_inspector_cls(view, path, method, components, request, overrides, operation_keys)
|
||||
operation = view_inspector.get_operation(operation_keys)
|
||||
if operation is None:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from .base import (
|
|||
)
|
||||
from .field import (
|
||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||
SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||
InlineSerializerInspector, JSONFieldInspector, RecursiveFieldInspector, ReferencingSerializerInspector,
|
||||
RelatedFieldInspector, SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||
)
|
||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
from .view import SwaggerAutoSchema
|
||||
|
|
@ -24,7 +24,7 @@ __all__ = [
|
|||
|
||||
# field inspectors
|
||||
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'JSONFieldInspector',
|
||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector', 'SerializerMethodFieldInspector',
|
||||
|
||||
# view inspectors
|
||||
|
|
|
|||
|
|
@ -272,7 +272,8 @@ class FieldInspector(BaseInspector):
|
|||
assert not isinstance(field, openapi.SwaggerDict), "passed field is already a SwaggerDict object"
|
||||
title = force_real_str(field.label) if field.label else None
|
||||
title = title if swagger_object_type == openapi.Schema else None # only Schema has title
|
||||
description = force_real_str(field.help_text) if field.help_text else None
|
||||
help_text = getattr(field, 'help_text', None)
|
||||
description = force_real_str(help_text) if help_text else None
|
||||
description = description if swagger_object_type != openapi.Items else None # Items has no description either
|
||||
|
||||
def SwaggerType(existing_object=None, **instance_kwargs):
|
||||
|
|
@ -341,6 +342,9 @@ class ViewInspector(BaseInspector):
|
|||
#: methods that are assumed to require a request body determined by the view's ``serializer_class``
|
||||
implicit_body_methods = ('PUT', 'PATCH', 'POST')
|
||||
|
||||
#: methods which are assumed to return a list of objects when present on non-detail endpoints
|
||||
implicit_list_response_methods = ('GET',)
|
||||
|
||||
# real values set in __init__ to prevent import errors
|
||||
field_inspectors = [] #:
|
||||
filter_inspectors = [] #:
|
||||
|
|
@ -374,20 +378,30 @@ class ViewInspector(BaseInspector):
|
|||
"""
|
||||
raise NotImplementedError("ViewInspector must implement get_operation()!")
|
||||
|
||||
# methods below provided as default implementations for probing inspectors
|
||||
def is_list_view(self):
|
||||
"""Determine whether this view is a list or a detail view. The difference between the two is that
|
||||
detail views depend on a pk/id path parameter. Note that a non-detail view does not necessarily imply a list
|
||||
reponse (:meth:`.has_list_response`), nor are list responses limited to non-detail views.
|
||||
|
||||
For example, one might have a `/topic/<pk>/posts` endpoint which is a detail view that has a list response.
|
||||
|
||||
:rtype: bool"""
|
||||
return is_list_view(self.path, self.method, self.view)
|
||||
|
||||
def has_list_response(self):
|
||||
"""Determine whether this view returns multiple objects. By default this is any non-detail view
|
||||
(see :meth:`.is_list_view`) whose request method is one of :attr:`.implicit_list_response_methods`.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.is_list_view() and (self.method.upper() in self.implicit_list_response_methods)
|
||||
|
||||
def should_filter(self):
|
||||
"""Determine whether filter backend parameters should be included for this request.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
if not getattr(self.view, 'filter_backends', None):
|
||||
return False
|
||||
|
||||
if self.method.lower() not in ["get", "delete"]:
|
||||
return False
|
||||
|
||||
return is_list_view(self.path, self.method, self.view)
|
||||
return getattr(self.view, 'filter_backends', None) and self.has_list_response()
|
||||
|
||||
def get_filter_parameters(self):
|
||||
"""Return the parameters added to the view by its filter backends.
|
||||
|
|
@ -408,13 +422,7 @@ class ViewInspector(BaseInspector):
|
|||
|
||||
:rtype: bool
|
||||
"""
|
||||
if not getattr(self.view, 'paginator', None):
|
||||
return False
|
||||
|
||||
if self.method.lower() != 'get':
|
||||
return False
|
||||
|
||||
return is_list_view(self.path, self.method, self.view)
|
||||
return getattr(self.view, 'paginator', None) and self.has_list_response()
|
||||
|
||||
def get_pagination_parameters(self):
|
||||
"""Return the parameters added to the view by its paginator.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import datetime
|
|||
import inspect
|
||||
import logging
|
||||
import operator
|
||||
import sys
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
|
@ -13,7 +14,9 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
|||
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import decimal_as_float, filter_none, get_serializer_class, get_serializer_ref_name
|
||||
from ..utils import (
|
||||
decimal_as_float, field_value_to_representation, filter_none, get_serializer_class, get_serializer_ref_name
|
||||
)
|
||||
from .base import FieldInspector, NotHandled, SerializerInspector, call_view_method
|
||||
|
||||
try:
|
||||
|
|
@ -72,14 +75,20 @@ class InlineSerializerInspector(SerializerInspector):
|
|||
def get_serializer_ref_name(self, serializer):
|
||||
return get_serializer_ref_name(serializer)
|
||||
|
||||
def _has_ref_name(self, serializer):
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
return hasattr(serializer_meta, 'ref_name')
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
|
||||
if isinstance(field, (serializers.ListSerializer, serializers.ListField)):
|
||||
child_schema = self.probe_field_inspectors(field.child, ChildSwaggerType, use_references)
|
||||
limits = find_limits(field) or {}
|
||||
return SwaggerType(
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=child_schema,
|
||||
**limits
|
||||
)
|
||||
elif isinstance(field, serializers.Serializer):
|
||||
if swagger_object_type != openapi.Schema:
|
||||
|
|
@ -116,6 +125,7 @@ class InlineSerializerInspector(SerializerInspector):
|
|||
# it is better to just remove title from inline models
|
||||
del result.title
|
||||
|
||||
setattr(result, '_NP_serializer', get_serializer_class(serializer))
|
||||
return result
|
||||
|
||||
if not ref_name or not use_references:
|
||||
|
|
@ -125,11 +135,15 @@ class InlineSerializerInspector(SerializerInspector):
|
|||
actual_schema = definitions.setdefault(ref_name, make_schema_definition)
|
||||
actual_schema._remove_read_only()
|
||||
|
||||
actual_serializer = get_serializer_class(getattr(actual_schema, '_serializer', None))
|
||||
actual_serializer = getattr(actual_schema, '_NP_serializer', None)
|
||||
this_serializer = get_serializer_class(field)
|
||||
if actual_serializer and actual_serializer != this_serializer: # pragma: no cover
|
||||
logger.warning("Schema for %s will override distinct serializer %s because they "
|
||||
"share the same ref_name", actual_serializer, this_serializer)
|
||||
explicit_refs = self._has_ref_name(actual_serializer) and self._has_ref_name(this_serializer)
|
||||
if not explicit_refs:
|
||||
raise SwaggerGenerationError(
|
||||
"Schema for %s would override distinct serializer %s because they implicitly share the same "
|
||||
"ref_name; explicitly set the ref_name atribute on both serializers' Meta classes"
|
||||
% (actual_serializer, this_serializer))
|
||||
|
||||
return openapi.SchemaRef(definitions, ref_name)
|
||||
|
||||
|
|
@ -181,7 +195,7 @@ def get_queryset_from_view(view, serializer=None):
|
|||
|
||||
if queryset is not None and serializer is not None:
|
||||
# make sure the view is actually using *this* serializer
|
||||
assert type(serializer) == view.get_serializer_class()
|
||||
assert type(serializer) == call_view_method(view, 'get_serializer_class', 'serializer_class')
|
||||
|
||||
return queryset
|
||||
except Exception: # pragma: no cover
|
||||
|
|
@ -368,8 +382,21 @@ def find_limits(field):
|
|||
def decimal_field_type(field):
|
||||
return openapi.TYPE_NUMBER if decimal_as_float(field) else openapi.TYPE_STRING
|
||||
|
||||
def recurse_one_to_one(field, visited_set=None):
|
||||
if visited_set is None:
|
||||
visited_set = set()
|
||||
if field in visited_set:
|
||||
return None #cycle?
|
||||
if isinstance(field, models.OneToOneField):
|
||||
tgt = field.target_field
|
||||
visited_set.add(field)
|
||||
return recurse_one_to_one(tgt, visited_set=visited_set)
|
||||
else:
|
||||
tmp = get_basic_type_info(field)
|
||||
return tmp['type']
|
||||
|
||||
model_field_to_basic_type = [
|
||||
(models.OneToOneField, (recurse_one_to_one, None)),
|
||||
(models.AutoField, (openapi.TYPE_INTEGER, None)),
|
||||
(models.BinaryField, (openapi.TYPE_STRING, openapi.FORMAT_BINARY)),
|
||||
(models.BooleanField, (openapi.TYPE_BOOLEAN, None)),
|
||||
|
|
@ -455,18 +482,57 @@ def decimal_return_type():
|
|||
return openapi.TYPE_STRING if rest_framework_settings.COERCE_DECIMAL_TO_STRING else openapi.TYPE_NUMBER
|
||||
|
||||
|
||||
raw_type_info = [
|
||||
def get_origin_type(hint_class):
|
||||
return getattr(hint_class, '__origin__', None) or hint_class
|
||||
|
||||
|
||||
def hint_class_issubclass(hint_class, check_class):
|
||||
origin_type = get_origin_type(hint_class)
|
||||
return inspect.isclass(origin_type) and issubclass(origin_type, check_class)
|
||||
|
||||
|
||||
hinting_type_info = [
|
||||
(bool, (openapi.TYPE_BOOLEAN, None)),
|
||||
(int, (openapi.TYPE_INTEGER, None)),
|
||||
(str, (openapi.TYPE_STRING, None)),
|
||||
(float, (openapi.TYPE_NUMBER, None)),
|
||||
(dict, (openapi.TYPE_OBJECT, None)),
|
||||
(Decimal, (decimal_return_type, openapi.FORMAT_DECIMAL)),
|
||||
(uuid.UUID, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
|
||||
(datetime.datetime, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
|
||||
(datetime.date, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
|
||||
# TODO - support typing.List etc
|
||||
]
|
||||
|
||||
hinting_type_info = raw_type_info
|
||||
if sys.version_info < (3, 0):
|
||||
# noinspection PyUnresolvedReferences
|
||||
hinting_type_info.append((unicode, (openapi.TYPE_STRING, None))) # noqa: F821
|
||||
|
||||
if typing:
|
||||
def inspect_collection_hint_class(hint_class):
|
||||
args = hint_class.__args__
|
||||
child_class = args[0] if args else str
|
||||
child_type_info = get_basic_type_info_from_hint(child_class) or {'type': openapi.TYPE_STRING}
|
||||
|
||||
return OrderedDict([
|
||||
('type', openapi.TYPE_ARRAY),
|
||||
('items', openapi.Items(**child_type_info)),
|
||||
])
|
||||
|
||||
hinting_type_info.append(((typing.Sequence, typing.AbstractSet), inspect_collection_hint_class))
|
||||
|
||||
|
||||
def _get_union_types(hint_class):
|
||||
if typing:
|
||||
origin_type = get_origin_type(hint_class)
|
||||
if origin_type is typing.Union:
|
||||
return hint_class.__args__
|
||||
try:
|
||||
# python 3.5.2 and lower compatibility
|
||||
if issubclass(origin_type, typing.Union):
|
||||
return hint_class.__union_params__
|
||||
except TypeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def get_basic_type_info_from_hint(hint_class):
|
||||
|
|
@ -478,27 +544,34 @@ def get_basic_type_info_from_hint(hint_class):
|
|||
:return: the extracted attributes as a dictionary, or ``None`` if the field type is not known
|
||||
:rtype: OrderedDict
|
||||
"""
|
||||
union_types = _get_union_types(hint_class)
|
||||
|
||||
if typing and union_types:
|
||||
# Optional is implemented as Union[T, None]
|
||||
if len(union_types) == 2 and isinstance(None, union_types[1]):
|
||||
result = get_basic_type_info_from_hint(union_types[0])
|
||||
if result:
|
||||
result['x-nullable'] = True
|
||||
|
||||
return result
|
||||
|
||||
for check_class, type_format in hinting_type_info:
|
||||
if issubclass(hint_class, check_class):
|
||||
swagger_type, format = type_format
|
||||
if callable(swagger_type):
|
||||
swagger_type = swagger_type()
|
||||
# if callable(format):
|
||||
# format = format(klass)
|
||||
break
|
||||
else: # pragma: no cover
|
||||
return None
|
||||
|
||||
pattern = None
|
||||
for check_class, info in hinting_type_info:
|
||||
if hint_class_issubclass(hint_class, check_class):
|
||||
if callable(info):
|
||||
return info(hint_class)
|
||||
|
||||
result = OrderedDict([
|
||||
('type', swagger_type),
|
||||
('format', format),
|
||||
('pattern', pattern)
|
||||
])
|
||||
swagger_type, format = info
|
||||
if callable(swagger_type):
|
||||
swagger_type = swagger_type()
|
||||
|
||||
return result
|
||||
return OrderedDict([
|
||||
('type', swagger_type),
|
||||
('format', format),
|
||||
])
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class SerializerMethodFieldInspector(FieldInspector):
|
||||
|
|
@ -585,13 +658,24 @@ class ChoiceFieldInspector(FieldInspector):
|
|||
|
||||
if isinstance(field, serializers.ChoiceField):
|
||||
enum_type = openapi.TYPE_STRING
|
||||
enum_values = list(field.choices.keys())
|
||||
enum_values = []
|
||||
for choice in field.choices.keys():
|
||||
if isinstance(field, serializers.MultipleChoiceField):
|
||||
choice = field_value_to_representation(field, [choice])[0]
|
||||
else:
|
||||
choice = field_value_to_representation(field, choice)
|
||||
|
||||
enum_values.append(choice)
|
||||
|
||||
# for ModelSerializer, try to infer the type from the associated model field
|
||||
serializer = get_parent_serializer(field)
|
||||
if isinstance(serializer, serializers.ModelSerializer):
|
||||
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:
|
||||
model_type = get_basic_type_info(model_field)
|
||||
if model_type:
|
||||
|
|
@ -676,11 +760,23 @@ class HiddenFieldInspector(FieldInspector):
|
|||
return NotHandled
|
||||
|
||||
|
||||
class JSONFieldInspector(FieldInspector):
|
||||
"""Provides conversion for ``JSONField``."""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
|
||||
if isinstance(field, serializers.JSONField) and swagger_object_type == openapi.Schema:
|
||||
return SwaggerType(type=openapi.TYPE_OBJECT)
|
||||
|
||||
return NotHandled
|
||||
|
||||
|
||||
class StringDefaultFieldInspector(FieldInspector):
|
||||
"""For otherwise unhandled fields, return them as plain :data:`.TYPE_STRING` objects."""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): # pragma: no cover
|
||||
# TODO unhandled fields: TimeField JSONField
|
||||
# TODO unhandled fields: TimeField
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
return SwaggerType(type=openapi.TYPE_STRING)
|
||||
|
||||
|
|
@ -755,10 +851,19 @@ else:
|
|||
if isinstance(field, RecursiveField) and swagger_object_type == openapi.Schema:
|
||||
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
||||
|
||||
ref_name = get_serializer_ref_name(field.proxied)
|
||||
assert ref_name is not None, "Can't create RecursiveField schema for inline " + str(type(field.proxied))
|
||||
proxied = field.proxied
|
||||
if isinstance(field.proxied, serializers.ListSerializer):
|
||||
proxied = proxied.child
|
||||
|
||||
ref_name = get_serializer_ref_name(proxied)
|
||||
assert ref_name is not None, "Can't create RecursiveField schema for inline " + str(type(proxied))
|
||||
|
||||
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
||||
return openapi.SchemaRef(definitions, ref_name, ignore_unresolved=True)
|
||||
|
||||
ref = openapi.SchemaRef(definitions, ref_name, ignore_unresolved=True)
|
||||
if isinstance(field.proxied, serializers.ListSerializer):
|
||||
ref = openapi.Items(type=openapi.TYPE_ARRAY, items=ref)
|
||||
|
||||
return ref
|
||||
|
||||
return NotHandled
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import logging
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
from rest_framework.request import is_form_media_type
|
||||
|
|
@ -10,7 +9,7 @@ from .. import openapi
|
|||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import (
|
||||
filter_none, force_real_str, force_serializer_instance, get_consumes, get_produces, guess_response_status,
|
||||
is_list_view, merge_params, no_body, param_list_to_odict
|
||||
merge_params, no_body, param_list_to_odict
|
||||
)
|
||||
from .base import ViewInspector, call_view_method
|
||||
|
||||
|
|
@ -18,28 +17,15 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class SwaggerAutoSchema(ViewInspector):
|
||||
def __init__(self, view, path, method, components, request, overrides):
|
||||
def __init__(self, view, path, method, components, request, overrides, operation_keys=None):
|
||||
super(SwaggerAutoSchema, self).__init__(view, path, method, components, request, overrides)
|
||||
self._sch = AutoSchema()
|
||||
self._sch.view = view
|
||||
self.operation_keys = operation_keys
|
||||
|
||||
def _summary_and_description_compat(self):
|
||||
# TODO: remove in 1.13
|
||||
base_methods = (SwaggerAutoSchema.get_summary, SwaggerAutoSchema.get_description)
|
||||
self_methods = (type(self).get_summary, type(self).get_description)
|
||||
if self_methods != base_methods:
|
||||
warnings.warn(
|
||||
"`SwaggerAutoSchema` methods `get_summary` and `get_description` are deprecated and "
|
||||
"will be removed in drf-yasg 1.13. Override `get_summary_and_description` instead.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
# if get_summary or get_description are overriden by a child class,
|
||||
# we must call them for backwards compatibility
|
||||
return self.get_summary(), self.get_description()
|
||||
def get_operation(self, operation_keys=None):
|
||||
operation_keys = operation_keys or self.operation_keys
|
||||
|
||||
return self.get_summary_and_description()
|
||||
|
||||
def get_operation(self, operation_keys):
|
||||
consumes = self.get_consumes()
|
||||
produces = self.get_produces()
|
||||
|
||||
|
|
@ -50,7 +36,7 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
parameters = self.add_manual_parameters(parameters)
|
||||
|
||||
operation_id = self.get_operation_id(operation_keys)
|
||||
summary, description = self._summary_and_description_compat()
|
||||
summary, description = self.get_summary_and_description()
|
||||
security = self.get_security()
|
||||
assert security is None or isinstance(security, list), "security must be a list of security requirement objects"
|
||||
deprecated = self.is_deprecated()
|
||||
|
|
@ -225,7 +211,7 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
default_schema = self.serializer_to_schema(default_schema) or ''
|
||||
|
||||
if default_schema:
|
||||
if is_list_view(self.path, self.method, self.view) and self.method.lower() == 'get':
|
||||
if self.has_list_response():
|
||||
default_schema = openapi.Schema(type=openapi.TYPE_ARRAY, items=default_schema)
|
||||
if self.should_page():
|
||||
default_schema = self.get_paginated_response(default_schema) or default_schema
|
||||
|
|
@ -317,7 +303,7 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
|
||||
return natural_parameters + serializer_parameters
|
||||
|
||||
def get_operation_id(self, operation_keys):
|
||||
def get_operation_id(self, operation_keys=None):
|
||||
"""Return an unique ID for this operation. The ID must be unique across
|
||||
all :class:`.Operation` objects in the API.
|
||||
|
||||
|
|
@ -325,6 +311,8 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
of this view in the API; e.g. ``('snippets', 'list')``, ``('snippets', 'retrieve')``, etc.
|
||||
:rtype: str
|
||||
"""
|
||||
operation_keys = operation_keys or self.operation_keys
|
||||
|
||||
operation_id = self.overrides.get('operation_id', '')
|
||||
if not operation_id:
|
||||
operation_id = '_'.join(operation_keys)
|
||||
|
|
@ -363,27 +351,11 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
description = description.strip().replace('\r', '')
|
||||
|
||||
if description and (summary is None):
|
||||
# description from docstring ... do summary magic
|
||||
# description from docstring... do summary magic
|
||||
summary, description = self.split_summary_from_description(description)
|
||||
|
||||
return summary, description
|
||||
|
||||
def get_summary(self):
|
||||
"""Return a summary description for this operation.
|
||||
|
||||
:return: the summary
|
||||
:rtype: str
|
||||
"""
|
||||
return self.get_summary_and_description()[0]
|
||||
|
||||
def get_description(self):
|
||||
"""Return an operation description determined as appropriate from the view's method and class docstrings.
|
||||
|
||||
:return: the operation description
|
||||
:rtype: str
|
||||
"""
|
||||
return self.get_summary_and_description()[1]
|
||||
|
||||
def get_security(self):
|
||||
"""Return a list of security requirements for this operation.
|
||||
|
||||
|
|
@ -402,7 +374,7 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
"""
|
||||
return self.overrides.get('deprecated', None)
|
||||
|
||||
def get_tags(self, operation_keys):
|
||||
def get_tags(self, operation_keys=None):
|
||||
"""Get a list of tags for this operation. Tags determine how operations relate with each other, and in the UI
|
||||
each tag will show as a group containing the operations that use it. If not provided in overrides,
|
||||
tags will be inferred from the operation url.
|
||||
|
|
@ -411,6 +383,8 @@ class SwaggerAutoSchema(ViewInspector):
|
|||
of this view in the API; e.g. ``('snippets', 'list')``, ``('snippets', 'retrieve')``, etc.
|
||||
:rtype: list[str]
|
||||
"""
|
||||
operation_keys = operation_keys or self.operation_keys
|
||||
|
||||
tags = self.overrides.get('tags')
|
||||
if not tags:
|
||||
tags = [operation_keys[0]]
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class Command(BaseCommand):
|
|||
if user:
|
||||
# Only call get_user_model if --user was passed in order to
|
||||
# avoid crashing if auth is not configured in the project
|
||||
user = get_user_model().objects.get(username=user)
|
||||
user = get_user_model().objects.get(**{get_user_model().USERNAME_FIELD: user})
|
||||
|
||||
mock = mock or private or (user is not None) or (api_version is not None)
|
||||
if mock and not api_url:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from django.urls import get_script_prefix
|
|||
from django.utils.functional import Promise
|
||||
from inflection import camelize
|
||||
|
||||
from .utils import filter_none, force_real_str
|
||||
from .utils import dict_has_ordered_keys, filter_none, force_real_str
|
||||
|
||||
try:
|
||||
from collections import abc as collections_abc
|
||||
|
|
@ -145,7 +145,7 @@ class SwaggerDict(OrderedDict):
|
|||
result = OrderedDict()
|
||||
memo[id(obj)] = result
|
||||
items = obj.items()
|
||||
if not isinstance(obj, OrderedDict):
|
||||
if not dict_has_ordered_keys(obj):
|
||||
items = sorted(items)
|
||||
for attr, val in items:
|
||||
result[attr] = SwaggerDict._as_odict(val, memo)
|
||||
|
|
@ -167,7 +167,8 @@ class SwaggerDict(OrderedDict):
|
|||
def __reduce__(self):
|
||||
# for pickle supprt; this skips calls to all SwaggerDict __init__ methods and relies
|
||||
# on the already set attributes instead
|
||||
return _bare_SwaggerDict, (type(self),), vars(self), None, iter(self.items())
|
||||
attrs = {k: v for k, v in vars(self).items() if not k.startswith('_NP_')}
|
||||
return _bare_SwaggerDict, (type(self),), attrs, None, iter(self.items())
|
||||
|
||||
|
||||
class Contact(SwaggerDict):
|
||||
|
|
@ -400,7 +401,7 @@ class Items(SwaggerDict):
|
|||
self.format = format
|
||||
self.enum = enum
|
||||
self.pattern = pattern
|
||||
self.items = items
|
||||
self.items_ = items
|
||||
self._insert_extras__()
|
||||
_check_type(type, format, enum, pattern, items, self.__class__)
|
||||
|
||||
|
|
@ -434,7 +435,7 @@ class Parameter(SwaggerDict):
|
|||
self.format = format
|
||||
self.enum = enum
|
||||
self.pattern = pattern
|
||||
self.items = items
|
||||
self.items_ = items
|
||||
self.default = default
|
||||
self._insert_extras__()
|
||||
if (not schema and not type) or (schema and type):
|
||||
|
|
@ -469,7 +470,7 @@ class Schema(SwaggerDict):
|
|||
:type properties: dict[str,Schema or SchemaRef]
|
||||
:param additional_properties: allow wildcard properties not listed in `properties`
|
||||
:type additional_properties: bool or Schema or SchemaRef
|
||||
:param list[str] required: list of requried property names
|
||||
:param list[str] required: list of required property names
|
||||
:param items: type of array items, only valid if `type` is ``array``
|
||||
:type items: Schema or SchemaRef
|
||||
:param default: only valid when insider another ``Schema``\\ 's ``properties``;
|
||||
|
|
@ -492,7 +493,7 @@ class Schema(SwaggerDict):
|
|||
self.format = format
|
||||
self.enum = enum
|
||||
self.pattern = pattern
|
||||
self.items = items
|
||||
self.items_ = items
|
||||
self.read_only = read_only
|
||||
self.default = default
|
||||
self._insert_extras__()
|
||||
|
|
@ -617,16 +618,26 @@ class ReferenceResolver(object):
|
|||
::
|
||||
|
||||
> components = ReferenceResolver('definitions', 'parameters')
|
||||
> definitions = ReferenceResolver.with_scope('definitions')
|
||||
> definitions = components.with_scope('definitions')
|
||||
> definitions.set('Article', Schema(...))
|
||||
> print(components)
|
||||
{'definitions': OrderedDict([('Article', Schema(...)]), 'parameters': OrderedDict()}
|
||||
"""
|
||||
|
||||
def __init__(self, *scopes):
|
||||
def __init__(self, *scopes, **kwargs):
|
||||
"""
|
||||
:param str scopes: an enumeration of the valid scopes this resolver will contain
|
||||
"""
|
||||
force_init = kwargs.pop('force_init', False)
|
||||
if not force_init:
|
||||
raise AssertionError(
|
||||
"Creating an instance of ReferenceResolver almost certainly won't do what you want it to do.\n"
|
||||
"See https://github.com/axnsan12/drf-yasg/issues/211, "
|
||||
"https://github.com/axnsan12/drf-yasg/issues/271, "
|
||||
"https://github.com/axnsan12/drf-yasg/issues/325.\n"
|
||||
"Pass `force_init=True` to override this."
|
||||
)
|
||||
|
||||
self._objects = OrderedDict()
|
||||
self._force_scope = None
|
||||
for scope in scopes:
|
||||
|
|
@ -641,7 +652,7 @@ class ReferenceResolver(object):
|
|||
:rtype: .ReferenceResolver
|
||||
"""
|
||||
assert scope in self.scopes, "unknown scope %s" % scope
|
||||
ret = ReferenceResolver()
|
||||
ret = ReferenceResolver(force_init=True)
|
||||
ret._objects = self._objects
|
||||
ret._force_scope = scope
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import six
|
|||
|
||||
from django.shortcuts import resolve_url
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.functional import Promise
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
|
||||
from rest_framework.utils import encoders, json
|
||||
|
|
@ -124,7 +124,7 @@ class SwaggerUIRenderer(_UIRenderer):
|
|||
swagger_ui_settings = self.get_swagger_ui_settings()
|
||||
|
||||
request = renderer_context.get('request', None)
|
||||
oauth_redirect_url = force_text(swagger_ui_settings.get('oauth2RedirectUrl', ''))
|
||||
oauth_redirect_url = force_str(swagger_ui_settings.get('oauth2RedirectUrl', ''))
|
||||
if request and oauth_redirect_url:
|
||||
swagger_ui_settings['oauth2RedirectUrl'] = request.build_absolute_uri(oauth_redirect_url)
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 628 B |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,11 +1,15 @@
|
|||
import inspect
|
||||
import logging
|
||||
import sys
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.parsers import FileUploadParser
|
||||
from rest_framework.request import is_form_media_type
|
||||
from rest_framework.settings import api_settings as rest_framework_settings
|
||||
from rest_framework.utils import encoders, json
|
||||
|
|
@ -92,8 +96,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
|||
* a ``Serializer`` class or instance will be converted into a :class:`.Schema` and treated as above
|
||||
* a :class:`.Response` object will be used as-is; however if its ``schema`` attribute is a ``Serializer``,
|
||||
it will automatically be converted into a :class:`.Schema`
|
||||
:type responses: dict[str,(drf_yasg.openapi.Schema or drf_yasg.openapi.SchemaRef or drf_yasg.openapi.Response or
|
||||
str or rest_framework.serializers.Serializer)]
|
||||
:type responses: dict[int or str, (drf_yasg.openapi.Schema or drf_yasg.openapi.SchemaRef or
|
||||
drf_yasg.openapi.Response or str or rest_framework.serializers.Serializer)]
|
||||
|
||||
:param list[type[drf_yasg.inspectors.FieldInspector]] field_inspectors: extra serializer and field inspectors; these
|
||||
will be tried before :attr:`.ViewInspector.field_inspectors` on the :class:`.inspectors.SwaggerAutoSchema`
|
||||
|
|
@ -163,7 +167,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
|||
|
||||
if len(available_http_methods) > 1:
|
||||
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"
|
||||
else:
|
||||
# for a single-method view we assume that single method as the decorator target
|
||||
|
|
@ -176,8 +180,8 @@ 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 an action, detail_route or " \
|
||||
"list_route; you should also ensure that you put the swagger_auto_schema decorator " \
|
||||
"the methods argument should only be specified when decorating an action; " \
|
||||
"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
|
||||
|
|
@ -212,7 +216,7 @@ def is_list_view(path, method, view):
|
|||
:param APIView view: target view
|
||||
: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', '')
|
||||
method = getattr(view, action, None) or method
|
||||
detail = getattr(method, 'detail', None)
|
||||
|
|
@ -224,6 +228,9 @@ def is_list_view(path, method, view):
|
|||
# a detail action is surely not a list route
|
||||
return False
|
||||
|
||||
if isinstance(view, ListModelMixin):
|
||||
return True
|
||||
|
||||
# for GenericAPIView, if it's a detail view it can't also be a list view
|
||||
if isinstance(view, (RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin)):
|
||||
return False
|
||||
|
|
@ -365,12 +372,19 @@ def get_consumes(parser_classes):
|
|||
:rtype: list[str]
|
||||
"""
|
||||
parser_classes = get_object_classes(parser_classes)
|
||||
parser_classes = [pc for pc in parser_classes if not issubclass(pc, FileUploadParser)]
|
||||
media_types = [parser.media_type for parser in parser_classes or []]
|
||||
non_form_media_types = [encoding for encoding in media_types if not is_form_media_type(encoding)]
|
||||
# Because swagger Parameter objects don't support complex data types (nested objects, arrays),
|
||||
# we can't use those unless we are sure the view *only* accepts form data
|
||||
# This means that a view won't support file upload in swagger unless it explicitly
|
||||
# sets its parser classes to include only form parsers
|
||||
if len(non_form_media_types) == 0:
|
||||
return media_types
|
||||
else:
|
||||
return non_form_media_types
|
||||
|
||||
# If the form accepts both form data and another type, like json (which is the default config),
|
||||
# we will render its input as a Schema and thus it file parameters will be read-only
|
||||
return non_form_media_types
|
||||
|
||||
|
||||
def get_produces(renderer_classes):
|
||||
|
|
@ -389,8 +403,7 @@ def get_produces(renderer_classes):
|
|||
|
||||
|
||||
def decimal_as_float(field):
|
||||
"""
|
||||
Returns true if ``field`` is a django-rest-framework DecimalField and its ``coerce_to_string`` attribute or the
|
||||
"""Returns true if ``field`` is a django-rest-framework DecimalField and its ``coerce_to_string`` attribute or the
|
||||
``COERCE_DECIMAL_TO_STRING`` setting is set to ``False``.
|
||||
|
||||
:rtype: bool
|
||||
|
|
@ -401,8 +414,7 @@ def decimal_as_float(field):
|
|||
|
||||
|
||||
def get_serializer_ref_name(serializer):
|
||||
"""
|
||||
Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer')
|
||||
"""Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer')
|
||||
|
||||
:param serializer: Serializer instance
|
||||
:return: Serializer's ``ref_name`` or ``None`` for inline serializer
|
||||
|
|
@ -429,13 +441,35 @@ def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|||
Fix for https://github.com/axnsan12/drf-yasg/issues/159
|
||||
"""
|
||||
if s is not None:
|
||||
s = force_text(s, encoding, strings_only, errors)
|
||||
s = force_str(s, encoding, strings_only, errors)
|
||||
if type(s) != str:
|
||||
s = '' + s
|
||||
|
||||
# Remove common indentation to get the correct Markdown rendering
|
||||
s = textwrap.dedent(s)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def field_value_to_representation(field, value):
|
||||
"""Convert a python value related to a field (default, choices, etc.) into its OpenAPI-compatible representation.
|
||||
|
||||
:param serializers.Field field: field associated with the value
|
||||
:param object value: value
|
||||
:return: the converted value
|
||||
"""
|
||||
value = field.to_representation(value)
|
||||
if isinstance(value, Decimal):
|
||||
if decimal_as_float(field):
|
||||
value = float(value)
|
||||
else:
|
||||
value = str(value)
|
||||
|
||||
# JSON roundtrip ensures that the value is valid JSON;
|
||||
# for example, sets and tuples get transformed into lists
|
||||
return json.loads(json.dumps(value, cls=encoders.JSONEncoder))
|
||||
|
||||
|
||||
def get_field_default(field):
|
||||
"""
|
||||
Get the default value for a field, converted to a JSON-compatible value while properly handling callables.
|
||||
|
|
@ -449,7 +483,10 @@ def get_field_default(field):
|
|||
try:
|
||||
if hasattr(default, 'set_context'):
|
||||
default.set_context(field)
|
||||
default = default()
|
||||
if getattr(default, 'requires_context', False):
|
||||
default = default(field)
|
||||
else:
|
||||
default = default()
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("default for %s is callable but it raised an exception when "
|
||||
"called; 'default' will not be set on schema", field, exc_info=True)
|
||||
|
|
@ -457,15 +494,23 @@ def get_field_default(field):
|
|||
|
||||
if default is not serializers.empty and default is not None:
|
||||
try:
|
||||
default = field.to_representation(default)
|
||||
# JSON roundtrip ensures that the value is valid JSON;
|
||||
# for example, sets and tuples get transformed into lists
|
||||
default = json.loads(json.dumps(default, cls=encoders.JSONEncoder))
|
||||
if decimal_as_float(field):
|
||||
default = float(default)
|
||||
default = field_value_to_representation(field, default)
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("'default' on schema for %s will not be set because "
|
||||
"to_representation raised an exception", field, exc_info=True)
|
||||
default = serializers.empty
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def dict_has_ordered_keys(obj):
|
||||
"""Check if a given object is a dict that maintains insertion order.
|
||||
|
||||
:param obj: the dict object to check
|
||||
:rtype: bool
|
||||
"""
|
||||
if sys.version_info >= (3, 7):
|
||||
# the Python 3.7 language spec says that dict must maintain insertion order.
|
||||
return isinstance(obj, dict)
|
||||
|
||||
return isinstance(obj, OrderedDict)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import warnings
|
||||
from functools import wraps
|
||||
from functools import WRAPPER_ASSIGNMENTS, wraps
|
||||
|
||||
from django.utils.cache import add_never_cache_headers
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
from rest_framework import exceptions
|
||||
|
|
@ -30,7 +29,7 @@ def deferred_never_cache(view_func):
|
|||
never be cached.
|
||||
"""
|
||||
|
||||
@wraps(view_func, assigned=available_attrs(view_func))
|
||||
@wraps(view_func, assigned=WRAPPER_ASSIGNMENTS)
|
||||
def _wrapped_view_func(request, *args, **kwargs):
|
||||
response = view_func(request, *args, **kwargs)
|
||||
|
||||
|
|
@ -57,7 +56,7 @@ def get_schema_view(info=None, url=None, patterns=None, urlconf=None, public=Fal
|
|||
:param patterns: same as :class:`.OpenAPISchemaGenerator`
|
||||
:param urlconf: same as :class:`.OpenAPISchemaGenerator`
|
||||
:param bool public: if False, includes only the endpoints that are accesible by the user viewing the schema
|
||||
:param list validators: a list of validator names to apply; allowed values are ``flex``, ``ssv``
|
||||
:param list validators: a list of validator names to apply; the only allowed value is ``ssv``, for now
|
||||
:param type generator_class: schema generator class to use; should be a subclass of :class:`.OpenAPISchemaGenerator`
|
||||
:param tuple authentication_classes: authentication classes for the schema view itself
|
||||
:param tuple permission_classes: permission classes for the schema view itself
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.1.5 on 2019-03-02 03:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('articles', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='read_only_nullable',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -20,6 +20,7 @@ class Article(models.Model):
|
|||
on_delete=models.PROTECT)
|
||||
original_group = models.ForeignKey('ArticleGroup', related_name='articles_as_original', blank=True, default=None,
|
||||
on_delete=models.PROTECT)
|
||||
read_only_nullable = models.CharField(max_length=20, null=True, blank=True)
|
||||
|
||||
|
||||
class ArticleGroup(models.Model):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from articles.models import Article, ArticleGroup
|
||||
|
|
@ -17,10 +17,9 @@ class ArticleSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
|
||||
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified', 'read_only_nullable',
|
||||
'references', 'uuid', 'cover', 'cover_name', 'article_type', 'group', 'original_group', )
|
||||
read_only_fields = ('date_created', 'date_modified',
|
||||
'references', 'uuid', 'cover_name')
|
||||
read_only_fields = ('date_created', 'date_modified', 'references', 'uuid', 'cover_name', 'read_only_nullable')
|
||||
lookup_field = 'slug'
|
||||
extra_kwargs = {
|
||||
'body': {'help_text': 'body serializer help_text'},
|
||||
|
|
@ -29,6 +28,7 @@ class ArticleSerializer(serializers.ModelSerializer):
|
|||
'help_text': _("The ID of the user that created this article; if none is provided, "
|
||||
"defaults to the currently logged in user.")
|
||||
},
|
||||
'read_only_nullable': {'allow_null': True},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import datetime
|
||||
import functools
|
||||
|
||||
from django.utils.decorators import method_decorator
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import viewsets
|
||||
# noinspection PyDeprecation
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.parsers import FileUploadParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
|
||||
from articles import serializers
|
||||
|
|
@ -93,17 +91,10 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
|||
|
||||
swagger_schema = NoTitleAutoSchema
|
||||
|
||||
try:
|
||||
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
|
||||
from rest_framework.decorators import action
|
||||
|
||||
@swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
|
||||
@list_route(methods=['get'])
|
||||
@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)
|
||||
|
|
@ -118,7 +109,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
|||
type=openapi.TYPE_INTEGER,
|
||||
description="this should not crash (form parameter on DELETE method)"
|
||||
)])
|
||||
@detail_route(methods=['get', 'post', 'delete'], parser_classes=(MultiPartParser,))
|
||||
@action(detail=True, methods=['get', 'post', 'delete'], parser_classes=(MultiPartParser, FileUploadParser))
|
||||
def image(self, request, slug=None):
|
||||
"""
|
||||
image method docstring
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
from rest_framework import viewsets
|
||||
from rest_framework.pagination import BasePagination
|
||||
|
||||
from .models import Identity, Person
|
||||
from .serializers import IdentitySerializer, PersonSerializer
|
||||
|
||||
|
||||
class UnknownPagination(BasePagination):
|
||||
paginator_query_args = ['unknown_paginator']
|
||||
|
||||
|
||||
class PersonViewSet(viewsets.ModelViewSet):
|
||||
model = Person
|
||||
queryset = Person.objects
|
||||
serializer_class = PersonSerializer
|
||||
pagination_class = UnknownPagination
|
||||
|
||||
|
||||
class IdentityViewSet(viewsets.ModelViewSet):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.1.7 on 2019-03-16 14:06
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('snippets', '0002_auto_20181219_1016'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SnippetViewer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('snippet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewers', to='snippets.Snippet')),
|
||||
('viewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snippet_views', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.2 on 2019-06-12 22:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('snippets', '0003_snippetviewer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='snippet',
|
||||
name='language',
|
||||
field=models.CharField(choices=[('cpp', 'cpp'), ('js', 'js'), ('python', 'python')], default='python', max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='snippet',
|
||||
name='style',
|
||||
field=models.CharField(choices=[('monokai', 'monokai'), ('solarized-dark', 'solarized-dark'), ('vim', 'vim')], default='solarized-dark', max_length=100),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
from django.db import models
|
||||
from pygments.lexers import get_all_lexers
|
||||
from pygments.styles import get_all_styles
|
||||
|
||||
LEXERS = [item for item in get_all_lexers() if item[1]]
|
||||
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
|
||||
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
|
||||
LANGUAGE_CHOICES = sorted((item, item) for item in ('cpp', 'python', 'js'))
|
||||
STYLE_CHOICES = sorted((item, item) for item in ('solarized-dark', 'monokai', 'vim'))
|
||||
|
||||
|
||||
class Snippet(models.Model):
|
||||
|
|
@ -14,7 +11,12 @@ class Snippet(models.Model):
|
|||
code = models.TextField(help_text="code model help text")
|
||||
linenos = models.BooleanField(default=False)
|
||||
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
|
||||
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
|
||||
style = models.CharField(choices=STYLE_CHOICES, default='solarized-dark', max_length=100)
|
||||
|
||||
class Meta:
|
||||
ordering = ('created',)
|
||||
|
||||
|
||||
class SnippetViewer(models.Model):
|
||||
snippet = models.ForeignKey(Snippet, on_delete=models.CASCADE, related_name='viewers')
|
||||
viewer = models.ForeignKey('auth.User', related_name='snippet_views', on_delete=models.CASCADE)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
from decimal import Decimal
|
||||
|
||||
import rest_framework
|
||||
from django.contrib.auth import get_user_model
|
||||
from packaging.version import Version
|
||||
from rest_framework import serializers
|
||||
from rest_framework.compat import MinValueValidator
|
||||
|
||||
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet
|
||||
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer
|
||||
|
||||
if Version(rest_framework.__version__) < Version('3.10'):
|
||||
from rest_framework.compat import MaxLengthValidator, MinValueValidator
|
||||
else:
|
||||
from django.core.validators import MaxLengthValidator, MinValueValidator
|
||||
|
||||
|
||||
class LanguageSerializer(serializers.Serializer):
|
||||
name = serializers.ChoiceField(
|
||||
choices=LANGUAGE_CHOICES, default='python', help_text='The name of the programming language')
|
||||
read_only_nullable = serializers.CharField(read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
ref_name = None
|
||||
|
||||
|
||||
class ExampleProjectSerializer(serializers.Serializer):
|
||||
project_name = serializers.CharField(help_text='Name of the project')
|
||||
project_name = serializers.CharField(label='project name custom title', help_text='Name of the project')
|
||||
github_repo = serializers.CharField(required=True, help_text='Github repository of the project')
|
||||
|
||||
class Meta:
|
||||
|
|
@ -65,11 +72,13 @@ class SnippetSerializer(serializers.Serializer):
|
|||
)
|
||||
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
||||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
||||
tags = serializers.ListField(child=serializers.CharField(min_length=2), min_length=3, max_length=15)
|
||||
linenos = serializers.BooleanField(required=False)
|
||||
language = LanguageSerializer(help_text="Sample help text for language")
|
||||
styles = serializers.MultipleChoiceField(choices=STYLE_CHOICES, default=['friendly'])
|
||||
styles = serializers.MultipleChoiceField(choices=STYLE_CHOICES, default=['solarized-dark'])
|
||||
lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False)
|
||||
example_projects = serializers.ListSerializer(child=ExampleProjectSerializer(), read_only=True)
|
||||
example_projects = serializers.ListSerializer(child=ExampleProjectSerializer(), read_only=True,
|
||||
validators=[MaxLengthValidator(100)])
|
||||
difficulty_factor = serializers.FloatField(help_text="this is here just to test FloatField",
|
||||
read_only=True, default=lambda: 6.9)
|
||||
rate_as_string = serializers.DecimalField(max_digits=6, decimal_places=3, default=Decimal('0.0'),
|
||||
|
|
@ -97,3 +106,9 @@ class SnippetSerializer(serializers.Serializer):
|
|||
instance.style = validated_data.get('style', instance.style)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class SnippetViewerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SnippetViewer
|
||||
fields = '__all__'
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ if django.VERSION[:2] >= (2, 0):
|
|||
urlpatterns = [
|
||||
path('', views.SnippetList.as_view()),
|
||||
path('<int:pk>/', views.SnippetDetail.as_view()),
|
||||
path('views/<int:snippet_pk>/', views.SnippetViewerList.as_view()),
|
||||
]
|
||||
else:
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url('^$', views.SnippetList.as_view()),
|
||||
url(r'^(?P<pk>\d+)/$', views.SnippetDetail.as_view()),
|
||||
url(r'^views/(?P<snippet_pk>\d+)/$', views.SnippetViewerList.as_view()),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ from djangorestframework_camel_case.parser import CamelCaseJSONParser
|
|||
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
|
||||
from inflection import camelize
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.parsers import FormParser
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.parsers import FileUploadParser, FormParser
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.inspectors import SwaggerAutoSchema
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
from snippets.models import Snippet, SnippetViewer
|
||||
from snippets.serializers import SnippetSerializer, SnippetViewerSerializer
|
||||
|
||||
|
||||
class CamelCaseOperationIDAutoSchema(SwaggerAutoSchema):
|
||||
|
|
@ -22,7 +24,7 @@ class SnippetList(generics.ListCreateAPIView):
|
|||
queryset = Snippet.objects.all()
|
||||
serializer_class = SnippetSerializer
|
||||
|
||||
parser_classes = (FormParser, CamelCaseJSONParser,)
|
||||
parser_classes = (FormParser, CamelCaseJSONParser, FileUploadParser)
|
||||
renderer_classes = (CamelCaseJSONRenderer,)
|
||||
swagger_schema = CamelCaseOperationIDAutoSchema
|
||||
|
||||
|
|
@ -93,3 +95,31 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||
def delete(self, request, *args, **kwargs):
|
||||
"""delete method docstring"""
|
||||
return super(SnippetDetail, self).patch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class SnippetViewerList(generics.ListAPIView):
|
||||
"""SnippetViewerList classdoc"""
|
||||
serializer_class = SnippetViewerSerializer
|
||||
pagination_class = PageNumberPagination
|
||||
|
||||
parser_classes = (FormParser, CamelCaseJSONParser, FileUploadParser)
|
||||
renderer_classes = (CamelCaseJSONRenderer,)
|
||||
swagger_schema = CamelCaseOperationIDAutoSchema
|
||||
lookup_url_kwarg = 'snippet_pk'
|
||||
|
||||
def get_object(self):
|
||||
queryset = Snippet.objects.all()
|
||||
|
||||
# Perform the lookup filtering.
|
||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||
|
||||
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
|
||||
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, obj)
|
||||
|
||||
return obj
|
||||
|
||||
def get_queryset(self):
|
||||
return SnippetViewer.objects.filter(snippet=self.get_object())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -116,31 +116,36 @@ SWAGGER_SETTINGS = {
|
|||
'type': 'basic'
|
||||
},
|
||||
'Bearer': {
|
||||
'type': 'apiKey',
|
||||
'in': 'header',
|
||||
'name': 'Authorization',
|
||||
'in': 'header'
|
||||
},
|
||||
'Query': {
|
||||
'type': 'apiKey',
|
||||
'name': 'auth',
|
||||
'in': 'query'
|
||||
},
|
||||
'OAuth2 password': {
|
||||
'type': 'oauth2',
|
||||
'flow': 'password',
|
||||
'tokenUrl': OAUTH2_TOKEN_URL,
|
||||
'scopes': {
|
||||
'read': 'Read everything.',
|
||||
'write': 'Write everything,',
|
||||
}
|
||||
}
|
||||
},
|
||||
'tokenUrl': OAUTH2_TOKEN_URL,
|
||||
'type': 'oauth2',
|
||||
},
|
||||
'Query': {
|
||||
'in': 'query',
|
||||
'name': 'auth',
|
||||
'type': 'apiKey',
|
||||
},
|
||||
},
|
||||
'OAUTH2_REDIRECT_URL': OAUTH2_REDIRECT_URL,
|
||||
'OAUTH2_CONFIG': {
|
||||
'clientId': OAUTH2_CLIENT_ID,
|
||||
'clientSecret': OAUTH2_CLIENT_SECRET,
|
||||
'appName': OAUTH2_APP_NAME,
|
||||
}
|
||||
},
|
||||
"DEFAULT_PAGINATOR_INSPECTORS": [
|
||||
'testproj.inspectors.UnknownPaginatorInspector',
|
||||
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
]
|
||||
}
|
||||
|
||||
REDOC_SETTINGS = {
|
||||
|
|
@ -188,16 +193,6 @@ LOGGING = {
|
|||
'propagate': False,
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['console_log'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.db.backends': {
|
||||
'handlers': ['console_log'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.template': {
|
||||
'handlers': ['console_log'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.1.5 on 2019-04-01 00:28
|
||||
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('todo', '0002_todotree'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Pack',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('size_code', models.DecimalField(choices=[(Decimal('50'), '5x10'), (Decimal('100'), '10x10'), (Decimal('200'), '10x20')], decimal_places=3, default=Decimal('200'), max_digits=7)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
|
@ -18,3 +20,19 @@ class TodoYetAnother(models.Model):
|
|||
class TodoTree(models.Model):
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, related_name='children', null=True)
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
|
||||
class Pack(models.Model):
|
||||
SIZE_10x20 = Decimal(200.000)
|
||||
SIZE_10x10 = Decimal(100.000)
|
||||
SIZE_5x10 = Decimal(50.000)
|
||||
|
||||
size_code_choices = (
|
||||
(SIZE_5x10, '5x10'),
|
||||
(SIZE_10x10, '10x10'),
|
||||
(SIZE_10x20, '10x20'),
|
||||
)
|
||||
size_code = models.DecimalField(max_digits=7,
|
||||
decimal_places=3,
|
||||
choices=size_code_choices,
|
||||
default=SIZE_10x20)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from django.utils import timezone
|
|||
from rest_framework import serializers
|
||||
from rest_framework_recursive.fields import RecursiveField
|
||||
|
||||
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||
from .models import Pack, Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||
|
||||
|
||||
class TodoSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -41,10 +41,11 @@ class TodoYetAnotherSerializer(serializers.ModelSerializer):
|
|||
|
||||
class TodoTreeSerializer(serializers.ModelSerializer):
|
||||
children = serializers.ListField(child=RecursiveField(), source='children.all')
|
||||
many_children = RecursiveField(many=True, source='children')
|
||||
|
||||
class Meta:
|
||||
model = TodoTree
|
||||
fields = ('id', 'title', 'children')
|
||||
fields = ('id', 'title', 'children', 'many_children')
|
||||
|
||||
|
||||
class TodoRecursiveSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -56,3 +57,14 @@ class TodoRecursiveSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = TodoTree
|
||||
fields = ('id', 'title', 'parent', 'parent_id')
|
||||
|
||||
|
||||
class HarvestSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Pack
|
||||
fields = (
|
||||
'size_code',
|
||||
)
|
||||
read_only_fields = (
|
||||
'size_code',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ router.register(r'another', views.TodoAnotherViewSet)
|
|||
router.register(r'yetanother', views.TodoYetAnotherViewSet)
|
||||
router.register(r'tree', views.TodoTreeView)
|
||||
router.register(r'recursive', views.TodoRecursiveView)
|
||||
router.register(r'harvest', views.HarvestViewSet)
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from rest_framework import viewsets
|
||||
from rest_framework import mixins, permissions, viewsets
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||
from .models import Pack, Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||
from .serializer import (
|
||||
TodoAnotherSerializer, TodoRecursiveSerializer, TodoSerializer, TodoTreeSerializer, TodoYetAnotherSerializer
|
||||
HarvestSerializer, TodoAnotherSerializer, TodoRecursiveSerializer, TodoSerializer, TodoTreeSerializer,
|
||||
TodoYetAnotherSerializer
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -75,3 +77,16 @@ class TodoRecursiveView(viewsets.ModelViewSet):
|
|||
@swagger_auto_schema(responses={200: TodoRecursiveSerializer(many=True)})
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super(TodoRecursiveView, self).list(request, *args, **kwargs)
|
||||
|
||||
|
||||
class HarvestViewSet(mixins.ListModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
|
||||
queryset = Pack.objects.all()
|
||||
serializer_class = HarvestSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
authentication_classes = [TokenAuthentication]
|
||||
|
||||
def perform_update(self, serializer):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.db import migrations, IntegrityError
|
||||
from django.db import migrations, IntegrityError, transaction
|
||||
|
||||
|
||||
def add_default_user(apps, schema_editor):
|
||||
|
|
@ -13,14 +13,15 @@ def add_default_user(apps, schema_editor):
|
|||
User = apps.get_model(settings.AUTH_USER_MODEL)
|
||||
|
||||
try:
|
||||
admin = User(
|
||||
username=username,
|
||||
email=email,
|
||||
password=make_password(password),
|
||||
is_superuser=True,
|
||||
is_staff=True
|
||||
)
|
||||
admin.save()
|
||||
with transaction.atomic():
|
||||
admin = User(
|
||||
username=username,
|
||||
email=email,
|
||||
password=make_password(password),
|
||||
is_superuser=True,
|
||||
is_staff=True
|
||||
)
|
||||
admin.save()
|
||||
except IntegrityError:
|
||||
sys.stdout.write(" User '%s <%s>' already exists..." % (username, email))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import sys
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import serializers
|
||||
|
||||
|
|
@ -6,7 +8,10 @@ from snippets.models import Snippet
|
|||
|
||||
try:
|
||||
import typing # noqa: F401
|
||||
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
||||
if sys.version_info >= (3, 4):
|
||||
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
||||
else:
|
||||
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
||||
except ImportError:
|
||||
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -57,10 +57,15 @@ def swagger_dict(swagger, codec_json):
|
|||
@pytest.fixture
|
||||
def validate_schema():
|
||||
def validate_schema(swagger):
|
||||
from flex.core import parse as validate_flex
|
||||
try:
|
||||
from flex.core import parse as validate_flex
|
||||
|
||||
validate_flex(copy.deepcopy(swagger))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||
|
||||
validate_flex(copy.deepcopy(swagger))
|
||||
validate_ssv(copy.deepcopy(swagger))
|
||||
|
||||
return validate_schema
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
swagger: '2.0'
|
||||
info:
|
||||
title: Snippets API
|
||||
description: "This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg)\
|
||||
\ Django Rest Framework library.\n\nThe `swagger-ui` view can be found [here](/cached/swagger).\
|
||||
\ \nThe `ReDoc` view can be found [here](/cached/redoc). \nThe swagger YAML\
|
||||
\ document can be found [here](/cached/swagger.yaml). \n\nYou can log in using\
|
||||
\ the pre-existing `admin` user with password `passwordadmin`."
|
||||
description: |-
|
||||
This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg) Django Rest Framework library.
|
||||
|
||||
The `swagger-ui` view can be found [here](/cached/swagger).
|
||||
The `ReDoc` view can be found [here](/cached/redoc).
|
||||
The swagger YAML document can be found [here](/cached/swagger.yaml).
|
||||
|
||||
You can log in using the pre-existing `admin` user with password `passwordadmin`.
|
||||
termsOfService: https://www.google.com/policies/terms/
|
||||
contact:
|
||||
email: contact@snippets.local
|
||||
|
|
@ -276,7 +279,10 @@ paths:
|
|||
get:
|
||||
operationId: people_list
|
||||
description: ''
|
||||
parameters: []
|
||||
parameters:
|
||||
- name: unknown_paginator
|
||||
in: query
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
|
|
@ -440,6 +446,46 @@ paths:
|
|||
tags:
|
||||
- snippets
|
||||
parameters: []
|
||||
/snippets/views/{snippet_pk}/:
|
||||
get:
|
||||
operationId: snippetsViewsRead
|
||||
description: SnippetViewerList classdoc
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: A page number within the paginated result set.
|
||||
required: false
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
required:
|
||||
- count
|
||||
- results
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
next:
|
||||
type: string
|
||||
format: uri
|
||||
x-nullable: true
|
||||
previous:
|
||||
type: string
|
||||
format: uri
|
||||
x-nullable: true
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/SnippetViewer'
|
||||
tags:
|
||||
- snippets
|
||||
parameters:
|
||||
- name: snippet_pk
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
/snippets/{id}/:
|
||||
get:
|
||||
operationId: snippetsRead
|
||||
|
|
@ -552,6 +598,60 @@ paths:
|
|||
description: A unique integer value identifying this todo another.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/harvest/:
|
||||
get:
|
||||
operationId: todo_harvest_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Harvest'
|
||||
tags:
|
||||
- todo
|
||||
parameters: []
|
||||
/todo/harvest/{id}/:
|
||||
put:
|
||||
operationId: todo_harvest_update
|
||||
description: ''
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Harvest'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Harvest'
|
||||
tags:
|
||||
- todo
|
||||
patch:
|
||||
operationId: todo_harvest_partial_update
|
||||
description: ''
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Harvest'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Harvest'
|
||||
tags:
|
||||
- todo
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this pack.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/recursive/:
|
||||
get:
|
||||
operationId: todo_recursive_list
|
||||
|
|
@ -902,6 +1002,11 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
read_only_nullable:
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
references:
|
||||
description: this is a really bad example
|
||||
type: object
|
||||
|
|
@ -1011,7 +1116,7 @@ definitions:
|
|||
type: object
|
||||
properties:
|
||||
projectName:
|
||||
title: Project name
|
||||
title: project name custom title
|
||||
description: Name of the project
|
||||
type: string
|
||||
minLength: 1
|
||||
|
|
@ -1023,6 +1128,7 @@ definitions:
|
|||
Snippet:
|
||||
required:
|
||||
- code
|
||||
- tags
|
||||
- language
|
||||
type: object
|
||||
properties:
|
||||
|
|
@ -1057,6 +1163,13 @@ definitions:
|
|||
title: Code
|
||||
type: string
|
||||
minLength: 1
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 2
|
||||
maxItems: 15
|
||||
minItems: 3
|
||||
linenos:
|
||||
title: Linenos
|
||||
type: boolean
|
||||
|
|
@ -1069,480 +1182,26 @@ definitions:
|
|||
description: The name of the programming language
|
||||
type: string
|
||||
enum:
|
||||
- abap
|
||||
- abnf
|
||||
- ada
|
||||
- adl
|
||||
- agda
|
||||
- aheui
|
||||
- ahk
|
||||
- alloy
|
||||
- ampl
|
||||
- antlr
|
||||
- antlr-as
|
||||
- antlr-cpp
|
||||
- antlr-csharp
|
||||
- antlr-java
|
||||
- antlr-objc
|
||||
- antlr-perl
|
||||
- antlr-python
|
||||
- antlr-ruby
|
||||
- apacheconf
|
||||
- apl
|
||||
- applescript
|
||||
- arduino
|
||||
- as
|
||||
- as3
|
||||
- aspectj
|
||||
- aspx-cs
|
||||
- aspx-vb
|
||||
- asy
|
||||
- at
|
||||
- autoit
|
||||
- awk
|
||||
- basemake
|
||||
- bash
|
||||
- bat
|
||||
- bbcode
|
||||
- bc
|
||||
- befunge
|
||||
- bib
|
||||
- blitzbasic
|
||||
- blitzmax
|
||||
- bnf
|
||||
- boo
|
||||
- boogie
|
||||
- brainfuck
|
||||
- bro
|
||||
- bst
|
||||
- bugs
|
||||
- c
|
||||
- c-objdump
|
||||
- ca65
|
||||
- cadl
|
||||
- camkes
|
||||
- capdl
|
||||
- capnp
|
||||
- cbmbas
|
||||
- ceylon
|
||||
- cfc
|
||||
- cfengine3
|
||||
- cfm
|
||||
- cfs
|
||||
- chai
|
||||
- chapel
|
||||
- cheetah
|
||||
- cirru
|
||||
- clay
|
||||
- clean
|
||||
- clojure
|
||||
- clojurescript
|
||||
- cmake
|
||||
- cobol
|
||||
- cobolfree
|
||||
- coffee-script
|
||||
- common-lisp
|
||||
- componentpascal
|
||||
- console
|
||||
- control
|
||||
- coq
|
||||
- cpp
|
||||
- cpp-objdump
|
||||
- cpsa
|
||||
- cr
|
||||
- crmsh
|
||||
- croc
|
||||
- cryptol
|
||||
- csharp
|
||||
- csound
|
||||
- csound-document
|
||||
- csound-score
|
||||
- css
|
||||
- css+django
|
||||
- css+erb
|
||||
- css+genshitext
|
||||
- css+lasso
|
||||
- css+mako
|
||||
- css+mozpreproc
|
||||
- css+myghty
|
||||
- css+php
|
||||
- css+smarty
|
||||
- cucumber
|
||||
- cuda
|
||||
- cypher
|
||||
- cython
|
||||
- d
|
||||
- d-objdump
|
||||
- dart
|
||||
- delphi
|
||||
- dg
|
||||
- diff
|
||||
- django
|
||||
- docker
|
||||
- doscon
|
||||
- dpatch
|
||||
- dtd
|
||||
- duel
|
||||
- dylan
|
||||
- dylan-console
|
||||
- dylan-lid
|
||||
- earl-grey
|
||||
- easytrieve
|
||||
- ebnf
|
||||
- ec
|
||||
- ecl
|
||||
- eiffel
|
||||
- elixir
|
||||
- elm
|
||||
- emacs
|
||||
- erb
|
||||
- erl
|
||||
- erlang
|
||||
- evoque
|
||||
- extempore
|
||||
- ezhil
|
||||
- factor
|
||||
- fan
|
||||
- fancy
|
||||
- felix
|
||||
- fennel
|
||||
- fish
|
||||
- flatline
|
||||
- forth
|
||||
- fortran
|
||||
- fortranfixed
|
||||
- foxpro
|
||||
- fsharp
|
||||
- gap
|
||||
- gas
|
||||
- genshi
|
||||
- genshitext
|
||||
- glsl
|
||||
- gnuplot
|
||||
- go
|
||||
- golo
|
||||
- gooddata-cl
|
||||
- gosu
|
||||
- groff
|
||||
- groovy
|
||||
- gst
|
||||
- haml
|
||||
- handlebars
|
||||
- haskell
|
||||
- haxeml
|
||||
- hexdump
|
||||
- hlsl
|
||||
- hsail
|
||||
- html
|
||||
- html+cheetah
|
||||
- html+django
|
||||
- html+evoque
|
||||
- html+genshi
|
||||
- html+handlebars
|
||||
- html+lasso
|
||||
- html+mako
|
||||
- html+myghty
|
||||
- html+ng2
|
||||
- html+php
|
||||
- html+smarty
|
||||
- html+twig
|
||||
- html+velocity
|
||||
- http
|
||||
- hx
|
||||
- hybris
|
||||
- hylang
|
||||
- i6t
|
||||
- idl
|
||||
- idris
|
||||
- iex
|
||||
- igor
|
||||
- inform6
|
||||
- inform7
|
||||
- ini
|
||||
- io
|
||||
- ioke
|
||||
- irc
|
||||
- isabelle
|
||||
- j
|
||||
- jags
|
||||
- jasmin
|
||||
- java
|
||||
- javascript+mozpreproc
|
||||
- jcl
|
||||
- jlcon
|
||||
- js
|
||||
- js+cheetah
|
||||
- js+django
|
||||
- js+erb
|
||||
- js+genshitext
|
||||
- js+lasso
|
||||
- js+mako
|
||||
- js+myghty
|
||||
- js+php
|
||||
- js+smarty
|
||||
- jsgf
|
||||
- json
|
||||
- json-object
|
||||
- jsonld
|
||||
- jsp
|
||||
- julia
|
||||
- juttle
|
||||
- kal
|
||||
- kconfig
|
||||
- koka
|
||||
- kotlin
|
||||
- lagda
|
||||
- lasso
|
||||
- lcry
|
||||
- lean
|
||||
- less
|
||||
- lhs
|
||||
- lidr
|
||||
- lighty
|
||||
- limbo
|
||||
- liquid
|
||||
- live-script
|
||||
- llvm
|
||||
- logos
|
||||
- logtalk
|
||||
- lsl
|
||||
- lua
|
||||
- make
|
||||
- mako
|
||||
- maql
|
||||
- mask
|
||||
- mason
|
||||
- mathematica
|
||||
- matlab
|
||||
- matlabsession
|
||||
- md
|
||||
- minid
|
||||
- modelica
|
||||
- modula2
|
||||
- monkey
|
||||
- monte
|
||||
- moocode
|
||||
- moon
|
||||
- mozhashpreproc
|
||||
- mozpercentpreproc
|
||||
- mql
|
||||
- mscgen
|
||||
- mupad
|
||||
- mxml
|
||||
- myghty
|
||||
- mysql
|
||||
- nasm
|
||||
- ncl
|
||||
- nemerle
|
||||
- nesc
|
||||
- newlisp
|
||||
- newspeak
|
||||
- ng2
|
||||
- nginx
|
||||
- nim
|
||||
- nit
|
||||
- nixos
|
||||
- nsis
|
||||
- numpy
|
||||
- nusmv
|
||||
- objdump
|
||||
- objdump-nasm
|
||||
- objective-c
|
||||
- objective-c++
|
||||
- objective-j
|
||||
- ocaml
|
||||
- octave
|
||||
- odin
|
||||
- ooc
|
||||
- opa
|
||||
- openedge
|
||||
- pacmanconf
|
||||
- pan
|
||||
- parasail
|
||||
- pawn
|
||||
- perl
|
||||
- perl6
|
||||
- php
|
||||
- pig
|
||||
- pike
|
||||
- pkgconfig
|
||||
- plpgsql
|
||||
- postgresql
|
||||
- postscript
|
||||
- pot
|
||||
- pov
|
||||
- powershell
|
||||
- praat
|
||||
- prolog
|
||||
- properties
|
||||
- protobuf
|
||||
- ps1con
|
||||
- psql
|
||||
- pug
|
||||
- puppet
|
||||
- py3tb
|
||||
- pycon
|
||||
- pypylog
|
||||
- pytb
|
||||
- python
|
||||
- python3
|
||||
- qbasic
|
||||
- qml
|
||||
- qvto
|
||||
- racket
|
||||
- ragel
|
||||
- ragel-c
|
||||
- ragel-cpp
|
||||
- ragel-d
|
||||
- ragel-em
|
||||
- ragel-java
|
||||
- ragel-objc
|
||||
- ragel-ruby
|
||||
- raw
|
||||
- rb
|
||||
- rbcon
|
||||
- rconsole
|
||||
- rd
|
||||
- rebol
|
||||
- red
|
||||
- redcode
|
||||
- registry
|
||||
- resource
|
||||
- rexx
|
||||
- rhtml
|
||||
- rnc
|
||||
- roboconf-graph
|
||||
- roboconf-instances
|
||||
- robotframework
|
||||
- rql
|
||||
- rsl
|
||||
- rst
|
||||
- rts
|
||||
- rust
|
||||
- sas
|
||||
- sass
|
||||
- sc
|
||||
- scala
|
||||
- scaml
|
||||
- scheme
|
||||
- scilab
|
||||
- scss
|
||||
- shen
|
||||
- silver
|
||||
- slim
|
||||
- smali
|
||||
- smalltalk
|
||||
- smarty
|
||||
- sml
|
||||
- snobol
|
||||
- snowball
|
||||
- sourceslist
|
||||
- sp
|
||||
- sparql
|
||||
- spec
|
||||
- splus
|
||||
- sql
|
||||
- sqlite3
|
||||
- squidconf
|
||||
- ssp
|
||||
- stan
|
||||
- stata
|
||||
- swift
|
||||
- swig
|
||||
- systemverilog
|
||||
- tads3
|
||||
- tap
|
||||
- tasm
|
||||
- tcl
|
||||
- tcsh
|
||||
- tcshcon
|
||||
- tea
|
||||
- termcap
|
||||
- terminfo
|
||||
- terraform
|
||||
- tex
|
||||
- text
|
||||
- thrift
|
||||
- todotxt
|
||||
- trac-wiki
|
||||
- treetop
|
||||
- ts
|
||||
- tsql
|
||||
- turtle
|
||||
- twig
|
||||
- typoscript
|
||||
- typoscriptcssdata
|
||||
- typoscripthtmldata
|
||||
- urbiscript
|
||||
- vala
|
||||
- vb.net
|
||||
- vcl
|
||||
- vclsnippets
|
||||
- vctreestatus
|
||||
- velocity
|
||||
- verilog
|
||||
- vgl
|
||||
- vhdl
|
||||
- vim
|
||||
- wdiff
|
||||
- whiley
|
||||
- x10
|
||||
- xml
|
||||
- xml+cheetah
|
||||
- xml+django
|
||||
- xml+erb
|
||||
- xml+evoque
|
||||
- xml+lasso
|
||||
- xml+mako
|
||||
- xml+myghty
|
||||
- xml+php
|
||||
- xml+smarty
|
||||
- xml+velocity
|
||||
- xorg.conf
|
||||
- xquery
|
||||
- xslt
|
||||
- xtend
|
||||
- xul+mozpreproc
|
||||
- yaml
|
||||
- yaml+jinja
|
||||
- zephir
|
||||
default: python
|
||||
readOnlyNullable:
|
||||
title: Read only nullable
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
styles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- abap
|
||||
- algol
|
||||
- algol_nu
|
||||
- arduino
|
||||
- autumn
|
||||
- borland
|
||||
- bw
|
||||
- colorful
|
||||
- default
|
||||
- emacs
|
||||
- friendly
|
||||
- fruity
|
||||
- igor
|
||||
- lovelace
|
||||
- manni
|
||||
- monokai
|
||||
- murphy
|
||||
- native
|
||||
- paraiso-dark
|
||||
- paraiso-light
|
||||
- pastie
|
||||
- perldoc
|
||||
- rainbow_dash
|
||||
- rrt
|
||||
- tango
|
||||
- trac
|
||||
- solarized-dark
|
||||
- vim
|
||||
- vs
|
||||
- xcode
|
||||
default:
|
||||
- friendly
|
||||
- solarized-dark
|
||||
lines:
|
||||
type: array
|
||||
items:
|
||||
|
|
@ -1553,6 +1212,7 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
readOnly: true
|
||||
maxItems: 100
|
||||
difficultyFactor:
|
||||
title: Difficulty factor
|
||||
description: this is here just to test FloatField
|
||||
|
|
@ -1570,6 +1230,22 @@ definitions:
|
|||
format: decimal
|
||||
default: 0.0
|
||||
minimum: 0.0
|
||||
SnippetViewer:
|
||||
required:
|
||||
- snippet
|
||||
- viewer
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
snippet:
|
||||
title: Snippet
|
||||
type: integer
|
||||
viewer:
|
||||
title: Viewer
|
||||
type: integer
|
||||
Todo:
|
||||
required:
|
||||
- title
|
||||
|
|
@ -1593,6 +1269,17 @@ definitions:
|
|||
minLength: 1
|
||||
todo:
|
||||
$ref: '#/definitions/Todo'
|
||||
Harvest:
|
||||
type: object
|
||||
properties:
|
||||
size_code:
|
||||
title: Size code
|
||||
type: string
|
||||
enum:
|
||||
- '50'
|
||||
- '100'
|
||||
- '200'
|
||||
readOnly: true
|
||||
TodoRecursive:
|
||||
required:
|
||||
- title
|
||||
|
|
@ -1617,6 +1304,7 @@ definitions:
|
|||
required:
|
||||
- title
|
||||
- children
|
||||
- many_children
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
|
|
@ -1632,6 +1320,10 @@ definitions:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoTree'
|
||||
many_children:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoTree'
|
||||
TodoYetAnother:
|
||||
required:
|
||||
- title
|
||||
|
|
@ -1813,7 +1505,9 @@ definitions:
|
|||
readOnly: true
|
||||
help_text_example_3:
|
||||
title: Help text example 3
|
||||
description: "\n docstring is set so should appear in swagger as fallback\n\
|
||||
\ :return:\n "
|
||||
description: |2
|
||||
|
||||
docstring is set so should appear in swagger as fallback
|
||||
:return:
|
||||
type: integer
|
||||
readOnly: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.inspectors.field import get_basic_type_info_from_hint
|
||||
|
||||
try:
|
||||
import typing
|
||||
from typing import Dict, List, Union, Optional, Set
|
||||
except ImportError:
|
||||
typing = None
|
||||
|
||||
if typing:
|
||||
@pytest.mark.parametrize('hint_class, expected_swagger_type_info', [
|
||||
(int, {'type': openapi.TYPE_INTEGER, 'format': None}),
|
||||
(str, {'type': openapi.TYPE_STRING, 'format': None}),
|
||||
(bool, {'type': openapi.TYPE_BOOLEAN, 'format': None}),
|
||||
(dict, {'type': openapi.TYPE_OBJECT, 'format': None}),
|
||||
(Dict[int, int], {'type': openapi.TYPE_OBJECT, 'format': None}),
|
||||
(uuid.UUID, {'type': openapi.TYPE_STRING, 'format': openapi.FORMAT_UUID}),
|
||||
(List[int], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER)}),
|
||||
(List[str], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_STRING)}),
|
||||
(List[bool], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_BOOLEAN)}),
|
||||
(Set[int], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER)}),
|
||||
(Optional[bool], {'type': openapi.TYPE_BOOLEAN, 'format': None, 'x-nullable': True}),
|
||||
(Optional[List[int]], {
|
||||
'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER), 'x-nullable': True
|
||||
}),
|
||||
(Union[List[int], type(None)], {
|
||||
'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER), 'x-nullable': True
|
||||
}),
|
||||
# Following cases are not 100% correct, but it should work somehow and not crash.
|
||||
(Union[int, float], None),
|
||||
(List, {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_STRING)}),
|
||||
('SomeType', None),
|
||||
(type('SomeType', (object,), {}), None),
|
||||
(None, None),
|
||||
(6, None),
|
||||
])
|
||||
def test_get_basic_type_info_from_hint(hint_class, expected_swagger_type_info):
|
||||
type_info = get_basic_type_info_from_hint(hint_class)
|
||||
assert type_info == expected_swagger_type_info
|
||||
|
|
@ -5,7 +5,7 @@ from drf_yasg.openapi import ReferenceResolver
|
|||
|
||||
def test_basic():
|
||||
scopes = ['s1', 's2']
|
||||
rr = ReferenceResolver(*scopes)
|
||||
rr = ReferenceResolver(*scopes, force_init=True)
|
||||
assert scopes == rr.scopes == list(rr.keys()) == list(rr)
|
||||
rr.set('o1', 1, scope='s1')
|
||||
assert rr.has('o1', scope='s1')
|
||||
|
|
@ -25,7 +25,7 @@ def test_basic():
|
|||
|
||||
def test_scoped():
|
||||
scopes = ['s1', 's2']
|
||||
rr = ReferenceResolver(*scopes)
|
||||
rr = ReferenceResolver(*scopes, force_init=True)
|
||||
r1 = rr.with_scope('s1')
|
||||
r2 = rr.with_scope('s2')
|
||||
with pytest.raises(AssertionError):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import json
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
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_fake_model import models as fake_models
|
||||
from rest_framework import routers, serializers, viewsets
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
|
@ -14,6 +18,11 @@ from drf_yasg.errors import SwaggerGenerationError
|
|||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
try:
|
||||
import typing
|
||||
except ImportError:
|
||||
typing = None
|
||||
|
||||
|
||||
def test_schema_is_valid(swagger, codec_yaml):
|
||||
codec_yaml.encode(swagger)
|
||||
|
|
@ -204,6 +213,7 @@ def test_action_mapping():
|
|||
|
||||
@pytest.mark.parametrize('choices, expected_type', [
|
||||
(['A', 'B'], openapi.TYPE_STRING),
|
||||
([u'A', u'B'], openapi.TYPE_STRING),
|
||||
([123, 456], openapi.TYPE_INTEGER),
|
||||
([1.2, 3.4], openapi.TYPE_NUMBER),
|
||||
(['A', 456], openapi.TYPE_STRING)
|
||||
|
|
@ -229,3 +239,116 @@ def test_choice_field(choices, expected_type):
|
|||
property_schema = swagger['definitions']['Detail']['properties']['detail']
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_json_field():
|
||||
class TestJSONFieldSerializer(serializers.Serializer):
|
||||
json = serializers.JSONField()
|
||||
|
||||
class JSONViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = TestJSONFieldSerializer
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'jsons', JSONViewSet, **_basename_or_base_name('jsons'))
|
||||
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title='Test json field generator', default_version='v1'),
|
||||
patterns=router.urls
|
||||
)
|
||||
|
||||
swagger = generator.get_schema(None, True)
|
||||
property_schema = swagger["definitions"]["TestJSONField"]["properties"]["json"]
|
||||
assert property_schema == openapi.Schema(title='Json', type=openapi.TYPE_OBJECT)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('py_type, expected_type', [
|
||||
(str, openapi.TYPE_STRING),
|
||||
(int, openapi.TYPE_INTEGER),
|
||||
(float, openapi.TYPE_NUMBER),
|
||||
(bool, openapi.TYPE_BOOLEAN),
|
||||
])
|
||||
@pytest.mark.skipif(typing is None or sys.version_info.major < 3, reason="typing not supported")
|
||||
def test_optional_return_type(py_type, expected_type):
|
||||
|
||||
class OptionalMethodSerializer(serializers.Serializer):
|
||||
x = serializers.SerializerMethodField()
|
||||
|
||||
def get_x(self, instance):
|
||||
pass
|
||||
|
||||
# Add the type annotation here in order to avoid a SyntaxError in py27
|
||||
get_x.__annotations__["return"] = typing.Optional[py_type]
|
||||
|
||||
class OptionalMethodViewSet(viewsets.ViewSet):
|
||||
@swagger_auto_schema(responses={200: openapi.Response("OK", OptionalMethodSerializer)})
|
||||
def retrieve(self, request, pk=None):
|
||||
return Response({'optional': None})
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'optional', OptionalMethodViewSet, **_basename_or_base_name('optional'))
|
||||
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title='Test optional parameter', default_version='v1'),
|
||||
patterns=router.urls
|
||||
)
|
||||
swagger = generator.get_schema(None, True)
|
||||
property_schema = swagger["definitions"]["OptionalMethod"]["properties"]["x"]
|
||||
assert property_schema == openapi.Schema(title='X', type=expected_type, readOnly=True)
|
||||
|
||||
|
||||
EXPECTED_DESCRIPTION = """\
|
||||
description: |-
|
||||
This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg) Django Rest Framework library.
|
||||
|
||||
The `swagger-ui` view can be found [here](/cached/swagger).
|
||||
The `ReDoc` view can be found [here](/cached/redoc).
|
||||
The swagger YAML document can be found [here](/cached/swagger.yaml).
|
||||
|
||||
You can log in using the pre-existing `admin` user with password `passwordadmin`.
|
||||
"""
|
||||
|
||||
|
||||
def test_multiline_strings(call_generate_swagger):
|
||||
output = call_generate_swagger(format='yaml')
|
||||
print("|\n|".join(output.splitlines()[:20]))
|
||||
assert EXPECTED_DESCRIPTION in output
|
||||
|
|
|
|||
37
tox.ini
37
tox.ini
|
|
@ -1,14 +1,15 @@
|
|||
[tox]
|
||||
minversion = 3.3.0
|
||||
isolated_build = true
|
||||
isolated_build_env=.package
|
||||
isolated_build_env = .package
|
||||
|
||||
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
|
||||
envlist =
|
||||
py{27,34,35,36}-django111-drf{37,38,39},
|
||||
py{34,35,36,37}-django20-drf{37,38,39},
|
||||
py{35,36,37}-django21-drf{37,38,39},
|
||||
py36-django{111,22}-drf{38,39},
|
||||
py37-django22-drf{38,39,310,311},
|
||||
py38-django{22,3}-drf{310,311},
|
||||
djmaster, lint, docs
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv:.package]
|
||||
# no additional dependencies besides PEP 517
|
||||
|
|
@ -17,16 +18,30 @@ deps =
|
|||
[testenv]
|
||||
deps =
|
||||
django111: Django>=1.11,<2.0
|
||||
django20: Django>=2.0,<2.1
|
||||
django111: django-oauth-toolkit>=1.1.0,<1.2.0
|
||||
|
||||
django21: Django>=2.1,<2.2
|
||||
django21: django-oauth-toolkit>=1.2.0
|
||||
|
||||
drf37: djangorestframework>=3.7.7,<3.8
|
||||
drf38: djangorestframework>=3.8.0,<3.9
|
||||
django22: Django>=2.2,<2.3
|
||||
django22: django-oauth-toolkit>=1.2.0
|
||||
|
||||
django3: Django>=2.2,<2.3
|
||||
django3: django-oauth-toolkit>=1.2.0
|
||||
|
||||
drf38: djangorestframework>=3.8,<3.9
|
||||
drf39: djangorestframework>=3.9,<3.10
|
||||
drf310: djangorestframework>=3.10,<3.11
|
||||
drf311: djangorestframework>=3.11,<3.12
|
||||
|
||||
# test with the latest build of django-rest-framework to get early warning of compatibility issues
|
||||
djmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz
|
||||
typing: typing>=3.6.6
|
||||
|
||||
# test with the latest builds of Django and django-rest-framework
|
||||
# to get early warning of compatibility issues
|
||||
djmaster: https://github.com/django/django/archive/master.tar.gz
|
||||
djmaster: https://github.com/ottoyiu/django-cors-headers/archive/master.tar.gz
|
||||
djmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz
|
||||
djmaster: django-oauth-toolkit>=1.2.0
|
||||
|
||||
# other dependencies
|
||||
-r requirements/validation.txt
|
||||
|
|
@ -72,6 +87,6 @@ known_standard_library =
|
|||
types,warnings
|
||||
known_third_party =
|
||||
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,
|
||||
rest_framework_recursive,flex,gunicorn,inflection,pygments,pytest,rest_framework,ruamel,setuptools_scm,
|
||||
swagger_spec_validator,uritemplate,user_agents,whitenoise,oauth2_provider
|
||||
rest_framework_recursive,flex,gunicorn,inflection,pytest,rest_framework,ruamel,setuptools_scm,
|
||||
swagger_spec_validator,uritemplate,user_agents,whitenoise,oauth2_provider,packaging
|
||||
known_first_party = drf_yasg,testproj,articles,people,snippets,todo,users,urlconfs
|
||||
|
|
|
|||
Loading…
Reference in New Issue