From 62335cfaf913b3871723e52c4f63b3b6d3a7bb04 Mon Sep 17 00:00:00 2001 From: HoodyH Date: Mon, 25 Aug 2025 22:26:40 +0200 Subject: [PATCH] feat: BinIntegerField and COERCE_BIGINT_TO_STRING setting, bigint now can have string api representation --- docs/api-guide/fields.md | 12 +++++++ docs/api-guide/settings.md | 8 +++++ rest_framework/fields.py | 30 ++++++++++++++++ rest_framework/serializers.py | 5 +-- rest_framework/settings.py | 1 + tests/test_fields.py | 62 ++++++++++++++++++++++++++++++++++ tests/test_model_serializer.py | 2 +- 7 files changed, 117 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 8278e2a2f..4c1fcb968 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -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. * `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 A floating point representation. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 2a070b77e..79eabf3ce 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -371,6 +371,14 @@ When set to `True`, the serializer `DecimalField` class will return strings inst 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 diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 847ee7b19..e8d2fd971 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -921,6 +921,36 @@ class IntegerField(Field): 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): default_error_messages = { 'invalid': _('A valid number is required.'), diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8fe284bc8..5d26a8602 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -30,7 +30,7 @@ from rest_framework.compat import ( get_referenced_base_fields_from_q, postgres_fields ) 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.utils import html, model_meta, representation from rest_framework.utils.field_mapping import ( @@ -906,7 +906,8 @@ class ModelSerializer(Serializer): """ serializer_field_mapping = { models.AutoField: IntegerField, - models.BigIntegerField: IntegerField, + models.BigAutoField: BigIntegerField, + models.BigIntegerField: BigIntegerField, models.BooleanField: BooleanField, models.CharField: CharField, models.CommaSeparatedIntegerField: CharField, diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 50e3ad40e..c6cc97c53 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -116,6 +116,7 @@ DEFAULTS = { 'COMPACT_JSON': True, 'STRICT_JSON': True, 'COERCE_DECIMAL_TO_STRING': True, + 'COERCE_BIGINT_TO_STRING': False, 'UPLOADED_FILES_USE_URL': True, # Browsable API diff --git a/tests/test_fields.py b/tests/test_fields.py index b52442a2c..61563130a 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1099,6 +1099,68 @@ class TestMinMaxIntegerField(FieldValues): 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): """ Valid and invalid values for `FloatField`. diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index eac51ae70..38e7f4423 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -171,7 +171,7 @@ class TestRegularFieldMappings(TestCase): expected = dedent(r""" TestSerializer\(\): auto_field = IntegerField\(read_only=True\) - big_integer_field = IntegerField\(.*\) + big_integer_field = BigIntegerField\(.*\) boolean_field = BooleanField\(required=False\) char_field = CharField\(max_length=100\) comma_separated_integer_field = CharField\(max_length=100, validators=\[\]\)