From 38b306426c07e1e55d7058a06c8754e41f2ecffe Mon Sep 17 00:00:00 2001 From: Kevin Georgy Date: Mon, 27 Feb 2017 13:04:43 +0100 Subject: [PATCH] Add the "localize" and "coerce_to_string" constructor options to the FloatField like it is done for the DecimalField, in order to use the user current local to serialise and deserialise the float value if required. --- rest_framework/fields.py | 19 +++++++++++++++++-- tests/test_fields.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2efe89610..ecd84df55 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -25,7 +25,9 @@ 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.formats import ( + localize_input, number_format, sanitize_separators +) from django.utils.functional import cached_property from django.utils.ipv6 import clean_ipv6_address from django.utils.timezone import utc @@ -920,6 +922,10 @@ class FloatField(Field): def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) + self.localize = kwargs.pop('localize', False) + self.coerce_to_string = kwargs.pop('coerce_to_string', None) + if self.localize: + self.coerce_to_string = True super(FloatField, self).__init__(**kwargs) if self.max_value is not None: message = self.error_messages['max_value'].format(max_value=self.max_value) @@ -930,7 +936,12 @@ class FloatField(Field): def to_internal_value(self, data): - if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: + data = smart_text(data).strip() + + if self.localize: + data = sanitize_separators(data) + + if len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') try: @@ -939,6 +950,10 @@ class FloatField(Field): self.fail('invalid') def to_representation(self, value): + if self.localize: + return number_format(value) + if self.coerce_to_string: + return str(float(value)) return float(value) diff --git a/tests/test_fields.py b/tests/test_fields.py index 16221d4cc..9fdb46b62 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -922,6 +922,36 @@ class TestMinMaxFloatField(FieldValues): field = serializers.FloatField(min_value=1, max_value=3) +class TestCoerceToStringFloatField(FieldValues): + valid_inputs = {} + invalid_inputs = {} + outputs = { + '1': str(1.0), + '0': str(0.0), + 1: str(1.0), + 0: str(0.0), + 1.5: str(1.5), + } + field = serializers.FloatField(coerce_to_string=True) + + +class TestLocalizedFloatField(TestCase): + @override_settings(USE_L10N=True, LANGUAGE_CODE='it') + def test_to_internal_value(self): + field = serializers.FloatField(localize=True) + self.assertEqual(field.to_internal_value('1,5'), 1.5) + + @override_settings(USE_L10N=True, LANGUAGE_CODE=None, DECIMAL_SEPARATOR=',', THOUSAND_SEPARATOR='\'', + NUMBER_GROUPING=3, USE_THOUSAND_SEPARATOR=True) + def test_to_representation(self): + field = serializers.FloatField(localize=True) + self.assertEqual(field.to_representation(1000.75), '1\'000,75') + + def test_localize_forces_coerce_to_string(self): + field = serializers.FloatField(localize=True) + self.assertTrue(isinstance(field.to_representation(3), six.string_types)) + + class TestDecimalField(FieldValues): """ Valid and invalid values for `DecimalField`.