feat: BinIntegerField and COERCE_BIGINT_TO_STRING setting, bigint now can have string api representation

This commit is contained in:
HoodyH 2025-08-25 22:26:40 +02:00
parent 1472848501
commit 62335cfaf9
7 changed files with 117 additions and 3 deletions

View File

@ -269,6 +269,18 @@ Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.
* `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.
## BigIntegerField
An biginteger representation.
Corresponds to `django.db.models.fields.BigIntegerField`.
**Signature**: `BigIntegerField(max_value=None, min_value=None, coerce_to_string=None)`
* `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.
* `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 `True` unless overridden. If `BigInterger` objects are returned by the serializer, then the final output format will be determined by the renderer.
## FloatField ## FloatField
A floating point representation. A floating point representation.

View File

@ -371,6 +371,14 @@ When set to `True`, the serializer `DecimalField` class will return strings inst
Default: `True` Default: `True`
#### COERCE_BIGINT_TO_STRING
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.
When set to `True`, the serializer `BigIntegerField` class 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.
Default: `False`
--- ---
## View names and descriptions ## View names and descriptions

View File

@ -921,6 +921,36 @@ class IntegerField(Field):
return int(value) return int(value)
class BigIntegerField(IntegerField):
default_error_messages = {
'invalid': _('A valid biginteger is required.'),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
'max_string_length': _('String value too large.')
}
def __init__(self, coerce_to_string=None, **kwargs):
super().__init__(**kwargs)
if coerce_to_string is not None:
self.coerce_to_string = coerce_to_string
def to_representation(self, value):
coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_BIGINT_TO_STRING)
if value is None:
if coerce_to_string:
return ''
else:
return None
if coerce_to_string:
return str(value)
else:
return int(value)
class FloatField(Field): class FloatField(Field):
default_error_messages = { default_error_messages = {
'invalid': _('A valid number is required.'), 'invalid': _('A valid number is required.'),

View File

@ -30,7 +30,7 @@ from rest_framework.compat import (
get_referenced_base_fields_from_q, postgres_fields get_referenced_base_fields_from_q, postgres_fields
) )
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import get_error_detail from rest_framework.fields import get_error_detail, BigIntegerField
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation from rest_framework.utils import html, model_meta, representation
from rest_framework.utils.field_mapping import ( from rest_framework.utils.field_mapping import (
@ -906,7 +906,8 @@ class ModelSerializer(Serializer):
""" """
serializer_field_mapping = { serializer_field_mapping = {
models.AutoField: IntegerField, models.AutoField: IntegerField,
models.BigIntegerField: IntegerField, models.BigAutoField: BigIntegerField,
models.BigIntegerField: BigIntegerField,
models.BooleanField: BooleanField, models.BooleanField: BooleanField,
models.CharField: CharField, models.CharField: CharField,
models.CommaSeparatedIntegerField: CharField, models.CommaSeparatedIntegerField: CharField,

View File

@ -116,6 +116,7 @@ DEFAULTS = {
'COMPACT_JSON': True, 'COMPACT_JSON': True,
'STRICT_JSON': True, 'STRICT_JSON': True,
'COERCE_DECIMAL_TO_STRING': True, 'COERCE_DECIMAL_TO_STRING': True,
'COERCE_BIGINT_TO_STRING': False,
'UPLOADED_FILES_USE_URL': True, 'UPLOADED_FILES_USE_URL': True,
# Browsable API # Browsable API

View File

@ -1099,6 +1099,68 @@ class TestMinMaxIntegerField(FieldValues):
field = serializers.IntegerField(min_value=1, max_value=3) field = serializers.IntegerField(min_value=1, max_value=3)
class TestBigIntegerField(FieldValues):
"""
Valid and invalid values for `BigIntegerField`.
"""
valid_inputs = {
'1': 1,
'0': 0,
1: 1,
0: 0,
123: 123,
-123: -123,
'999999999999999999999999999': 999999999999999999999999999,
-999999999999999999999999999: -999999999999999999999999999,
1.0: 1,
0.0: 0,
'1.0': 1
}
invalid_inputs = {
0.5: ['A valid biginteger is required.'],
'abc': ['A valid biginteger is required.'],
'0.5': ['A valid biginteger is required.']
}
outputs = {
'1': 1,
'0': 0,
1: 1,
0: 0,
1.0: 1,
0.0: 0,
'999999999999999999999999999': 999999999999999999999999999,
-999999999999999999999999999: -999999999999999999999999999
}
field = serializers.BigIntegerField()
class TestMinMaxBigIntegerField(FieldValues):
"""
Valid and invalid values for `IntegerField` with min and max limits.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
}
invalid_inputs = {
0: ['Ensure this value is greater than or equal to 1.'],
4: ['Ensure this value is less than or equal to 3.'],
'0': ['Ensure this value is greater than or equal to 1.'],
'4': ['Ensure this value is less than or equal to 3.'],
}
outputs = {}
field = serializers.BigIntegerField(min_value=1, max_value=3)
class TestCoercionBigIntegerField(TestCase):
def test_force_coerce_to_string(self):
field = serializers.BigIntegerField(coerce_to_string=True)
assert isinstance(field.to_representation(int('1')), str)
class TestFloatField(FieldValues): class TestFloatField(FieldValues):
""" """
Valid and invalid values for `FloatField`. Valid and invalid values for `FloatField`.

View File

@ -171,7 +171,7 @@ class TestRegularFieldMappings(TestCase):
expected = dedent(r""" expected = dedent(r"""
TestSerializer\(\): TestSerializer\(\):
auto_field = IntegerField\(read_only=True\) auto_field = IntegerField\(read_only=True\)
big_integer_field = IntegerField\(.*\) big_integer_field = BigIntegerField\(.*\)
boolean_field = BooleanField\(required=False\) boolean_field = BooleanField\(required=False\)
char_field = CharField\(max_length=100\) char_field = CharField\(max_length=100\)
comma_separated_integer_field = CharField\(max_length=100, validators=\[<django.core.validators.RegexValidator object>\]\) comma_separated_integer_field = CharField\(max_length=100, validators=\[<django.core.validators.RegexValidator object>\]\)