salopensource / sal

Modular reporting for Endpoints
Apache License 2.0
212 stars 64 forks source link

Fix KeyError raised when trying to sort application inventory by install count #458

Closed w0de closed 11 months ago

w0de commented 1 year ago

This fixes an exception raised when trying to sort a machine group's application inventory by install count.

Changes:

To reproduce exception:

  1. Populate at least one device w/ an application inventory in a Sal instance. At least one application should have an install count > 1 (reviewer's mac certainly has multiple Pythons).
  2. Navigate to /inventory/machine/1/ (machine group 1's app inventory).
  3. Attempt to sort by install count. Expected result:

https://github.com/salopensource/sal/assets/20032435/c1036385-c0ee-4fad-8103-54fc42b26c2b

Datatables error 7 == server returned a 500. There's only one line in the root traceback:

[24/Oct/2023 01:55:04] ERROR [django.request:224] Internal Server Error: /inventory/machine/1/
Traceback (most recent call last):
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/db/models/options.py", line 575, in get_field
    return self.fields_map[field_name]
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 'None'

But there's a further useful exception during handling:

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/sal/decorators.py", line 70, in decorator
    return function(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/datatableview/views/base.py", line 29, in dispatch
    datatable.configure()
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/datatableview/datatables.py", line 292, in configure
    self.config = self.normalize_config(self._meta.__dict__, self.query_config)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/datatableview/datatables.py", line 338, in normalize_config
    self._ordering_columns = self.ensure_ordering_columns(config["ordering"])
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/datatableview/datatables.py", line 427, in ensure_ordering_columns
    field = resolve_orm_path(self.model, name)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/datatableview/utils.py", line 64, in resolve_orm_path
    field = endpoint_model._meta.get_field(bits[-1])
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wode/src/sal.w0de/.venv/lib/python3.11/site-packages/django/db/models/options.py", line 577, in get_field
    raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))
django.core.exceptions.FieldDoesNotExist: Application has no field named 'None'
[24/Oct/2023 01:55:04] ERROR [django.server:157] "GET /inventory/machine/1/?draw=2&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=name&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=bundle-id&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=bundle-name&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=3&columns%5B3%5D%5Bname%5D=install-count&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=3&order%5B0%5D%5Bdir%5D=asc&start=0&length=20&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1698108890069 HTTP/1.1" 500 137349

I believe (not sure) this occurs because the 'install_count' column does not have a defined db source, but is not properly configured as a non-db sourced column. The column is misconfigured due to its idiosyncratic monkeypatch definition.

This causes django-datatable-view to attempt accessing field None on the Application model when asked to sort the column's data.