Просмотр исходного кода

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

jeremystretch 2 лет назад
Родитель
Сommit
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
 * [#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
+* [#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
 * [#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

+ 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
     )
     request_id = models.UUIDField(
-        editable=False
+        editable=False,
+        db_index=True
     )
     action = models.CharField(
         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 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 Location
 from extras.choices import *
@@ -924,3 +925,71 @@ class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         params = {'changed_object_type_id': [ContentType.objects.get(app_label='dcim', model='site').pk]}
         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.utils.translation import gettext as _
 
-from extras.choices import CustomFieldFilterLogicChoices
+from extras.choices import CustomFieldFilterLogicChoices, ObjectChangeActionChoices
 from extras.filters import TagFilter
-from extras.models import CustomField, SavedFilter
+from extras.models import CustomField, ObjectChange, SavedFilter
 from utilities.constants import (
     FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
     FILTER_NUMERIC_BASED_LOOKUP_MAP
@@ -231,6 +231,26 @@ class ChangeLoggedModelFilterSet(BaseFilterSet):
     """
     created = 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):