Add localize keyword argument to DecimalField (#4233)

Add `localize` keyword argument for DecimalField
This commit is contained in:
kiyoqoko 2016-07-06 17:07:16 +02:00 committed by Tom Christie
parent b10de37476
commit 6338ce80ab
3 changed files with 34 additions and 2 deletions

View File

@ -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. - `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. - `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. - `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. - `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 #### Example usage

View File

@ -25,6 +25,7 @@ from django.utils.dateparse import (
) )
from django.utils.duration import duration_string from django.utils.duration import duration_string
from django.utils.encoding import is_protected_type, smart_text 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.functional import cached_property
from django.utils.ipv6 import clean_ipv6_address from django.utils.ipv6 import clean_ipv6_address
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -871,6 +872,7 @@ class FloatField(Field):
self.validators.append(MinValueValidator(self.min_value, message=message)) self.validators.append(MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data): def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length') self.fail('max_string_length')
@ -895,11 +897,15 @@ class DecimalField(Field):
} }
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. 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.max_digits = max_digits
self.decimal_places = decimal_places self.decimal_places = decimal_places
self.localize = localize
if coerce_to_string is not None: if coerce_to_string is not None:
self.coerce_to_string = coerce_to_string self.coerce_to_string = coerce_to_string
if self.localize:
self.coerce_to_string = True
self.max_value = max_value self.max_value = max_value
self.min_value = min_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 Validate that the input is a decimal number and return a Decimal
instance. instance.
""" """
data = smart_text(data).strip() data = smart_text(data).strip()
if self.localize:
data = sanitize_separators(data)
if len(data) > self.MAX_STRING_LENGTH: if len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length') self.fail('max_string_length')
@ -988,6 +999,9 @@ class DecimalField(Field):
if not coerce_to_string: if not coerce_to_string:
return quantized return quantized
if self.localize:
return localize_input(quantized)
return '{0:f}'.format(quantized) return '{0:f}'.format(quantized)
def quantize(self, value): def quantize(self, value):

View File

@ -5,6 +5,7 @@ from decimal import Decimal
import pytest import pytest
from django.http import QueryDict from django.http import QueryDict
from django.test import TestCase, override_settings
from django.utils import six, timezone from django.utils import six, timezone
import rest_framework 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): class TestNoDecimalPlaces(FieldValues):
valid_inputs = { valid_inputs = {
'0.12345': Decimal('0.12345'), '0.12345': Decimal('0.12345'),