From e21156ff17f88ae4892045580b47a47b66d45331 Mon Sep 17 00:00:00 2001 From: Clinton Blackburn Date: Sat, 4 Apr 2020 16:17:55 -0700 Subject: [PATCH] Corrected OpenAPI schema type for DecimalField The API renders DecimalFields as strings. The generated schema now renders decimal fields correctly as strings. Fixes #7253 --- rest_framework/schemas/openapi.py | 30 +++++++----------------------- tests/schemas/test_openapi.py | 8 ++------ 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 1d0ec35d5..88f4db08c 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -6,8 +6,8 @@ from operator import attrgetter from urllib.parse import urljoin from django.core.validators import ( - DecimalValidator, EmailValidator, MaxLengthValidator, MaxValueValidator, - MinLengthValidator, MinValueValidator, RegexValidator, URLValidator + EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, + MinValueValidator, RegexValidator, URLValidator ) from django.db import models from django.utils.encoding import force_str @@ -329,10 +329,10 @@ class AutoSchema(ViewInspector): 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` + elif all(isinstance(choice, (int, float)) 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): + elif all(isinstance(choice, (str, Decimal)) for choice in choices): type = 'string' else: type = None @@ -442,18 +442,11 @@ class AutoSchema(ViewInspector): content['format'] = field.protocol return content - # DecimalField has multipleOf based on decimal_places if isinstance(field, serializers.DecimalField): - content = { - 'type': 'number' + return { + 'type': 'string', + 'format': 'decimal' } - if field.decimal_places: - content['multipleOf'] = float('.' + (field.decimal_places - 1) * '0' + '1') - if field.max_whole_digits: - content['maximum'] = int(field.max_whole_digits * '9') + 1 - content['minimum'] = -content['maximum'] - self._map_min_max(field, content) - return content if isinstance(field, serializers.FloatField): content = { @@ -556,15 +549,6 @@ class AutoSchema(ViewInspector): schema['maximum'] = v.limit_value elif isinstance(v, MinValueValidator): schema['minimum'] = v.limit_value - elif isinstance(v, DecimalValidator): - if v.decimal_places: - schema['multipleOf'] = float('.' + (v.decimal_places - 1) * '0' + '1') - if v.max_digits: - digits = v.max_digits - if v.decimal_places is not None and v.decimal_places > 0: - digits -= v.decimal_places - schema['maximum'] = int(digits * '9') + 1 - schema['minimum'] = -schema['maximum'] def _get_paginator(self): pagination_class = getattr(self.view, 'pagination_class', None) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index c9f6d967e..604dd7ffa 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -830,13 +830,9 @@ class TestOperationIntrospection(TestCase): assert properties['regex']['pattern'] == r'[ABC]12{3}' assert properties['regex']['description'] == 'must have an A, B, or C followed by 1222' - assert properties['decimal1']['type'] == 'number' - assert properties['decimal1']['multipleOf'] == .01 - assert properties['decimal1']['maximum'] == 10000 - assert properties['decimal1']['minimum'] == -10000 + assert properties['decimal1'] == {'type': 'string', 'format': 'decimal'} - assert properties['decimal2']['type'] == 'number' - assert properties['decimal2']['multipleOf'] == .0001 + assert properties['decimal2'] == {'type': 'string', 'format': 'decimal'} assert properties['email']['type'] == 'string' assert properties['email']['format'] == 'email'