Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

table.html throws an 'NoneType' object has no attribute '_base_manager' when a plugin is disabled #11335

Closed
PieterL75 opened this issue Dec 29, 2022 · 7 comments · Fixed by #11709
Assignees
Labels
severity: medium Results in substantial degraded or broken functionality for specfic workflows status: accepted This issue has been accepted for implementation type: bug A confirmed report of unexpected behavior in the application

Comments

@PieterL75
Copy link
Contributor

NetBox version

v3.3.10

Python version

3.8

Steps to Reproduce

  1. install/enable a plugin
  2. perform some actions, so that the changelog contains log entries related to that plugin in the top 10
  3. disable the plugin (remove it from PLUGINS
  4. restart netbox
  5. open the homepage

Expected Behavior

Homepage is shown

Observed Behavior

An error is thrown referencing the changelog object of the disabled plugin

Traceback (most recent call last):
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 142, in dispatch
    return handler(request, *args, **kwargs)
  File "/opt/netbox/netbox/netbox/views/misc.py", line 130, in get
    return render(request, self.template_name, {
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/shortcuts.py", line 24, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/backends/django.py", line 62, in render
    return self.template.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/test/utils.py", line 111, in instrumented_test_render
    return self.nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/test/utils.py", line 111, in instrumented_test_render
    return self.nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/test/utils.py", line 111, in instrumented_test_render
    return self.nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/defaulttags.py", line 322, in render
    return nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django_tables2/templatetags/django_tables2.py", line 167, in render
    return template.render(context={"table": table}, request=request)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/backends/django.py", line 62, in render
    return self.template.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/test/utils.py", line 111, in instrumented_test_render
    return self.nodelist.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/template/defaulttags.py", line 321, in render
    if match:
  File "/opt/netbox/venv/lib/python3.9/site-packages/django_tables2/rows.py", line 325, in __len__
    length = len(self.data)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django_tables2/data.py", line 156, in __len__
    self._length = len(self.data)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/query.py", line 376, in __len__
    self._fetch_all()
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/query.py", line 1869, in _fetch_all
    self._prefetch_related_objects()
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/query.py", line 1258, in _prefetch_related_objects
    prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/query.py", line 2298, in prefetch_related_objects
    obj_list, additional_lookups = prefetch_one_level(
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/query.py", line 2440, in prefetch_one_level
    ) = prefetcher.get_prefetch_queryset(instances, lookup.get_current_queryset(level))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/contrib/contenttypes/fields.py", line 199, in get_prefetch_queryset
    ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys))
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/contrib/contenttypes/models.py", line 185, in get_all_objects_for_this_type
    return self.model_class()._base_manager.using(self._state.db).filter(**kwargs)
AttributeError: 'NoneType' object has no attribute '_base_manager'

I ran again this, when I had to disable a plugin after upgrading to v3.4, as the plugin was not yet compatible.
It would be great if the changelog is not that sensitive to disabled/removed plugins. I see this issue surfaces regularly and on different places where the changelog is shown

@PieterL75 PieterL75 added the type: bug A confirmed report of unexpected behavior in the application label Dec 29, 2022
@PieterL75 PieterL75 changed the title bae.html throws an 'NoneType' object has no attribute '_base_manager' when a plugin is disabled base.html throws an 'NoneType' object has no attribute '_base_manager' when a plugin is disabled Dec 29, 2022
@PieterL75 PieterL75 changed the title base.html throws an 'NoneType' object has no attribute '_base_manager' when a plugin is disabled table.html throws an 'NoneType' object has no attribute '_base_manager' when a plugin is disabled Dec 29, 2022
@jeremystretch
Copy link
Member

Thanks for capturing this. I've run into it a few times during the course of development. It's going to be a bit tricky to solve for I'm afraid, since removing the plugin without clearing out its associated content types leaves the database in an invalid state. #10696 might help with this.

@jeremystretch jeremystretch added the status: under review Further discussion is needed to determine this issue's scope and/or implementation label Dec 29, 2022
@PieterL75
Copy link
Contributor Author

I understand that with the uninstallation of a plugin, that you want to remove all stale/db data.
But this is the case of temporary disabling a plugin, with the purpose to retain the data in the database.
Thx for looking into it !

@jeremystretch
Copy link
Member

The root issue is that Django (understandably) assumes that any entry in the content types table will map to a valid Python model, and there doesn't appear to be any obvious hook on generic foreign keys for handling the condition where the model does not exist. So it may take some digging to devise a workable solution.

@abhi1693
Copy link
Member

I'm not sure if this will be accepted as an answer. However, I feel when a plugin is removed it can be assumed the data is no longer needed. So, if ObjectChange allows CASCADE deletion for changed_object_type and related_object_type, remove_stale_contenttypes command can help cleanup the tables.

Here is diff for the change

diff --git a/netbox/extras/models/change_logging.py b/netbox/extras/models/change_logging.py
index 2c91d97a4..f5579d7a4 100644
--- a/netbox/extras/models/change_logging.py
+++ b/netbox/extras/models/change_logging.py
@@ -39,7 +39,7 @@ class ObjectChange(models.Model):
     )
     changed_object_type = models.ForeignKey(
         to=ContentType,
-        on_delete=models.PROTECT,
+        on_delete=models.CASCADE,
         related_name='+'
     )
     changed_object_id = models.PositiveBigIntegerField()
