diff --git a/rest_framework/fields.py b/rest_framework/fields.py index aaf9ef14f..18c4a601b 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 _ @@ -275,7 +276,7 @@ class Field(object): def __init__(self, read_only=False, write_only=False, required=None, default=empty, initial=empty, source=None, label=None, help_text=None, style=None, - error_messages=None, validators=None, allow_null=False): + error_messages=None, validators=None, allow_null=False, localize=False): self._creation_counter = Field._creation_counter Field._creation_counter += 1 @@ -299,6 +300,7 @@ class Field(object): self.help_text = help_text self.style = {} if style is None else style self.allow_null = allow_null + self.localize = localize if self.default_empty_html is not empty: if default is not empty: @@ -871,6 +873,9 @@ class FloatField(Field): self.validators.append(MinValueValidator(self.min_value, message=message)) def to_internal_value(self, data): + + if self.localize: + data = sanitize_separators(data) if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') @@ -880,7 +885,10 @@ class FloatField(Field): self.fail('invalid') def to_representation(self, value): - return float(value) + value = float(value) + if self.localize: + value = localize_input(value) + return value class DecimalField(Field): @@ -923,7 +931,12 @@ class DecimalField(Field): Validate that the input is a decimal number and return a Decimal instance. """ + + if self.localize: + data = sanitize_separators(data) + data = smart_text(data).strip() + if len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') @@ -988,6 +1001,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..eebba4c83 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,30 @@ class TestNoStringCoercionDecimalField(FieldValues): ) +class TestLocalizedDecimalField(TestCase): + @override_settings(USE_L10N=True, LANGUAGE_CODE='pl', LANGUAGES=(('pl', 'Polish'),)) + 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', LANGUAGES=(('pl', 'Polish'),)) + 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') + + +class TestLocalizedFloatField(TestCase): + @override_settings(USE_L10N=True, LANGUAGE_CODE='pl', LANGUAGES=(('pl', 'Polish'),)) + def test_to_internal_value(self): + field = serializers.FloatField(localize=True) + self.assertEqual(field.to_internal_value('1,1'), 1.1) + + @override_settings(USE_L10N=True, LANGUAGE_CODE='pl', LANGUAGES=(('pl', 'Polish'),)) + def test_to_representation(self): + field = serializers.FloatField(localize=True) + self.assertEqual(field.to_representation(1.1), '1,1') + + class TestNoDecimalPlaces(FieldValues): valid_inputs = { '0.12345': Decimal('0.12345'),