#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
sqlite3backend (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 , 4 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 , 4 years ago
| Description: | modified (diff) |
|---|
comment:3 by , 4 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
follow-up: 5 comment:4 by , 4 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:5 by , 4 years ago
| Cc: | added |
|---|
Replying to Keryn Knight:
Verified on
main, problem is that the value returned from sqlite isNaNas a string, rather thanfloat('nan'), andcreate_decimal_from_floatis 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 , 4 years ago
| Owner: | removed |
|---|---|
| Status: | assigned → new |
comment:7 by , 4 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 , 4 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 , 4 years ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:11 by , 4 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Verified on
main, problem is that the value returned from sqlite isNaNas a string, rather thanfloat('nan'), andcreate_decimal_from_floatis 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.