Skip to content

Commit f9f10e0

Browse files
HoodyHauvipy
andauthored
Bigint coerce to string (#9775)
* feat: BinIntegerField and COERCE_BIGINT_TO_STRING setting, bigint now can have string api representation * fix: wrong import location in serializers.py * feat: updated with changes requests - typo fix - value test fix - import order fix * Update tests/test_fields.py * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * refactor: changed BigIntegerField.to_representation simplified + calling super --------- Co-authored-by: Asif Saif Uddin {"Auvi":"অভি"} <[email protected]>
1 parent 1c4af77 commit f9f10e0

File tree

7 files changed

+111
-3
lines changed

7 files changed

+111
-3
lines changed

docs/api-guide/fields.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,18 @@ Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.
269269
* `max_value` Validate that the number provided is no greater than this value.
270270
* `min_value` Validate that the number provided is no less than this value.
271271

272+
## BigIntegerField
273+
274+
A biginteger representation.
275+
276+
Corresponds to `django.db.models.fields.BigIntegerField`.
277+
278+
**Signature**: `BigIntegerField(max_value=None, min_value=None, coerce_to_string=None)`
279+
280+
* `max_value` Validate that the number provided is no greater than this value.
281+
* `min_value` Validate that the number provided is no less than this value.
282+
* `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `BigInteger` objects should be returned. Defaults to the same value as the `COERCE_BIGINT_TO_STRING` settings key, which will be `False` unless overridden. If `BigInteger` objects are returned by the serializer, then the final output format will be determined by the renderer.
283+
272284
## FloatField
273285

274286
A floating point representation.

docs/api-guide/settings.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,14 @@ When set to `True`, the serializer `DecimalField` class will return strings inst
371371

372372
Default: `True`
373373

374+
#### COERCE_BIGINT_TO_STRING
375+
376+
When returning biginteger objects in API representations that do not support numbers up to 2^64, it is best to return the value as a string. This avoids the loss of precision that occurs with biginteger implementations.
377+
378+
When set to `True`, the serializer `BigIntegerField` class (by default) will return strings instead of `BigInteger` objects. When set to `False`, serializers will return `BigInteger` objects, which the default JSON encoder will return as numbers.
379+
380+
Default: `False`
381+
374382
---
375383

376384
## View names and descriptions

rest_framework/fields.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,28 @@ def to_representation(self, value):
921921
return int(value)
922922

923923

924+
class BigIntegerField(IntegerField):
925+
926+
default_error_messages = {
927+
'invalid': _('A valid biginteger is required.'),
928+
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
929+
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
930+
'max_string_length': _('String value too large.')
931+
}
932+
933+
def __init__(self, coerce_to_string=None, **kwargs):
934+
super().__init__(**kwargs)
935+
936+
if coerce_to_string is not None:
937+
self.coerce_to_string = coerce_to_string
938+
939+
def to_representation(self, value):
940+
if getattr(self, 'coerce_to_string', api_settings.COERCE_BIGINT_TO_STRING):
941+
return '' if value is None else str(value)
942+
943+
return super().to_representation(value)
944+
945+
924946
class FloatField(Field):
925947
default_error_messages = {
926948
'invalid': _('A valid number is required.'),

rest_framework/serializers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
# This helps keep the separation between model fields, form fields, and
5454
# serializer fields more explicit.
5555
from rest_framework.fields import ( # NOQA # isort:skip
56-
BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
56+
BigIntegerField, BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
5757
DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField,
5858
HiddenField, HStoreField, IPAddressField, ImageField, IntegerField, JSONField,
5959
ListField, ModelField, MultipleChoiceField, ReadOnlyField,
@@ -906,7 +906,8 @@ class ModelSerializer(Serializer):
906906
"""
907907
serializer_field_mapping = {
908908
models.AutoField: IntegerField,
909-
models.BigIntegerField: IntegerField,
909+
models.BigAutoField: BigIntegerField,
910+
models.BigIntegerField: BigIntegerField,
910911
models.BooleanField: BooleanField,
911912
models.CharField: CharField,
912913
models.CommaSeparatedIntegerField: CharField,

rest_framework/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
'COMPACT_JSON': True,
117117
'STRICT_JSON': True,
118118
'COERCE_DECIMAL_TO_STRING': True,
119+
'COERCE_BIGINT_TO_STRING': False,
119120
'UPLOADED_FILES_USE_URL': True,
120121

121122
# Browsable API

tests/test_fields.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,70 @@ class TestMinMaxIntegerField(FieldValues):
10991099
field = serializers.IntegerField(min_value=1, max_value=3)
11001100

11011101

1102+
class TestBigIntegerField(FieldValues):
1103+
"""
1104+
Valid and invalid values for `BigIntegerField`.
1105+
"""
1106+
valid_inputs = {
1107+
'1': 1,
1108+
'0': 0,
1109+
1: 1,
1110+
0: 0,
1111+
123: 123,
1112+
-123: -123,
1113+
'999999999999999999999999999': 999999999999999999999999999,
1114+
-999999999999999999999999999: -999999999999999999999999999,
1115+
1.0: 1,
1116+
0.0: 0,
1117+
'1.0': 1
1118+
}
1119+
invalid_inputs = {
1120+
0.5: ['A valid biginteger is required.'],
1121+
'abc': ['A valid biginteger is required.'],
1122+
'0.5': ['A valid biginteger is required.']
1123+
}
1124+
outputs = {
1125+
'1': 1,
1126+
'0': 0,
1127+
1: 1,
1128+
0: 0,
1129+
1.0: 1,
1130+
0.0: 0,
1131+
'999999999999999999999999999': 999999999999999999999999999,
1132+
-999999999999999999999999999: -999999999999999999999999999
1133+
}
1134+
field = serializers.BigIntegerField()
1135+
1136+
1137+
class TestMinMaxBigIntegerField(FieldValues):
1138+
"""
1139+
Valid and invalid values for `BigIntegerField` with min and max limits.
1140+
"""
1141+
valid_inputs = {
1142+
'1': 1,
1143+
'3': 3,
1144+
1: 1,
1145+
3: 3,
1146+
}
1147+
invalid_inputs = {
1148+
0: ['Ensure this value is greater than or equal to 1.'],
1149+
4: ['Ensure this value is less than or equal to 3.'],
1150+
'0': ['Ensure this value is greater than or equal to 1.'],
1151+
'4': ['Ensure this value is less than or equal to 3.'],
1152+
}
1153+
outputs = {}
1154+
field = serializers.BigIntegerField(min_value=1, max_value=3)
1155+
1156+
1157+
class TestCoercionBigIntegerField(TestCase):
1158+
1159+
def test_force_coerce_to_string(self):
1160+
field = serializers.BigIntegerField(coerce_to_string=True)
1161+
value = field.to_representation(1)
1162+
assert isinstance(value, str)
1163+
assert value == "1"
1164+
1165+
11021166
class TestFloatField(FieldValues):
11031167
"""
11041168
Valid and invalid values for `FloatField`.

tests/test_model_serializer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class Meta:
171171
expected = dedent(r"""
172172
TestSerializer\(\):
173173
auto_field = IntegerField\(read_only=True\)
174-
big_integer_field = IntegerField\(.*\)
174+
big_integer_field = BigIntegerField\(.*\)
175175
boolean_field = BooleanField\(required=False\)
176176
char_field = CharField\(max_length=100\)
177177
comma_separated_integer_field = CharField\(max_length=100, validators=\[<django.core.validators.RegexValidator object>\]\)

0 commit comments

Comments
 (0)