Schemas: Improved decimal handling when mapping ChoiceField. (#7264)

This commit is contained in:
Dhaval Mehta 2020-04-09 22:48:00 +05:30 committed by GitHub
parent 603aac7db1
commit 1872bde462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 30 additions and 18 deletions

View File

@ -330,30 +330,29 @@ class AutoSchema(ViewInspector):
def _map_choicefield(self, field): def _map_choicefield(self, field):
choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates
if all(isinstance(choice, bool) for choice in choices):
type = 'boolean'
elif all(isinstance(choice, int) for choice in choices):
type = 'integer'
elif all(isinstance(choice, (int, float, Decimal)) for choice in choices): # `number` includes `integer`
# Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21
type = 'number'
elif all(isinstance(choice, str) for choice in choices):
type = 'string'
else:
type = None
mapping = { mapping = {
# The value of `enum` keyword MUST be an array and SHOULD be unique. # The value of `enum` keyword MUST be an array and SHOULD be unique.
# Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.20 # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.20
'enum': choices 'enum': choices
} }
# If We figured out `type` then and only then we should set it. It must be a string. if all(isinstance(choice, bool) for choice in choices):
# Ref: https://swagger.io/docs/specification/data-models/data-types/#mixed-type mapping['type'] = 'boolean'
# It is optional but it can not be null. elif all(isinstance(choice, int) for choice in choices):
mapping['type'] = 'integer'
elif all(isinstance(choice, Decimal) for choice in choices):
mapping['format'] = 'decimal'
if api_settings.COERCE_DECIMAL_TO_STRING:
mapping['enum'] = [str(choice) for choice in mapping['enum']]
mapping['type'] = 'string'
else:
mapping['type'] = 'number'
elif all(isinstance(choice, (int, float, Decimal)) for choice in choices): # `number` includes `integer`
# Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21 # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21
if type: mapping['type'] = 'number'
mapping['type'] = type elif all(isinstance(choice, str) for choice in choices):
mapping['type'] = 'string'
return mapping return mapping
def _map_field(self, field): def _map_field(self, field):

View File

@ -1,5 +1,6 @@
import uuid import uuid
import warnings import warnings
from decimal import Decimal
import pytest import pytest
from django.conf.urls import url from django.conf.urls import url
@ -78,6 +79,9 @@ class TestFieldMapping(TestCase):
(1, 'One'), (2, 'Two'), (3, 'Three'), (2, 'Two'), (3, 'Three'), (1, 'One'), (1, 'One'), (2, 'Two'), (3, 'Three'), (2, 'Two'), (3, 'Three'), (1, 'One'),
])), ])),
{'items': {'enum': [1, 2, 3], 'type': 'integer'}, 'type': 'array'}), {'items': {'enum': [1, 2, 3], 'type': 'integer'}, 'type': 'array'}),
(serializers.ListField(child=
serializers.ChoiceField(choices=[(Decimal('1.111'), 'one'), (Decimal('2.222'), 'two')])),
{'items': {'enum': ['1.111', '2.222'], 'type': 'string', 'format': 'decimal'}, 'type': 'array'}),
(serializers.IntegerField(min_value=2147483648), (serializers.IntegerField(min_value=2147483648),
{'type': 'integer', 'minimum': 2147483648, 'format': 'int64'}), {'type': 'integer', 'minimum': 2147483648, 'format': 'int64'}),
] ]
@ -85,6 +89,15 @@ class TestFieldMapping(TestCase):
with self.subTest(field=field): with self.subTest(field=field):
assert inspector._map_field(field) == mapping assert inspector._map_field(field) == mapping
@override_settings(REST_FRAMEWORK={'COERCE_DECIMAL_TO_STRING': False})
def test_decimal_schema_for_choice_field(self):
inspector = AutoSchema()
field = serializers.ListField(
child=serializers.ChoiceField(choices=[(Decimal('1.111'), 'one'), (Decimal('2.222'), 'two')]))
mapping = {'items': {'enum': [Decimal('1.111'), Decimal('2.222')], 'type': 'number'}, 'type': 'array'}
assert inspector._map_field(field) == mapping
def test_lazy_string_field(self): def test_lazy_string_field(self):
class ItemSerializer(serializers.Serializer): class ItemSerializer(serializers.Serializer):
text = serializers.CharField(help_text=_('lazy string')) text = serializers.CharField(help_text=_('lazy string'))