Skip to content

Commit 52da527

Browse files
kgeorgyCarlton Gibson
authored andcommitted
Add localization support to the FloatField (#4925)
* 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. * * Restore imports in order to make isort test pass * Do string sanitization only if input is a string * Fix string identification in python2.7 * Add documentation for the coerce_to_string and localize FloatField parameters * Removes unneeded lines, and set default value for coerce_to_string to be False insteed of None * Update FloatField signature docs
1 parent 313c5ca commit 52da527

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

docs/api-guide/fields.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,12 @@ A floating point representation.
250250

251251
Corresponds to `django.db.models.fields.FloatField`.
252252

253-
**Signature**: `FloatField(max_value=None, min_value=None)`
253+
**Signature**: `FloatField(coerce_to_string=False, max_value=None, min_value=None, localize=False)`
254254

255+
- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `float` objects should be returned. Defaults to `False`.
255256
- `max_value` Validate that the number provided is no greater than this value.
256257
- `min_value` Validate that the number provided is no less than this value.
258+
- `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.
257259

258260
## DecimalField
259261

rest_framework/fields.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
)
2424
from django.utils.duration import duration_string
2525
from django.utils.encoding import is_protected_type, smart_text
26-
from django.utils.formats import localize_input, sanitize_separators
26+
from django.utils.formats import (
27+
localize_input, number_format, sanitize_separators
28+
)
2729
from django.utils.functional import lazy
2830
from django.utils.ipv6 import clean_ipv6_address
2931
from django.utils.timezone import utc
@@ -954,6 +956,8 @@ class FloatField(Field):
954956
def __init__(self, **kwargs):
955957
self.max_value = kwargs.pop('max_value', None)
956958
self.min_value = kwargs.pop('min_value', None)
959+
self.localize = kwargs.pop('localize', False)
960+
self.coerce_to_string = kwargs.pop('coerce_to_string', False)
957961
super(FloatField, self).__init__(**kwargs)
958962
if self.max_value is not None:
959963
message = lazy(
@@ -970,15 +974,26 @@ def __init__(self, **kwargs):
970974

971975
def to_internal_value(self, data):
972976

973-
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
974-
self.fail('max_string_length')
977+
if isinstance(data, six.string_types):
978+
979+
data = smart_text(data).strip()
980+
981+
if len(data) > self.MAX_STRING_LENGTH:
982+
self.fail('max_string_length')
983+
984+
if self.localize:
985+
data = sanitize_separators(data)
975986

976987
try:
977988
return float(data)
978989
except (TypeError, ValueError):
979990
self.fail('invalid')
980991

981992
def to_representation(self, value):
993+
if self.localize:
994+
return number_format(value)
995+
if self.coerce_to_string:
996+
return str(float(value))
982997
return float(value)
983998

984999

tests/test_fields.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,36 @@ class TestMinMaxFloatField(FieldValues):
952952
field = serializers.FloatField(min_value=1, max_value=3)
953953

954954

955+
class TestCoerceToStringFloatField(FieldValues):
956+
valid_inputs = {}
957+
invalid_inputs = {}
958+
outputs = {
959+
'1': str(1.0),
960+
'0': str(0.0),
961+
1: str(1.0),
962+
0: str(0.0),
963+
1.5: str(1.5),
964+
}
965+
field = serializers.FloatField(coerce_to_string=True)
966+
967+
968+
class TestLocalizedFloatField(TestCase):
969+
@override_settings(USE_L10N=True, LANGUAGE_CODE='it')
970+
def test_to_internal_value(self):
971+
field = serializers.FloatField(localize=True)
972+
self.assertEqual(field.to_internal_value('1,5'), 1.5)
973+
974+
@override_settings(USE_L10N=True, LANGUAGE_CODE=None, DECIMAL_SEPARATOR=',', THOUSAND_SEPARATOR='\'',
975+
NUMBER_GROUPING=3, USE_THOUSAND_SEPARATOR=True)
976+
def test_to_representation(self):
977+
field = serializers.FloatField(localize=True)
978+
self.assertEqual(field.to_representation(1000.75), '1\'000,75')
979+
980+
def test_localize_forces_coerce_to_string(self):
981+
field = serializers.FloatField(localize=True)
982+
self.assertTrue(isinstance(field.to_representation(3), six.string_types))
983+
984+
955985
class TestDecimalField(FieldValues):
956986
"""
957987
Valid and invalid values for `DecimalField`.

0 commit comments

Comments
 (0)