From 51a76b53068499733dfd61ef8a9616ec392b581e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 8 Feb 2023 16:15:51 -0500 Subject: [PATCH 1/5] Fixes #11335: Default manager for ObjectChange should filter by installed apps --- netbox/extras/models/change_logging.py | 4 ++-- netbox/extras/querysets.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/netbox/extras/models/change_logging.py b/netbox/extras/models/change_logging.py index e2b118b84ff..00a8feec732 100644 --- a/netbox/extras/models/change_logging.py +++ b/netbox/extras/models/change_logging.py @@ -5,7 +5,7 @@ from django.urls import reverse from extras.choices import * -from utilities.querysets import RestrictedQuerySet +from ..querysets import ObjectChangeManager __all__ = ( 'ObjectChange', @@ -82,7 +82,7 @@ class ObjectChange(models.Model): null=True ) - objects = RestrictedQuerySet.as_manager() + objects = ObjectChangeManager() class Meta: ordering = ['-time'] diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 2b97af0fbf1..5462a34773d 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -1,5 +1,6 @@ +from django.conf import settings from django.contrib.postgres.aggregates import JSONBAgg -from django.db.models import OuterRef, Subquery, Q +from django.db.models import Manager, OuterRef, Subquery, Q from extras.models.tags import TaggedItem from utilities.query_functions import EmptyGroupByJSONBAgg @@ -151,3 +152,14 @@ def _get_config_context_filters(self): ) return base_query + + +class ObjectChangeManager(Manager.from_queryset(RestrictedQuerySet)): + + def get_queryset(self): + # Exclude any change records which refer to an instance of a model that's no longer installed. This + # can happen when a plugin is removed but its data remains in the database, for example. + app_labels = [ + app.split('.')[-1] for app in settings.INSTALLED_APPS + ] + return super().get_queryset().filter(changed_object_type__app_label__in=app_labels) From 5c7173bd18685d794392c7b7105438913b61a86f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 9 Feb 2023 09:55:18 -0500 Subject: [PATCH 2/5] Employ canonical model discovery mechanism --- netbox/extras/querysets.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 5462a34773d..165c5abd72a 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -1,4 +1,5 @@ -from django.conf import settings +from django.apps import apps +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.aggregates import JSONBAgg from django.db.models import Manager, OuterRef, Subquery, Q @@ -159,7 +160,7 @@ class ObjectChangeManager(Manager.from_queryset(RestrictedQuerySet)): def get_queryset(self): # Exclude any change records which refer to an instance of a model that's no longer installed. This # can happen when a plugin is removed but its data remains in the database, for example. - app_labels = [ - app.split('.')[-1] for app in settings.INSTALLED_APPS - ] - return super().get_queryset().filter(changed_object_type__app_label__in=app_labels) + content_type_ids = set( + ct.pk for ct in ContentType.objects.get_for_models(*apps.get_models()).values() + ) + return super().get_queryset().filter(changed_object_type_id__in=content_type_ids) From 07b093dfef6134754735b0eb0495efa7e81f6a49 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 10 Mar 2023 10:23:38 -0500 Subject: [PATCH 3/5] Move filtering logic to valid_models() queryset method --- docs/release-notes/version-3.4.md | 1 + netbox/extras/api/views.py | 2 +- netbox/extras/models/change_logging.py | 4 ++-- netbox/extras/querysets.py | 6 +++--- netbox/extras/views.py | 8 ++++---- netbox/users/views.py | 4 +++- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 22c33bb01d1..7030714f32c 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -105,6 +105,7 @@ ### Bug Fixes +* [#11335](https://github.com/netbox-community/netbox/issues/11335) - Avoid rendering changelog entries referencing models from removed plugins * [#11470](https://github.com/netbox-community/netbox/issues/11470) - Avoid raising exception when filtering IPs by an invalid address * [#11565](https://github.com/netbox-community/netbox/issues/11565) - Apply custom field defaults to IP address created during FHRP group creation * [#11631](https://github.com/netbox-community/netbox/issues/11631) - Fix filtering changelog & journal entries by multiple content type IDs diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 3f796d7f88d..f4b5a1433c2 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -368,7 +368,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): Retrieve a list of recent changes. """ metadata_class = ContentTypeMetadata - queryset = ObjectChange.objects.prefetch_related('user') + queryset = ObjectChange.objects.valid_models().prefetch_related('user') serializer_class = serializers.ObjectChangeSerializer filterset_class = filtersets.ObjectChangeFilterSet diff --git a/netbox/extras/models/change_logging.py b/netbox/extras/models/change_logging.py index 00a8feec732..2cb53ed01a4 100644 --- a/netbox/extras/models/change_logging.py +++ b/netbox/extras/models/change_logging.py @@ -5,7 +5,7 @@ from django.urls import reverse from extras.choices import * -from ..querysets import ObjectChangeManager +from ..querysets import ObjectChangeQuerySet __all__ = ( 'ObjectChange', @@ -82,7 +82,7 @@ class ObjectChange(models.Model): null=True ) - objects = ObjectChangeManager() + objects = ObjectChangeQuerySet.as_manager() class Meta: ordering = ['-time'] diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 165c5abd72a..5bee14d9f40 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -155,12 +155,12 @@ def _get_config_context_filters(self): return base_query -class ObjectChangeManager(Manager.from_queryset(RestrictedQuerySet)): +class ObjectChangeQuerySet(RestrictedQuerySet): - def get_queryset(self): + def valid_models(self): # Exclude any change records which refer to an instance of a model that's no longer installed. This # can happen when a plugin is removed but its data remains in the database, for example. content_type_ids = set( ct.pk for ct in ContentType.objects.get_for_models(*apps.get_models()).values() ) - return super().get_queryset().filter(changed_object_type_id__in=content_type_ids) + return self.filter(changed_object_type_id__in=content_type_ids) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 6cbadf09d99..6ba63ab584b 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -511,7 +511,7 @@ class ConfigTemplateBulkSyncDataView(generic.BulkSyncDataView): # class ObjectChangeListView(generic.ObjectListView): - queryset = ObjectChange.objects.all() + queryset = ObjectChange.objects.valid_models() filterset = filtersets.ObjectChangeFilterSet filterset_form = forms.ObjectChangeFilterForm table = tables.ObjectChangeTable @@ -521,10 +521,10 @@ class ObjectChangeListView(generic.ObjectListView): @register_model_view(ObjectChange) class ObjectChangeView(generic.ObjectView): - queryset = ObjectChange.objects.all() + queryset = ObjectChange.objects.valid_models() def get_extra_context(self, request, instance): - related_changes = ObjectChange.objects.restrict(request.user, 'view').filter( + related_changes = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter( request_id=instance.request_id ).exclude( pk=instance.pk @@ -534,7 +534,7 @@ def get_extra_context(self, request, instance): orderable=False ) - objectchanges = ObjectChange.objects.restrict(request.user, 'view').filter( + objectchanges = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter( changed_object_type=instance.changed_object_type, changed_object_id=instance.changed_object_id, ) diff --git a/netbox/users/views.py b/netbox/users/views.py index a82620914ad..05648e2e3a2 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -159,7 +159,9 @@ class ProfileView(LoginRequiredMixin, View): def get(self, request): # Compile changelog table - changelog = ObjectChange.objects.restrict(request.user, 'view').filter(user=request.user).prefetch_related( + changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter( + user=request.user + ).prefetch_related( 'changed_object_type' )[:20] changelog_table = ObjectChangeTable(changelog) From a1ac2664d02eed18fd619a2c2b150356eb17f7f9 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 6 May 2023 21:26:49 +0530 Subject: [PATCH 4/5] fixed import to avoid content type does not exist --- netbox/extras/tests/test_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index b59481a36b6..086c8e246c9 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -8,7 +8,6 @@ from core.choices import ManagedFileRootPathChoices from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site -from extras.api.views import ReportViewSet, ScriptViewSet from extras.models import * from extras.reports import Report from extras.scripts import BooleanVar, IntegerVar, Script, StringVar @@ -579,6 +578,7 @@ def setUp(self): super().setUp() # Monkey-patch the API viewset's _get_report() method to return our test Report above + from extras.api.views import ReportViewSet ReportViewSet._get_report = self.get_test_report def test_get_report(self): @@ -621,6 +621,7 @@ def setUp(self): super().setUp() # Monkey-patch the API viewset's _get_script() method to return our test Script above + from extras.api.views import ScriptViewSet ScriptViewSet._get_script = self.get_test_script def test_get_script(self): From 7852522705e77345c536c4b88040acaae6c5fe2d Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 May 2023 16:37:21 -0400 Subject: [PATCH 5/5] Cleanup --- docs/release-notes/version-3.4.md | 1 - netbox/extras/querysets.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 7030714f32c..22c33bb01d1 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -105,7 +105,6 @@ ### Bug Fixes -* [#11335](https://github.com/netbox-community/netbox/issues/11335) - Avoid rendering changelog entries referencing models from removed plugins * [#11470](https://github.com/netbox-community/netbox/issues/11470) - Avoid raising exception when filtering IPs by an invalid address * [#11565](https://github.com/netbox-community/netbox/issues/11565) - Apply custom field defaults to IP address created during FHRP group creation * [#11631](https://github.com/netbox-community/netbox/issues/11631) - Fix filtering changelog & journal entries by multiple content type IDs diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 5bee14d9f40..2e6f93b9306 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -1,7 +1,7 @@ from django.apps import apps from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.aggregates import JSONBAgg -from django.db.models import Manager, OuterRef, Subquery, Q +from django.db.models import OuterRef, Subquery, Q from extras.models.tags import TaggedItem from utilities.query_functions import EmptyGroupByJSONBAgg