Compare commits

...

15 Commits

Author SHA1 Message Date
Pietro Brenna 728c02356c fix #559: recursively resolve type of one2one using target_field 2020-03-18 19:15:18 +01:00
Cristi Vîjdea 9ccf24c27a Add 1.17.1 changelog 2020-02-17 03:40:09 +02:00
Cristi Vîjdea 8aa255cf56 Drop Python 2.7 tests 2020-02-17 03:40:00 +02:00
Cristi Vîjdea 7491d330a8 Fix ugettext_lazy warning 2020-02-17 03:12:59 +02:00
Cristi Vîjdea ebe21b77c6 Fix tox.ini 2020-02-17 03:12:07 +02:00
Cristi Vîjdea 17da098940 Fix lint errors 2020-02-17 03:06:37 +02:00
Cristi Vîjdea a872eb66d6 Add Django 3.0, DRF 3.11, drop Python 3.5, Django 2.1 2020-02-17 02:58:55 +02:00
johnthagen 6a1166deb5 Add example for using swagger_schema_fields for a Field (#494)
* Add example for using swagger_schema_fields for a Field
* Mention that Meta class can be added to fields as well
* Reference the DRF docs on how to add validation to serializers
2019-11-16 18:27:03 +02:00
Yannick Chabbert b700191f46 add comments on why we returns non form media types by default (#436) 2019-11-14 15:03:11 +02:00
Jethro Lee 5c25ecd8f2 Edit type check for swagger_auto_schema (#490) 2019-11-14 14:57:49 +02:00
johnthagen 8fd27664f1 Fix typo in docstring (#479) 2019-11-14 14:17:53 +02:00
yurihs 456b697ca2 Apply dedent to descriptions (#416) (#464) 2019-11-14 14:13:40 +02:00
johnthagen 9966297f87 Support Python 3.8 (#477)
* Support Python 3.8
* Add Python 3.8 trove classifier
* Add Python 3.8 support to README
2019-11-14 14:01:55 +02:00
Carlos Martinez 27007a9cf4 Fix #485 (#486) 2019-11-14 00:13:29 +02:00
Ned Batchelder a72e5b2899 Write multi-line strings in block style (#466)
Closes  #439
2019-10-03 02:06:05 +03:00
20 changed files with 176 additions and 61 deletions

View File

@ -1,10 +1,8 @@
language: python
python:
- '2.7'
- '3.5'
- '3.6'
- '3.7'
- '3.8-dev'
- '3.8'
dist: xenial
@ -39,7 +37,6 @@ matrix:
allow_failures:
- env: TOXENV=lint
- env: TOXENV=djmaster
- python: '3.8-dev'
fast_finish: true

View File

@ -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.

View File

@ -13,9 +13,9 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
Compatible with
- **Django Rest Framework**: 3.8, 3.9, 3.10
- **Django**: 1.11, 2.1, 2.2
- **Python**: 2.7, 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.

View File

@ -2,6 +2,18 @@
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**
**********

View File

@ -212,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
@ -236,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
@ -389,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

View File

@ -4,3 +4,4 @@
-r lint.txt
tox-battery>=0.5
django-oauth-toolkit

View File

@ -2,9 +2,8 @@
Pillow>=4.3.0
django-filter>=1.1.0,<2.0; python_version == "2.7"
django-filter>=1.1.0; python_version >= "3.5"
#djangorestframework-camel-case>=0.2.0
# tempory replacement of broken lib
-e git+https://github.com/tfranzel/djangorestframework-camel-case.git@bd556d38fa7382acadfe91d93d92d99c663248a9#egg=djangorestframework_camel_case
djangorestframework-camel-case>=1.1.2
djangorestframework-recursive>=0.1.2
dj-database-url>=0.4.2
user_agents>=1.1.0
django-cors-headers

View File

@ -19,7 +19,7 @@ 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, 7)
py3_supported_range = (5, 8)
# convert inclusive range to exclusive range
py3_supported_range = (py3_supported_range[0], py3_supported_range[1] + 1)

View File

@ -1,4 +1,4 @@
from six import raise_from
from six import binary_type, raise_from, text_type
import copy
import json
@ -176,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)

View File

@ -382,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)),

View File

@ -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:

View File

@ -470,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``;

View File

@ -1,6 +1,7 @@
import inspect
import logging
import sys
import textwrap
from collections import OrderedDict
from decimal import Decimal
@ -95,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`
@ -374,10 +375,16 @@ def get_consumes(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):
@ -438,6 +445,9 @@ def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
if type(s) != str:
s = '' + s
# Remove common indentation to get the correct Markdown rendering
s = textwrap.dedent(s)
return s
@ -473,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)

View File

@ -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

View File

@ -193,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,

View File

@ -13,9 +13,9 @@ swagger_info = openapi.Info(
default_version='v1',
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).
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`.""", # noqa
terms_of_service="https://www.google.com/policies/terms/",

View File

@ -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:

View File

@ -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
@ -1502,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

View File

@ -7,11 +7,11 @@ 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
from django_fake_model import models as fake_models
from drf_yasg import codecs, openapi
from drf_yasg.codecs import yaml_sane_load
from drf_yasg.errors import SwaggerGenerationError
@ -334,3 +334,21 @@ def test_optional_return_type(py_type, expected_type):
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

16
tox.ini
View File

@ -5,11 +5,9 @@ isolated_build_env = .package
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
envlist =
py27-django111-drf39-typing,
py27-django111-drf{38,39},
py{35,36}-django{111,21,22}-drf{38,39},
py37-django{21,22}-drf{38,39,310},
py38-django22-drf310,
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
@ -20,21 +18,21 @@ deps =
[testenv]
deps =
django111: Django>=1.11,<2.0
django111: django-cors-headers>=2.1.0
django111: django-oauth-toolkit>=1.1.0,<1.2.0
django21: Django>=2.1,<2.2
django21: django-cors-headers>=2.1.0
django21: django-oauth-toolkit>=1.2.0
django22: Django>=2.2,<2.3
django22: django-cors-headers>=2.1.0
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
drf310: djangorestframework>=3.10,<3.11
drf311: djangorestframework>=3.11,<3.12
typing: typing>=3.6.6