Compare commits

...

9 Commits

Author SHA1 Message Date
Cristi Vîjdea 6ea8711a1f Fix in-place modification of swagger_auto_schema arguments (#75)
Fixes #74
2018-03-05 11:51:51 +02:00
Cristi Vîjdea ee46f59fb1 Infer ChoiceField type from model field when in ModelSerializer
Fixes part of issue #69
2018-02-26 20:32:57 +02:00
Santiago Castro 3f7ad62950 Make 1.10.0 the min six version (#71) 2018-02-26 17:18:52 +02:00
Cristi Vîjdea b38d3e6805 Do not set pattern on non-string values
Fixes #68
2018-02-23 18:51:55 +02:00
Cristi Vîjdea 058fd7096d Change README link from pypi.python.org to pypi.org
Description rendering is prettier on the new version.
2018-02-22 20:46:26 +02:00
Cristi Vîjdea a32321d43a Add release dates to changelog 2018-02-22 20:46:22 +02:00
Cristi Vîjdea f15e70b7db Fix missing assignment for default in openapi.Parameter 2018-02-22 20:33:47 +02:00
Cristi Vîjdea 64c280e222 Allow Response objects with no schema
Fixes #66.
2018-02-22 03:46:44 +02:00
Cristi Vîjdea d5073081d9 Fix ModelViewSet Nested bug (#65)
* Add todo repro app
* Add explicit test against model named "Nested"
* Force serializers named NestedSerializer to be output as inline models
* Allow ref_name to rescue a NestedSerializer
* Add tests and documentation
2018-02-22 03:46:16 +02:00
27 changed files with 514 additions and 40 deletions
+3 -3
View File
@@ -13,9 +13,9 @@
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
@@ -24,7 +24,7 @@
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/drf_yasg/templates" />
<option value="$MODULE_DIR$/src/drf_yasg/templates" />
</list>
</option>
</component>
@@ -32,4 +32,4 @@
<option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>
</module>
+1 -1
View File
@@ -362,7 +362,7 @@ provided out of the box - if you have ``djangorestframework-camel-case`` install
:alt: Codecov
.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg
:target: https://pypi.python.org/pypi/drf-yasg/
:target: https://pypi.org/project/drf-yasg/
:alt: PyPI
.. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg
+73
View File
@@ -2,10 +2,53 @@
Changelog
#########
*********
**1.4.5**
*********
*Release date: Mar 05, 2018*
- **FIXED:** fixed an issue with modification of ``swagger_auto_schema`` arguments in-place during introspection, which
would sometimes cause an incomplete Swagger document to be generated after the first pass (:issue:`74`, :pr:`75`)
*********
**1.4.4**
*********
*Release date: Feb 26, 2018*
- **IMPROVED:** ``type`` for ``ChoiceField`` generated by a ``ModelSerializer`` from a model field with ``choices=...``
will now be set according to the associated model field (:issue:`69`)
- **FIXED:** ``lookup_field`` and ``lookup_value_regex`` on the same ``ViewSet`` will no longer trigger an exception
(:issue:`68`)
*********
**1.4.3**
*********
*Release date: Feb 22, 2018*
- **FIXED:** added a missing assignment that would cause the ``default`` argument to ``openapi.Parameter.__init__`` to
be ignored
*********
**1.4.2**
*********
*Release date: Feb 22, 2018*
- **FIXED:** fixed a bug that causes a ``ModelViewSet`` generated from models with nested ``ForeignKey`` to output
models named ``Nested`` into the ``definitions`` section (:issue:`59`, :pr:`65`)
- **FIXED:** ``Response`` objects without a ``schema`` are now properly handled when passed through
``swagger_auto_schema`` (:issue:`66`)
*********
**1.4.1**
*********
*Release date: Feb 21, 2018*
- **FIXED:** the ``coerce_to_string`` is now respected when setting the type, default value and min/max values of
``DecimalField`` in the OpenAPI schema (:issue:`62`)
- **FIXED:** error responses from web UI views are now rendered with ``TemplateHTMLRenderer`` instead of throwing
@@ -17,6 +60,8 @@ Changelog
**1.4.0**
*********
*Release date: Feb 04, 2018*
- **ADDED:** added settings for OAuth2 client configuration in ``swagger-ui`` (:issue:`53`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.3
@@ -24,6 +69,8 @@ Changelog
**1.3.1**
*********
*Release date: Jan 24, 2018*
- **FIXED:** fixed a bug that would sometimes cause endpoints to wrongly be output as form operations (:issue:`50`)
- **IMPROVED:** added generation of ``produces`` based on renderer classes
- **IMPROVED:** added generation of top-level ``consumes`` and ``produces`` based on
@@ -33,6 +80,8 @@ Changelog
**1.3.0**
*********
*Release date: Jan 23, 2018*
- **ADDED:** security requirements are now correctly set and can be customized; this should fix problems related
to authentication in ``swagger-ui`` Try it out! (:issue:`50`, :pr:`54`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.2
@@ -43,6 +92,8 @@ Changelog
**1.2.2**
*********
*Release date: Jan 12, 2018*
- **FIXED:** djangorestframework>=3.7.7 is now required because of breaking changes
(:issue:`44`, :pr:`45`, thanks to :ghuser:`h-hirokawa`)
@@ -50,12 +101,16 @@ Changelog
**1.2.1**
*********
*Release date: Jan 12, 2018*
- Fixed deployment issues
*********
**1.2.0**
*********
*Release date: Jan 12, 2018 (missing from PyPI due to deployment issues)*
- **ADDED:** ``basePath`` is now generated by taking into account the ``SCRIPT_NAME`` variable and the
longest common prefix of API urls (:issue:`37`, :pr:`42`)
- **IMPROVED:** removed inline scripts and styles from bundled HTML templates to increase CSP compatibility
@@ -67,6 +122,8 @@ Changelog
**1.1.3**
*********
*Release date: Jan 02, 2018*
- **FIXED:** schema view cache will now always ``Vary`` on the ``Cookie`` and ``Authentication`` (the
``Vary`` header was previously only added if ``public`` was set to ``True``) - this fixes issues related to Django
authentication in ``swagger-ui`` and ``CurrentUserDefault`` values in the schema
@@ -75,6 +132,8 @@ Changelog
**1.1.2**
*********
*Release date: Jan 01, 2018*
- **IMPROVED:** updated ``swagger-ui`` to version 3.8.1
- **IMPROVED:** removed some unneeded static files
@@ -82,6 +141,8 @@ Changelog
**1.1.1**
*********
*Release date: Dec 27, 2017*
- **ADDED:** :ref:`generate_swagger management command <management-command>`
(:issue:`29`, :pr:`31`, thanks to :ghuser:`beaugunderson`)
- **FIXED:** fixed improper generation of ``\Z`` regex tokens - will now be repalced by ``$``
@@ -90,6 +151,8 @@ Changelog
**1.1.0**
*********
*Release date: Dec 27, 2017*
- **ADDED:** added support for APIs versioned with ``URLPathVersioning`` or ``NamespaceVersioning``
- **ADDED:** added ability to recursively customize schema generation
:ref:`using pluggable inspector classes <custom-spec-inspectors>`
@@ -105,6 +168,8 @@ Changelog
**1.0.6**
*********
*Release date: Dec 23, 2017*
- **FIXED:** Swagger UI "Try it out!" should now work with Django login
- **FIXED:** callable ``default`` values on serializer fields will now be properly called (:pr:`24`, :issue:`25`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.8.0
@@ -116,6 +181,8 @@ Changelog
**1.0.5**
*********
*Release date: Dec 18, 2017*
- **FIXED:** fixed a crash caused by having read-only Serializers nested by reference
- **FIXED:** removed erroneous backslashes in paths when routes are generated using Django 2
`path() <https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.path>`_
@@ -127,6 +194,8 @@ Changelog
**1.0.4**
*********
*Release date: Dec 16, 2017*
- **FIXED:** fixed improper generation of YAML references
- **ADDED:** added ``query_serializer`` parameter to
:func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`)
@@ -135,6 +204,8 @@ Changelog
**1.0.3**
*********
*Release date: Dec 15, 2017*
- **FIXED:** fixed bug that caused schema views returned from cache to fail (:issue:`14`)
- **FIXED:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by
attempting to shove file parameters into Schema objects
@@ -143,4 +214,6 @@ Changelog
**1.0.2**
*********
*Release date: Dec 13, 2017*
- First published version
+27 -1
View File
@@ -172,7 +172,11 @@ You can define some per-serializer options by adding a ``Meta`` class to your se
Currently, the only option you can add here is
* ``ref_name`` - a string which will be used as the model definition name for this serializer class; setting it to
``None`` will force the serializer to be generated as an inline model everywhere it is used
``None`` will force the serializer to be generated as an inline model everywhere it is used. If two serializers
have the same ``ref_name``, both their usages will be replaced with a reference to the same definition.
If this option is not specified, all serializers have an implicit name derived from their class name, minus any
``Serializer`` suffix (e.g. ``UserSerializer`` -> ``User``, ``SerializerWithSuffix`` -> ``SerializerWithSuffix``)
*************************
Subclassing and extending
@@ -301,3 +305,25 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
This means that you should generally avoid view or method-specific ``FieldInspector``\ s if you are dealing with
references (a.k.a named models), because you can never know which view will be the first to generate the schema
for a given serializer.
**IMPORTANT:** nested fields on ``ModelSerializer``\ s that are generated from model ``ForeignKeys`` will always be
output by value. If you want the by-reference behaviour you have to explictly set the serializer class of nested
fields instead of letting ``ModelSerializer`` generate one automatically; for example:
.. code-block:: python
class OneSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ('id',)
class AnotherSerializer(serializers.ModelSerializer):
chilf = OneSerializer()
class Meta:
model = SomeParentModel
fields = ('id', 'child')
Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline
unless it has a ``ref_name`` set explicitly.
+1 -1
View File
@@ -4,7 +4,7 @@ openapi_codec>=1.3.2
ruamel.yaml>=0.15.34
inflection>=0.3.1
future>=0.16.0
six>=1.11.0
six>=1.10.0
uritemplate>=3.0.0
djangorestframework>=3.7.7
+4 -3
View File
@@ -1,3 +1,4 @@
import copy
import logging
import re
from collections import OrderedDict, defaultdict
@@ -388,7 +389,7 @@ class OpenAPISchemaGenerator(object):
if method in overrides:
overrides = overrides[method]
return overrides
return copy.deepcopy(overrides)
def get_path_parameters(self, path, view_cls):
"""Return a list of Parameter instances corresponding to any templated path variables.
@@ -405,8 +406,8 @@ class OpenAPISchemaGenerator(object):
for variable in uritemplate.variables(path):
model, model_field = get_queryset_field(queryset, variable)
attrs = get_basic_type_info(model_field) or {'type': openapi.TYPE_STRING}
if hasattr(view_cls, 'lookup_value_regex') and getattr(view_cls, 'lookup_field', None) == variable:
attrs['pattern'] = view_cls.lookup_value_regex
if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING:
attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
if model_field and model_field.help_text:
description = force_text(model_field.help_text)
+2 -1
View File
@@ -234,7 +234,8 @@ class FieldInspector(BaseInspector):
if default is not None:
instance_kwargs['default'] = default
instance_kwargs.setdefault('title', title)
if instance_kwargs.get('type', None) != openapi.TYPE_ARRAY:
instance_kwargs.setdefault('title', title)
instance_kwargs.setdefault('description', description)
instance_kwargs.update(kwargs)
+39 -12
View File
@@ -1,3 +1,4 @@
import logging
import operator
from collections import OrderedDict
from decimal import Decimal
@@ -12,6 +13,8 @@ from ..errors import SwaggerGenerationError
from ..utils import decimal_as_float, filter_none
from .base import FieldInspector, NotHandled, SerializerInspector
logger = logging.getLogger(__name__)
class InlineSerializerInspector(SerializerInspector):
"""Provides serializer conversions using :meth:`.FieldInspector.field_to_swagger_object`."""
@@ -54,10 +57,14 @@ class InlineSerializerInspector(SerializerInspector):
serializer = field
serializer_meta = getattr(serializer, 'Meta', None)
serializer_name = type(serializer).__name__
if hasattr(serializer_meta, 'ref_name'):
ref_name = serializer_meta.ref_name
elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer):
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer': " + str(serializer))
ref_name = None
else:
ref_name = type(serializer).__name__
ref_name = serializer_name
if ref_name.endswith('Serializer'):
ref_name = ref_name[:-len('Serializer')]
@@ -77,11 +84,17 @@ class InlineSerializerInspector(SerializerInspector):
if child.required:
required.append(property_name)
return SwaggerType(
result = SwaggerType(
type=openapi.TYPE_OBJECT,
properties=properties,
required=required or None,
)
if not ref_name:
# on an inline model, the title is derived from the field name
# but is visually displayed like the model named, which is confusing
# it is better to just remove title from inline models
del result.title
return result
if not ref_name or not use_references:
return make_schema_definition()
@@ -348,7 +361,10 @@ def get_basic_type_info(field):
else: # pragma: no cover
return None
pattern = find_regex(field) if format in (None, openapi.FORMAT_SLUG) else None
pattern = None
if swagger_type == openapi.TYPE_STRING and format in (None, openapi.FORMAT_SLUG):
pattern = find_regex(field)
limits = find_limits(field)
result = OrderedDict([
@@ -381,16 +397,27 @@ class ChoiceFieldInspector(FieldInspector):
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.MultipleChoiceField):
return SwaggerType(
type=openapi.TYPE_ARRAY,
items=ChildSwaggerType(
type=openapi.TYPE_STRING,
enum=list(field.choices.keys())
if isinstance(field, serializers.ChoiceField):
enum_type = openapi.TYPE_STRING
# 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)
if model_field:
enum_type = get_basic_type_info(model_field).get('type', enum_type)
if isinstance(field, serializers.MultipleChoiceField):
return SwaggerType(
type=openapi.TYPE_ARRAY,
items=ChildSwaggerType(
type=enum_type,
enum=list(field.choices.keys())
)
)
)
elif isinstance(field, serializers.ChoiceField):
return SwaggerType(type=openapi.TYPE_STRING, enum=list(field.choices.keys()))
return SwaggerType(type=enum_type, enum=list(field.choices.keys()))
return NotHandled
+1 -1
View File
@@ -221,7 +221,7 @@ class SwaggerAutoSchema(ViewInspector):
)
elif isinstance(serializer, openapi.Response):
response = serializer
if not isinstance(response.schema, openapi.Schema.OR_REF):
if hasattr(response, 'schema') and not isinstance(response.schema, openapi.Schema.OR_REF):
serializer = force_serializer_instance(response.schema)
response.schema = self.serializer_to_schema(serializer)
elif isinstance(serializer, openapi.Schema.OR_REF):
+1
View File
@@ -396,6 +396,7 @@ class Parameter(SwaggerDict):
self.enum = enum
self.pattern = pattern
self.items = items
self.default = default
self._insert_extras__()
if self['in'] == IN_PATH:
# path parameters must always be required
+9 -4
View File
@@ -11,10 +11,15 @@ from rest_framework.views import APIView
logger = logging.getLogger(__name__)
#: used to forcibly remove the body of a request via :func:`.swagger_auto_schema`
no_body = object()
unset = object()
class no_body(object):
"""Used as a sentinel value to forcibly remove the body of a request via :func:`.swagger_auto_schema`."""
pass
class unset(object):
"""Used as a sentinel value for function parameters not set by the caller where ``None`` would be a valid value."""
pass
def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None,
@@ -33,7 +38,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
:param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object;
this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS``
setting, and can be set to ``None`` to prevent this operation from being generated
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :data:`.no_body`. The value given here
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :class:`.no_body`. The value given here
will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``.
A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters
@@ -0,0 +1,18 @@
# Generated by Django 2.0.1 on 2018-02-26 18:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('articles', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='article',
name='article_type',
field=models.PositiveSmallIntegerField(choices=[(1, 'first'), (2, 'second'), (3, 'third'), (7, 'seven'), (8, 'eight')], help_text='IntegerField declared on model with choices=(...) and exposed via ModelSerializer', null=True),
),
]
+4
View File
@@ -8,5 +8,9 @@ class Article(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey('auth.User', related_name='articles', on_delete=models.CASCADE)
article_type = models.PositiveSmallIntegerField(
help_text="IntegerField declared on model with choices=(...) and exposed via ModelSerializer",
choices=((1, "first"), (2, "second"), (3, "third"), (7, "seven"), (8, "eight")), null=True
)
cover = models.ImageField(upload_to='article/original/', blank=True)
+2 -2
View File
@@ -16,7 +16,7 @@ class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
'references', 'uuid', 'cover', 'cover_name')
'references', 'uuid', 'cover', 'cover_name', 'article_type')
read_only_fields = ('date_created', 'date_modified',
'references', 'uuid', 'cover_name')
lookup_field = 'slug'
@@ -31,7 +31,7 @@ class ArticleSerializer(serializers.ModelSerializer):
class ImageUploadSerializer(serializers.Serializer):
what_am_i_doing = serializers.RegexField(regex=r"^69$", help_text="test")
what_am_i_doing = serializers.RegexField(regex=r"^69$", help_text="test", default="69")
image_styles = serializers.ListSerializer(
child=serializers.ChoiceField(choices=['wide', 'tall', 'thumb', 'social']),
help_text="Parameter with Items"
+6 -2
View File
@@ -1,7 +1,7 @@
from djangorestframework_camel_case.parser import CamelCaseJSONParser
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
from inflection import camelize
from rest_framework import generics
from rest_framework import generics, status
from rest_framework.parsers import FormParser
from drf_yasg import openapi
@@ -62,8 +62,12 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
type=openapi.TYPE_INTEGER,
description="path parameter override",
required=True
),
], responses={
status.HTTP_204_NO_CONTENT: openapi.Response(
description="This should not crash"
)
])
})
def delete(self, request, *args, **kwargs):
"""delete method docstring"""
return super(SnippetDetail, self).patch(request, *args, **kwargs)
+1
View File
@@ -26,6 +26,7 @@ INSTALLED_APPS = [
'snippets',
'users',
'articles',
'todo',
]
MIDDLEWARE = [
+1
View File
@@ -62,5 +62,6 @@ urlpatterns = [
url(r'^snippets/', include('snippets.urls')),
url(r'^articles/', include('articles.urls')),
url(r'^users/', include('users.urls')),
url(r'^todo/', include('todo.urls')),
url(r'^plain/', plain_view),
]
View File
+40
View File
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-02-21 23:26
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Todo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='TodoAnother',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50)),
('todo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.Todo')),
],
),
migrations.CreateModel(
name='TodoYetAnother',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50)),
('todo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.TodoAnother')),
],
),
]
+15
View File
@@ -0,0 +1,15 @@
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=50)
class TodoAnother(models.Model):
todo = models.ForeignKey(Todo, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
class TodoYetAnother(models.Model):
todo = models.ForeignKey(TodoAnother, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
+24
View File
@@ -0,0 +1,24 @@
from rest_framework import serializers
from .models import Todo, TodoAnother, TodoYetAnother
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('title',)
class TodoAnotherSerializer(serializers.ModelSerializer):
todo = TodoSerializer()
class Meta:
model = TodoAnother
fields = ('title', 'todo')
class TodoYetAnotherSerializer(serializers.ModelSerializer):
class Meta:
model = TodoYetAnother
fields = ('title', 'todo')
depth = 2
+10
View File
@@ -0,0 +1,10 @@
from rest_framework import routers
from todo import views
router = routers.DefaultRouter()
router.register(r'', views.TodoViewSet)
router.register(r'another', views.TodoAnotherViewSet)
router.register(r'yetanother', views.TodoYetAnotherViewSet)
urlpatterns = router.urls
+22
View File
@@ -0,0 +1,22 @@
from rest_framework import viewsets
from .models import Todo, TodoAnother, TodoYetAnother
from .serializer import TodoAnotherSerializer, TodoSerializer, TodoYetAnotherSerializer
class TodoViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
lookup_field = 'id'
lookup_value_regex = '[0-9]+'
class TodoAnotherViewSet(viewsets.ReadOnlyModelViewSet):
queryset = TodoAnother.objects.all()
serializer_class = TodoAnotherSerializer
class TodoYetAnotherViewSet(viewsets.ReadOnlyModelViewSet):
queryset = TodoYetAnother.objects.all()
serializer_class = TodoYetAnotherSerializer
+170 -9
View File
@@ -198,9 +198,10 @@ paths:
- name: what_am_i_doing
in: formData
description: test
required: true
required: false
type: string
pattern: ^69$
default: '69'
- name: image_styles
in: formData
description: Parameter with Items
@@ -330,7 +331,7 @@ paths:
type: integer
responses:
'204':
description: ''
description: This should not crash
tags:
- snippets
parameters:
@@ -339,6 +340,105 @@ paths:
description: A unique integer value identifying this snippet.
required: true
type: integer
/todo/:
get:
operationId: todo_list
description: ''
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/Todo'
tags:
- todo
parameters: []
/todo/another/:
get:
operationId: todo_another_list
description: ''
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/TodoAnother'
tags:
- todo
parameters: []
/todo/another/{id}/:
get:
operationId: todo_another_read
description: ''
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/TodoAnother'
tags:
- todo
parameters:
- name: id
in: path
description: A unique integer value identifying this todo another.
required: true
type: integer
/todo/yetanother/:
get:
operationId: todo_yetanother_list
description: ''
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/TodoYetAnother'
tags:
- todo
parameters: []
/todo/yetanother/{id}/:
get:
operationId: todo_yetanother_read
description: ''
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/TodoYetAnother'
tags:
- todo
parameters:
- name: id
in: path
description: A unique integer value identifying this todo yet another.
required: true
type: integer
/todo/{id}/:
get:
operationId: todo_read
description: ''
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Todo'
tags:
- todo
parameters:
- name: id
in: path
description: A unique integer value identifying this todo.
required: true
type: integer
/users/:
get:
operationId: users_list
@@ -491,6 +591,16 @@ definitions:
cover_name:
type: string
readOnly: true
article_type:
description: IntegerField declared on model with choices=(...) and exposed
via ModelSerializer
type: integer
enum:
- 1
- 2
- 3
- 7
- 8
Project:
required:
- projectName
@@ -538,7 +648,6 @@ definitions:
title: Linenos
type: boolean
language:
title: Language
description: Sample help text for language
type: object
properties:
@@ -983,7 +1092,6 @@ definitions:
- zephir
default: python
styles:
title: Styles
type: array
items:
type: string
@@ -1020,12 +1128,10 @@ definitions:
default:
- friendly
lines:
title: Lines
type: array
items:
type: integer
exampleProjects:
title: Example projects
type: array
items:
$ref: '#/definitions/Project'
@@ -1047,6 +1153,64 @@ definitions:
format: decimal
default: 0.0
minimum: 0.0
Todo:
required:
- title
type: object
properties:
title:
title: Title
type: string
maxLength: 50
TodoAnother:
required:
- title
- todo
type: object
properties:
title:
title: Title
type: string
maxLength: 50
todo:
$ref: '#/definitions/Todo'
TodoYetAnother:
required:
- title
type: object
properties:
title:
title: Title
type: string
maxLength: 50
todo:
required:
- title
type: object
properties:
id:
title: ID
type: integer
readOnly: true
title:
title: Title
type: string
maxLength: 50
todo:
required:
- title
type: object
properties:
id:
title: ID
type: integer
readOnly: true
title:
title: Title
type: string
maxLength: 50
readOnly: true
readOnly: true
UserSerializerrr:
required:
- username
@@ -1071,13 +1235,11 @@ definitions:
format: email
maxLength: 254
articles:
title: Articles
type: array
items:
type: integer
uniqueItems: true
snippets:
title: Snippets
type: array
items:
type: integer
@@ -1095,7 +1257,6 @@ definitions:
format: date
readOnly: true
article_slugs:
title: Article slugs
type: array
items:
type: string
+6
View File
@@ -46,3 +46,9 @@ def test_noop_inspectors(swagger_settings, mock_schema_request, codec_json, refe
json_bytes = codec_json.encode(swagger)
swagger_dict = json.loads(json_bytes.decode('utf-8'), object_pairs_hook=OrderedDict)
compare_schemas(swagger_dict, reference_schema)
def test_no_nested_model(swagger_dict):
# ForeignKey models in deep ModelViewSets might wrongly be labeled as 'Nested' in the definitions section
# see https://github.com/axnsan12/drf-yasg/issues/59
assert 'Nested' not in swagger_dict['definitions']
+34
View File
@@ -2,11 +2,14 @@ import json
from collections import OrderedDict
import pytest
from rest_framework import routers, serializers, viewsets
from rest_framework.response import Response
from drf_yasg import codecs, openapi
from drf_yasg.codecs import yaml_sane_load
from drf_yasg.errors import SwaggerGenerationError
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.utils import swagger_auto_schema
def test_schema_is_valid(swagger, codec_yaml):
@@ -79,3 +82,34 @@ def test_securiy_requirements(swagger_settings, mock_schema_request):
swagger = generator.get_schema(mock_schema_request, public=True)
assert swagger['security'] == []
def test_replaced_serializer():
class DetailSerializer(serializers.Serializer):
detail = serializers.CharField()
class DetailViewSet(viewsets.ViewSet):
serializer_class = DetailSerializer
@swagger_auto_schema(responses={404: openapi.Response("Not found or Not accessible", DetailSerializer)})
def retrieve(self, request, pk=None):
serializer = DetailSerializer({'detail': None})
return Response(serializer.data)
router = routers.DefaultRouter()
router.register(r'details', DetailViewSet, base_name='details')
generator = OpenAPISchemaGenerator(
info=openapi.Info(title="Test generator", default_version="v1"),
version="v2",
url='',
patterns=router.urls
)
for _ in range(3):
swagger = generator.get_schema(None, True)
assert 'Detail' in swagger['definitions']
assert 'detail' in swagger['definitions']['Detail']['properties']
responses = swagger['paths']['/details/{id}/']['get']['responses']
assert '404' in responses
assert responses['404']['schema']['$ref'] == "#/definitions/Detail"