@@ -49,7 +49,7 @@ class ObjectChange(models.Model):
     )
     related_object_type = models.ForeignKey(
         to=ContentType,
-        on_delete=models.PROTECT,
+        on_delete=models.CASCADE,
         related_name='+',
         blank=True,
         null=True

The command needed for cleanup of DB along with results

python manage.py remove_stale_contenttypes --include-stale-apps -v 3                                                                     git:(fix/11335-plugin-removed|✚1…1 
Some content types in your database are stale and can be deleted.
Any objects that depend on these content types will also be deleted.
The content types and dependent objects that would be deleted are:

    - Content type for netbox_dns.nameserver
    - 4 auth.Permission object(s)
    - Content type for netbox_dns.record
    - 4 auth.Permission object(s)
    - Content type for netbox_dns.view
    - 4 auth.Permission object(s)
    - 1 extras.ObjectChange object(s)
    - 1 extras.CachedValue object(s)
    - Content type for netbox_dns.zone
    - 4 auth.Permission object(s)

This list doesn't include any cascade deletions to data outside of Django's
models (uncommon).

Are you sure you want to delete these content types?

@abhi1693
Copy link
Member

I can submit a PR if my comment is accepted as a solution

@jeremystretch
Copy link
Member

However, I feel when a plugin is removed it can be assumed the data is no longer needed.

I think this was @PieterL75's concern above though. We need a way to let users temporarily disable a plugin without deleting its data.

@julianstolp
Copy link

julianstolp commented Jan 23, 2023

So until this is fixed I need to delete all changelog data for the specific plugin in order to be able to view all my changelog, if i would uninstall the plugin? It does not matter if it is permanent or temporary. Maybe this should be linked to the documentation.

@jeremystretch jeremystretch added status: accepted This issue has been accepted for implementation and removed status: under review Further discussion is needed to determine this issue's scope and/or implementation labels Feb 8, 2023
@jeremystretch jeremystretch self-assigned this Feb 8, 2023
@jeremystretch jeremystretch added the severity: medium Results in substantial degraded or broken functionality for specfic workflows label Jun 23, 2023
jeremystretch added a commit that referenced this issue Jul 5, 2023
…lled apps (#11709)

* Fixes #11335: Default manager for ObjectChange should filter by installed apps

* Employ canonical model discovery mechanism

* Move filtering logic to valid_models() queryset method

* fixed import to avoid content type does not exist

* Cleanup

---------

Co-authored-by: Abhimanyu Saharan <[email protected]>
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
severity: medium Results in substantial degraded or broken functionality for specfic workflows status: accepted This issue has been accepted for implementation type: bug A confirmed report of unexpected behavior in the application
Projects
None yet
4 participants