Opened 8 years ago
Closed 8 years ago
#26963 closed Cleanup/optimization (invalid)
Improve error message when trying to insert a value that overflows DecimalField
Reported by: | Floris den Hengst | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | DecimalField, ValidationError, quantize |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
DecimalField accepts max_digit
parameter that specifies the amount of digit positions used by the DecimalField
.
Consider the following model that allows for only one digit before the decimal separator:
from django.db.models import Model class MyModel(Model): my_field = DecimalField(max_digits=2, decimal_places=1)
A value of Decimal("10.1")
is not supported, as it requires 3 decimal places, however its input is accepted until the value is being inserted in the database.
This line in django.db.backends.utils.format_number
fails when any insertion or update is attempted (e.g. using save()
, bulk_create
etc. ), because quantize
raises an InvalidOperation
when there are not enough positions to fit the rounded value (according to Decimal.quantize
docs).
The error message is not very informative (0).
This commit contains a test that triggers the error. Note that overflow might also happen because of rounding during the quantization (e.g. values of 9.99999 could result in 10.0 during quantization, depending on the Context used).
For a copy of the commit see (1).
(0) Example error message
Traceback (most recent call last): File "/path_to_project/file.py", line 123, in my_failing_function overflowing_instance.save() File "/path_to_django/db/models/base.py", line 796, in save force_update=force_update, update_fields=update_fields) File "/path_to_django/db/models/base.py", line 824, in save_base updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields) File "/path_to_django/db/models/base.py", line 908, in _save_table result = self._do_insert(cls._base_manager, using, fields, update_pk, raw) File "/path_to_django/db/models/base.py", line 947, in _do_insert using=using, raw=raw) File "/path_to_django/db/models/manager.py", line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/path_to_django/db/models/query.py", line 1046, in _insert return query.get_compiler(using=using).execute_sql(return_id) File "/path_to_django/db/models/sql/compiler.py", line 1053, in execute_sql for sql, params in self.as_sql(): File "/path_to_django/db/models/sql/compiler.py", line 1006, in as_sql for obj in self.query.objs File "/path_to_django/db/models/sql/compiler.py", line 1006, in <listcomp> for obj in self.query.objs File "/path_to_django/db/models/sql/compiler.py", line 1005, in <listcomp> [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields] File "/path_to_django/db/models/sql/compiler.py", line 945, in prepare_value value = field.get_db_prep_save(value, connection=self.connection) File "/path_to_django/db/models/fields/__init__.py", line 1587, in get_db_prep_save return connection.ops.adapt_decimalfield_value(self.to_python(value), self.max_digits, self.decimal_places) File "/path_to_django/db/backends/base/operations.py", line 495, in adapt_decimalfield_value return utils.format_number(value, max_digits, decimal_places) File "/path_to_django/db/backends/utils.py", line 204, in format_number value = value.quantize(decimal.Decimal(".1") ** decimal_places, context=context) decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]
(1)Test that triggers the error (add to django.tests.model_fields.test_decimalfield.DecimalFieldTests
)
def test_save_with_max_digits_overflow(self): """ Ensure overflowing decimals yield a meaningful error. """ overflowing_value = Decimal(10 ** 6) expected_message = "Not enough digit positions in field 'd' to represent {}".format(overflowing_value) # some meaningful error message overflowing_instance = Foo(a='a', d=overflowing_value) with self.assertRaisesMessage(ValidationError, # some meaningful error expected_message): overflowing_instance.save()
If you want a nice error message, you need to run validation before calling save:
overflowing_instance.full_clean()
.Doing so in your test raises
ValidationError: {'d': ['Ensure that there are no more than 5 digits in total.']}
.