2
0
Эх сурвалжийг харах

Closes #11494: Enable filtering objects by create/update request IDs

jeremystretch 2 жил өмнө
parent
commit
6e4c4c4342

+ 1 - 0
docs/release-notes/version-3.5.md

@@ -37,6 +37,7 @@ A new ASN range model has been introduced to facilitate the provisioning of new
 * [#10729](https://github.com/netbox-community/netbox/issues/10729) - Add date & time custom field type
 * [#10729](https://github.com/netbox-community/netbox/issues/10729) - Add date & time custom field type
 * [#11254](https://github.com/netbox-community/netbox/issues/11254) - Introduce the `X-Request-ID` HTTP header to annotate the unique ID of each request for change logging
 * [#11254](https://github.com/netbox-community/netbox/issues/11254) - Introduce the `X-Request-ID` HTTP header to annotate the unique ID of each request for change logging
 * [#11440](https://github.com/netbox-community/netbox/issues/11440) - Add an `enabled` field for device type interfaces
 * [#11440](https://github.com/netbox-community/netbox/issues/11440) - Add an `enabled` field for device type interfaces
+* [#11494](https://github.com/netbox-community/netbox/issues/11494) - Enable filtering objects by create/update request IDs
 * [#11517](https://github.com/netbox-community/netbox/issues/11517) - Standardize the inclusion of related objects across the entire UI
 * [#11517](https://github.com/netbox-community/netbox/issues/11517) - Standardize the inclusion of related objects across the entire UI
 * [#11584](https://github.com/netbox-community/netbox/issues/11584) - Add a list view for contact assignments
 * [#11584](https://github.com/netbox-community/netbox/issues/11584) - Add a list view for contact assignments
 * [#11625](https://github.com/netbox-community/netbox/issues/11625) - Add HTMX support to ObjectEditView
 * [#11625](https://github.com/netbox-community/netbox/issues/11625) - Add HTMX support to ObjectEditView

+ 18 - 0
netbox/extras/migrations/0090_objectchange_index_request_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.7 on 2023-03-16 20:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0089_customfield_is_cloneable'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='objectchange',
+            name='request_id',
+            field=models.UUIDField(db_index=True, editable=False),
+        ),
+    ]

+ 2 - 1
netbox/extras/models/change_logging.py

@@ -31,7 +31,8 @@ class ObjectChange(models.Model):
         editable=False
         editable=False
     )
     )
     request_id = models.UUIDField(
     request_id = models.UUIDField(
-        editable=False
+        editable=False,
+        db_index=True
     )
     )
     action = models.CharField(
     action = models.CharField(
         max_length=50,
         max_length=50,

+ 69 - 0
netbox/extras/tests/test_filtersets.py

@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
 from django.test import TestCase
 
 
 from circuits.models import Provider
 from circuits.models import Provider
+from dcim.filtersets import SiteFilterSet
 from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from dcim.models import Location
 from dcim.models import Location
 from extras.choices import *
 from extras.choices import *
@@ -924,3 +925,71 @@ class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         params = {'changed_object_type_id': [ContentType.objects.get(app_label='dcim', model='site').pk]}
         params = {'changed_object_type_id': [ContentType.objects.get(app_label='dcim', model='site').pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+
+class ChangeLoggedFilterSetTestCase(TestCase):
+    """
+    Evaluate base ChangeLoggedFilterSet filters using the Site model.
+    """
+    queryset = Site.objects.all()
+    filterset = SiteFilterSet
+
+    @classmethod
+    def setUpTestData(cls):
+        content_type = ContentType.objects.get_for_model(Site)
+
+        # Create three sites
+        sites = (
+            Site(name='Site 1', slug='site-1'),
+            Site(name='Site 2', slug='site-2'),
+            Site(name='Site 3', slug='site-3'),
+        )
+        Site.objects.bulk_create(sites)
+
+        # Simulate *creation* changelog records for two of the sites
+        request_id = uuid.uuid4()
+        objectchanges = (
+            ObjectChange(
+                changed_object_type=content_type,
+                changed_object_id=sites[0].pk,
+                action=ObjectChangeActionChoices.ACTION_CREATE,
+                request_id=request_id
+            ),
+            ObjectChange(
+                changed_object_type=content_type,
+                changed_object_id=sites[1].pk,
+                action=ObjectChangeActionChoices.ACTION_CREATE,
+                request_id=request_id
+            ),
+        )
+        ObjectChange.objects.bulk_create(objectchanges)
+
+        # Simulate *update* changelog records for two of the sites
+        request_id = uuid.uuid4()
+        objectchanges = (
+            ObjectChange(
+                changed_object_type=content_type,
+                changed_object_id=sites[0].pk,
+                action=ObjectChangeActionChoices.ACTION_UPDATE,
+                request_id=request_id
+            ),
+            ObjectChange(
+                changed_object_type=content_type,
+                changed_object_id=sites[1].pk,
+                action=ObjectChangeActionChoices.ACTION_UPDATE,
+                request_id=request_id
+            ),
+        )
+        ObjectChange.objects.bulk_create(objectchanges)
+
+    def test_created_by_request(self):
+        request_id = ObjectChange.objects.filter(action=ObjectChangeActionChoices.ACTION_CREATE).first().request_id
+        params = {'created_by_request': request_id}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+        self.assertEqual(self.queryset.count(), 3)
+
+    def test_updated_by_request(self):
+        request_id = ObjectChange.objects.filter(action=ObjectChangeActionChoices.ACTION_UPDATE).first().request_id
+        params = {'updated_by_request': request_id}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+        self.assertEqual(self.queryset.count(), 3)

+ 22 - 2
netbox/netbox/filtersets.py

@@ -7,9 +7,9 @@ from django_filters.exceptions import FieldLookupError
 from django_filters.utils import get_model_field, resolve_field
 from django_filters.utils import get_model_field, resolve_field
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 
 
-from extras.choices import CustomFieldFilterLogicChoices
+from extras.choices import CustomFieldFilterLogicChoices, ObjectChangeActionChoices
 from extras.filters import TagFilter
 from extras.filters import TagFilter
-from extras.models import CustomField, SavedFilter
+from extras.models import CustomField, ObjectChange, SavedFilter
 from utilities.constants import (
 from utilities.constants import (
     FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
     FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
     FILTER_NUMERIC_BASED_LOOKUP_MAP
     FILTER_NUMERIC_BASED_LOOKUP_MAP
@@ -231,6 +231,26 @@ class ChangeLoggedModelFilterSet(BaseFilterSet):
     """
     """
     created = filters.MultiValueDateTimeFilter()
     created = filters.MultiValueDateTimeFilter()
     last_updated = filters.MultiValueDateTimeFilter()
     last_updated = filters.MultiValueDateTimeFilter()
+    created_by_request = django_filters.UUIDFilter(
+        method='filter_by_request'
+    )
+    updated_by_request = django_filters.UUIDFilter(
+        method='filter_by_request'
+    )
+
+    def filter_by_request(self, queryset, name, value):
+        content_type = ContentType.objects.get_for_model(self.Meta.model)
+        action = {
+            'created_by_request': ObjectChangeActionChoices.ACTION_CREATE,
+            'updated_by_request': ObjectChangeActionChoices.ACTION_UPDATE,
+        }.get(name)
+        request_id = value
+        pks = ObjectChange.objects.filter(
+            changed_object_type=content_type,
+            action=action,
+            request_id=request_id
+        ).values_list('changed_object_id', flat=True)
+        return queryset.filter(pk__in=pks)
 
 
 
 
 class NetBoxModelFilterSet(ChangeLoggedModelFilterSet):
 class NetBoxModelFilterSet(ChangeLoggedModelFilterSet):