Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee46f59fb1 | |||
| 3f7ad62950 | |||
| b38d3e6805 | |||
| 058fd7096d | |||
| a32321d43a | |||
| f15e70b7db | |||
| 64c280e222 | |||
| d5073081d9 |
Generated
+3
-3
@@ -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
@@ -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
|
||||
|
||||
@@ -2,10 +2,44 @@
|
||||
Changelog
|
||||
#########
|
||||
|
||||
|
||||
*********
|
||||
**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 +51,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 +60,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 +71,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 +83,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 +92,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 +113,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 +123,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 +132,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 +142,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 +159,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 +172,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 +185,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 +195,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 +205,6 @@ Changelog
|
||||
**1.0.2**
|
||||
*********
|
||||
|
||||
*Release date: Dec 13, 2017*
|
||||
|
||||
- First published version
|
||||
|
||||
+27
-1
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -405,8 +405,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -26,6 +26,7 @@ INSTALLED_APPS = [
|
||||
'snippets',
|
||||
'users',
|
||||
'articles',
|
||||
'todo',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
|
||||
@@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user