#33033 closed Bug (fixed)
NaN can be stored in DecimalField but cannot be retrieved
Reported by: | dennisvang | Owned by: | Chinmoy |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 3.2 |
Severity: | Normal | Keywords: | |
Cc: | Carlton Gibson | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Description
If, for whatever reason, a NaN
value (either float('nan')
, math.nan
, or numpy.nan
) is stored in a DecimalField
using sqlite3
, the object cannot be retrieved from the database.
Attempts to do so will raise TypeError: argument must be int or float
This issue also breaks e.g. the admin changelist view.
Steps to reproduce
- Create a brand new project using python 3.8.10 and django 3.2.6 with the default
sqlite3
backend (optionally with numpy 1.21.2).
- Create a model with a
DecimalField
:
class MyModel(models.Model): value = models.DecimalField(max_digits=10, decimal_places=5)
- Programmatically create a model instance with
value=float('nan')
(ormath.nan
, ornumpy.nan
), then try to retrieve the object from the database (or refresh from database).
obj = MyModel.objects.create(value=float('nan')) # the following raises a "TypeError: argument must be int or float" obj.refresh_from_db()
- Visiting the admin change view (or changelist view) for the model will also raise the error.
Traceback:
Internal Server Error: /nanbug/mymodel/1/change/ Traceback (most recent call last): File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/contrib/admin/options.py", line 616, in wrapper return self.admin_site.admin_view(view)(*args, **kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view response = view_func(request, *args, **kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func response = view_func(request, *args, **kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 232, in inner return view(request, *args, **kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1660, in change_view return self.changeform_view(request, object_id, form_url, extra_context) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper return bound_method(*args, **kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view response = view_func(request, *args, **kwargs) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1540, in changeform_view return self._changeform_view(request, object_id, form_url, extra_context) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1561, in _changeform_view obj = self.get_object(request, unquote(object_id), to_field) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/contrib/admin/options.py", line 763, in get_object return queryset.get(**{field.name: object_id}) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/db/models/query.py", line 431, in get num = len(clone) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__ self._fetch_all() File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all self._result_cache = list(self._iterable_class(self)) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/db/models/query.py", line 68, in __iter__ for row in compiler.results_iter(results): File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1122, in apply_converters value = converter(value, expression, connection) File "/home/.../.local/share/virtualenvs/howto-GW7qAAiJ/lib/python3.8/site-packages/django/db/backends/sqlite3/operations.py", line 313, in converter return create_decimal(value).quantize(quantize_value, context=expression.output_field.context) TypeError: argument must be int or float
Change History (13)
comment:1 by , 3 years ago
Description: | modified (diff) |
---|---|
Summary: | numpy.nan can be stored in DecimalField but cannot be retrieved → NaN can be stored in DecimalField but cannot be retrieved |
comment:2 by , 3 years ago
Description: | modified (diff) |
---|
comment:3 by , 3 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
follow-up: 5 comment:4 by , 3 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:5 by , 3 years ago
Cc: | added |
---|
Replying to Keryn Knight:
Verified on
main
, problem is that the value returned from sqlite isNaN
as a string, rather thanfloat('nan')
, andcreate_decimal_from_float
is strict about it's accepted types. I've not verified whether it would affect any other backends, but presuming it doesn't, it shouldn't be too problematic to fix the converter given it'll only apply for the sqlite3 backend.
Storing NaN
also doesn't work on MySQL and Oracle. Moreover DecimalValidator
raises ValidationError
on it. I don't think it's supported, I'd adjust DecimalField.to_python()
to raise an exception in this case.
comment:6 by , 3 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:7 by , 3 years ago
Unfortunately I don't have a postgresql database available at the moment to test the minimal example, but using postgresql in production, NaN values caused errors at a later stage, viz. during template rendering. For example:
TypeError: bad operand type for abs(): 'str'
in
... django/utils/numberformat.py", line 44, in format if abs(exponent) + len(digits) > 200:
comment:8 by , 3 years ago
NaN values caused errors at a later stage, viz. during template rendering. For example: ...
Exactly, as far as I'm aware it has never been supported. That's why I'd adjust DecimalField.to_python()
to raise an exception in this case.
comment:9 by , 3 years ago
Owner: | set to |
---|---|
Status: | new → assigned |
comment:11 by , 3 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
Verified on
main
, problem is that the value returned from sqlite isNaN
as a string, rather thanfloat('nan')
, andcreate_decimal_from_float
is strict about it's accepted types. I've not verified whether it would affect any other backends, but presuming it doesn't, it shouldn't be too problematic to fix the converter given it'll only apply for the sqlite3 backend.