From 6338ce80aba2f12b6b7931e2e220d7447e36811f Mon Sep 17 00:00:00 2001 From: kiyoqoko Date: Wed, 6 Jul 2016 17:07:16 +0200 Subject: [PATCH] Add localize keyword argument to `DecimalField` (#4233) Add `localize` keyword argument for DecimalField --- docs/api-guide/fields.md | 3 ++- rest_framework/fields.py | 16 +++++++++++++++- tests/test_fields.py | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b58d02898..f95608afb 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -261,9 +261,10 @@ Corresponds to `django.db.models.fields.DecimalField`. - `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places. - `decimal_places` The number of decimal places to store with the number. -- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. +- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`. - `max_value` Validate that the number provided is no greater than this value. - `min_value` Validate that the number provided is no less than this value. +- `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file. #### Example usage diff --git a/rest_framework/fields.py b/rest_framework/fields.py index aaf9ef14f..46d7ed09b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -25,6 +25,7 @@ from django.utils.dateparse import ( ) from django.utils.duration import duration_string from django.utils.encoding import is_protected_type, smart_text +from django.utils.formats import localize_input, sanitize_separators from django.utils.functional import cached_property from django.utils.ipv6 import clean_ipv6_address from django.utils.translation import ugettext_lazy as _ @@ -871,6 +872,7 @@ class FloatField(Field): self.validators.append(MinValueValidator(self.min_value, message=message)) def to_internal_value(self, data): + if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') @@ -895,11 +897,15 @@ class DecimalField(Field): } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. - def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, **kwargs): + def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, + localize=False, **kwargs): self.max_digits = max_digits self.decimal_places = decimal_places + self.localize = localize if coerce_to_string is not None: self.coerce_to_string = coerce_to_string + if self.localize: + self.coerce_to_string = True self.max_value = max_value self.min_value = min_value @@ -923,7 +929,12 @@ class DecimalField(Field): Validate that the input is a decimal number and return a Decimal instance. """ + data = smart_text(data).strip() + + if self.localize: + data = sanitize_separators(data) + if len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') @@ -988,6 +999,9 @@ class DecimalField(Field): if not coerce_to_string: return quantized + if self.localize: + return localize_input(quantized) + return '{0:f}'.format(quantized) def quantize(self, value): diff --git a/tests/test_fields.py b/tests/test_fields.py index 0dbdcad06..1149cc4b3 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -5,6 +5,7 @@ from decimal import Decimal import pytest from django.http import QueryDict +from django.test import TestCase, override_settings from django.utils import six, timezone import rest_framework @@ -894,6 +895,22 @@ class TestNoStringCoercionDecimalField(FieldValues): ) +class TestLocalizedDecimalField(TestCase): + @override_settings(USE_L10N=True, LANGUAGE_CODE='pl') + def test_to_internal_value(self): + field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True) + self.assertEqual(field.to_internal_value('1,1'), Decimal('1.1')) + + @override_settings(USE_L10N=True, LANGUAGE_CODE='pl') + def test_to_representation(self): + field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True) + self.assertEqual(field.to_representation(Decimal('1.1')), '1,1') + + def test_localize_forces_coerce_to_string(self): + field = serializers.DecimalField(max_digits=2, decimal_places=1, coerce_to_string=False, localize=True) + self.assertTrue(isinstance(field.to_representation(Decimal('1.1')), six.string_types)) + + class TestNoDecimalPlaces(FieldValues): valid_inputs = { '0.12345': Decimal('0.12345'),