Replying to Mariusz Felisiak:
Replying to Claude Paroz:
Django offers both model et form validation. If you insert values without using either one, you are on your own and responsible for what you insert in the database. I dont' think Django should do more here. To be confirmed.
Yes, we have both model and form validation for NaN
s. I don't think we should do more than that.
What made me report as a bug is that in db.models.fields.DecimalField
, method to_python
, there are two shields against invalid values, one of them explicit against nan
. Both "nan" and "inf" can be weeded out early if the check is if not math.isfinite
instead of if math.isnan
, as it's actually implemented in the form validation. Currently float("inf")
fails to store in the DB as "Infinite" because it can't be quantize
'd at db/backends/utils.py::format_number
(decimal.InvalidOperation
), while "nan" can.
Notice that I'm using the Model validation, if I'm not mistaken and it refers to creating objects with MyModel.objects.create
. E.g. this works and happily stores "NaN" in the DB:
obj = MyModel.objects.create(value="nan")
But this fails with a ValidationError
raised from DecimalField.to_python
:
obj = MyModel.objects.create(value="invalid")
django.core.exceptions.ValidationError: ['“invalid” value must be a decimal number.']
And this fails with decimal.InvalidOperation
:
obj = MyModel.objects.create(value="inf")
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]
IMHO it would be better interface to fail before saving, than to store the value and then fail on retrieving. The form validation does this, creating a valid Decimal
even if the value entered are the strings "nan" or "inf" (in to_python()
), and then check that only Decimal.is_finite()
are valid values (in validate()
).
This would be my refactor to DecimalField.to_python
, without adding any code, to consistently raise a ValidationError for all "inf", "nan" and invalid inputs, instead of one for each.
1703 def to_python(self, value):
1704 if value is None:
1705 return value
1706 if isinstance(value, float):
1707 result = self.context.create_decimal_from_float(value)
1708 try:
1709 result = decimal.Decimal(value)
1710 except (decimal.InvalidOperation, TypeError, ValueError): # Catches everything except "nan" and "inf"
1711 raise exceptions.ValidationError(
1712 self.error_messages["invalid"],
1713 code="invalid",
1714 params={"value": value},
1715 )
1716 if not result.is_finite(): # Catches both "nan" and "inf"
1717 raise exceptions.ValidationError(
1718 self.error_messages["invalid"],
1719 code="invalid",
1720 params={"value": value},
1721 )
1722 return result
Thanks for your time!