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

fix(filtersets): Correct Module and Data File ID filters

Fixes installed_module_id filter to use Module.objects instead of
ModuleBay.objects. Corrects data_file_id filters across multiple
filtersets to use DataFile.objects instead of DataSource.objects.
Adds regression tests for all corrected filters.

Fixes #23345
Martin Hauser 1 день назад
Родитель
Сommit
8196af5c71

+ 1 - 1
netbox/dcim/filtersets.py

@@ -2645,7 +2645,7 @@ class ModuleBayFilterSet(ModularDeviceComponentFilterSet):
     )
     )
     installed_module_id = django_filters.ModelMultipleChoiceFilter(
     installed_module_id = django_filters.ModelMultipleChoiceFilter(
         field_name='installed_module',
         field_name='installed_module',
-        queryset=ModuleBay.objects.all(),
+        queryset=Module.objects.all(),
         label=_('Installed module (ID)'),
         label=_('Installed module (ID)'),
     )
     )
     module_bay_type_id = django_filters.ModelMultipleChoiceFilter(
     module_bay_type_id = django_filters.ModelMultipleChoiceFilter(

+ 10 - 0
netbox/dcim/tests/test_filtersets.py

@@ -6887,6 +6887,16 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTestMixin, ChangeLogge
         params = {'module_id': [modules[0].pk, modules[1].pk]}
         params = {'module_id': [modules[0].pk, modules[1].pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_installed_module(self):
+        """Filter module bays by installed module ID."""
+        modules = Module.objects.all()[:2]
+        params = {'installed_module_id': [modules[0].pk, modules[1].pk]}
+        # module_id also matches two bays here, so compare the bays rather than count them
+        self.assertSetEqual(
+            set(self.filterset(params, self.queryset).qs.values_list('pk', flat=True)),
+            {modules[0].module_bay_id, modules[1].module_bay_id},
+        )
+
 
 
 class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTestMixin, ChangeLoggedFilterSetTestMixin):
 class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTestMixin, ChangeLoggedFilterSetTestMixin):
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()

+ 5 - 5
netbox/extras/filtersets.py

@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 
 
-from core.models import DataSource, ObjectType
+from core.models import DataFile, DataSource, ObjectType
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from netbox.event_rules import get_event_rule_action_choices, get_event_rule_action_slugs
 from netbox.event_rules import get_event_rule_action_choices, get_event_rule_action_slugs
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet, PrimaryModelFilterSet
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet, PrimaryModelFilterSet
@@ -306,7 +306,7 @@ class ExportTemplateFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         label=_('Data source (ID)'),
         label=_('Data source (ID)'),
     )
     )
     data_file_id = django_filters.ModelMultipleChoiceFilter(
     data_file_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=DataSource.objects.all(),
+        queryset=DataFile.objects.all(),
         distinct=False,
         distinct=False,
         label=_('Data file (ID)'),
         label=_('Data file (ID)'),
     )
     )
@@ -678,7 +678,7 @@ class ConfigContextProfileFilterSet(PrimaryModelFilterSet):
         label=_('Data source (ID)'),
         label=_('Data source (ID)'),
     )
     )
     data_file_id = django_filters.ModelMultipleChoiceFilter(
     data_file_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=DataSource.objects.all(),
+        queryset=DataFile.objects.all(),
         distinct=False,
         distinct=False,
         label=_('Data file (ID)'),
         label=_('Data file (ID)'),
     )
     )
@@ -854,7 +854,7 @@ class ConfigContextFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         label=_('Data source (ID)'),
         label=_('Data source (ID)'),
     )
     )
     data_file_id = django_filters.ModelMultipleChoiceFilter(
     data_file_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=DataSource.objects.all(),
+        queryset=DataFile.objects.all(),
         distinct=False,
         distinct=False,
         label=_('Data file (ID)'),
         label=_('Data file (ID)'),
     )
     )
@@ -885,7 +885,7 @@ class ConfigTemplateFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         label=_('Data source (ID)'),
         label=_('Data source (ID)'),
     )
     )
     data_file_id = django_filters.ModelMultipleChoiceFilter(
     data_file_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=DataSource.objects.all(),
+        queryset=DataFile.objects.all(),
         distinct=False,
         distinct=False,
         label=_('Data file (ID)'),
         label=_('Data file (ID)'),
     )
     )

+ 67 - 1
netbox/extras/tests/test_filtersets.py

@@ -7,7 +7,7 @@ from django.test import TestCase
 from circuits.models import Provider
 from circuits.models import Provider
 from core.choices import ManagedFileRootPathChoices, ObjectChangeActionChoices
 from core.choices import ManagedFileRootPathChoices, ObjectChangeActionChoices
 from core.events import *
 from core.events import *
-from core.models import ObjectChange, ObjectType
+from core.models import DataFile, DataSource, ObjectChange, ObjectType
 from dcim.filtersets import SiteFilterSet
 from dcim.filtersets import SiteFilterSet
 from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from extras.choices import *
 from extras.choices import *
@@ -715,6 +715,14 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
     def setUpTestData(cls):
     def setUpTestData(cls):
         object_types = ObjectType.objects.filter(model__in=['site', 'rack', 'device'])
         object_types = ObjectType.objects.filter(model__in=['site', 'rack', 'device'])
 
 
+        data_source = DataSource.objects.create(name='Data Source 1', type='local', source_url='file:///tmp/source1/')
+        data_files = (
+            DataFile(source=data_source, path='file1.txt', last_updated=datetime(2023, 1, 1, tzinfo=UTC), size=1000),
+            DataFile(source=data_source, path='file2.txt', last_updated=datetime(2023, 1, 2, tzinfo=UTC), size=2000),
+            DataFile(source=data_source, path='file3.txt', last_updated=datetime(2023, 1, 3, tzinfo=UTC), size=3000),
+        )
+        DataFile.objects.bulk_create(data_files)
+
         export_templates = (
         export_templates = (
             ExportTemplate(
             ExportTemplate(
                 name='Export Template 1',
                 name='Export Template 1',
@@ -724,6 +732,7 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 file_name='foo',
                 file_name='foo',
                 file_extension='foo',
                 file_extension='foo',
                 as_attachment=True,
                 as_attachment=True,
+                data_file=data_files[0],
             ),
             ),
             ExportTemplate(
             ExportTemplate(
                 name='Export Template 2',
                 name='Export Template 2',
@@ -733,6 +742,7 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 file_name='bar',
                 file_name='bar',
                 file_extension='bar',
                 file_extension='bar',
                 as_attachment=True,
                 as_attachment=True,
+                data_file=data_files[1],
             ),
             ),
             ExportTemplate(
             ExportTemplate(
                 name='Export Template 3',
                 name='Export Template 3',
@@ -741,6 +751,7 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 file_name='baz',
                 file_name='baz',
                 file_extension='baz',
                 file_extension='baz',
                 as_attachment=False,
                 as_attachment=False,
+                data_file=data_files[2],
             ),
             ),
         )
         )
         ExportTemplate.objects.bulk_create(export_templates)
         ExportTemplate.objects.bulk_create(export_templates)
@@ -781,6 +792,12 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
         params = {'as_attachment': True}
         params = {'as_attachment': True}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_data_file(self):
+        """Filter export templates by data file ID."""
+        data_files = DataFile.objects.all()[:2]
+        params = {'data_file_id': [data_files[0].pk, data_files[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
 
 
 class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
     queryset = ImageAttachment.objects.all()
     queryset = ImageAttachment.objects.all()
@@ -1042,18 +1059,29 @@ class ConfigContextProfileTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
+        data_source = DataSource.objects.create(name='Data Source 1', type='local', source_url='file:///tmp/source1/')
+        data_files = (
+            DataFile(source=data_source, path='file1.txt', last_updated=datetime(2023, 1, 1, tzinfo=UTC), size=1000),
+            DataFile(source=data_source, path='file2.txt', last_updated=datetime(2023, 1, 2, tzinfo=UTC), size=2000),
+            DataFile(source=data_source, path='file3.txt', last_updated=datetime(2023, 1, 3, tzinfo=UTC), size=3000),
+        )
+        DataFile.objects.bulk_create(data_files)
+
         profiles = (
         profiles = (
             ConfigContextProfile(
             ConfigContextProfile(
                 name='Config Context Profile 1',
                 name='Config Context Profile 1',
                 description='foo',
                 description='foo',
+                data_file=data_files[0],
             ),
             ),
             ConfigContextProfile(
             ConfigContextProfile(
                 name='Config Context Profile 2',
                 name='Config Context Profile 2',
                 description='bar',
                 description='bar',
+                data_file=data_files[1],
             ),
             ),
             ConfigContextProfile(
             ConfigContextProfile(
                 name='Config Context Profile 3',
                 name='Config Context Profile 3',
                 description='baz',
                 description='baz',
+                data_file=data_files[2],
             ),
             ),
         )
         )
         ConfigContextProfile.objects.bulk_create(profiles)
         ConfigContextProfile.objects.bulk_create(profiles)
@@ -1067,6 +1095,12 @@ class ConfigContextProfileTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
         params = {'name': [profiles[0].name, profiles[1].name]}
         params = {'name': [profiles[0].name, profiles[1].name]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_data_file(self):
+        """Filter config context profiles by data file ID."""
+        data_files = DataFile.objects.all()[:2]
+        params = {'data_file_id': [data_files[0].pk, data_files[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
 
 
 class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
     queryset = ConfigContext.objects.all()
     queryset = ConfigContext.objects.all()
@@ -1175,6 +1209,14 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 
 
         tags = create_tags('Alpha', 'Bravo', 'Charlie')
         tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
+        data_source = DataSource.objects.create(name='Data Source 1', type='local', source_url='file:///tmp/source1/')
+        data_files = (
+            DataFile(source=data_source, path='file1.txt', last_updated=datetime(2023, 1, 1, tzinfo=UTC), size=1000),
+            DataFile(source=data_source, path='file2.txt', last_updated=datetime(2023, 1, 2, tzinfo=UTC), size=2000),
+            DataFile(source=data_source, path='file3.txt', last_updated=datetime(2023, 1, 3, tzinfo=UTC), size=3000),
+        )
+        DataFile.objects.bulk_create(data_files)
+
         for i in range(0, 3):
         for i in range(0, 3):
             is_active = bool(i % 2)
             is_active = bool(i % 2)
             c = ConfigContext.objects.create(
             c = ConfigContext.objects.create(
@@ -1182,6 +1224,7 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 profile=profiles[i],
                 profile=profiles[i],
                 is_active=is_active,
                 is_active=is_active,
                 data='{"foo": 123}',
                 data='{"foo": 123}',
+                data_file=data_files[i],
                 description=f"foobar{i + 1}"
                 description=f"foobar{i + 1}"
             )
             )
             c.regions.set([regions[i]])
             c.regions.set([regions[i]])
@@ -1310,6 +1353,12 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
         params = {'tag': [tags[0].slug, tags[1].slug]}
         params = {'tag': [tags[0].slug, tags[1].slug]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_data_file(self):
+        """Filter config contexts by data file ID."""
+        data_files = DataFile.objects.all()[:2]
+        params = {'data_file_id': [data_files[0].pk, data_files[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
 
 
 class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
     queryset = ConfigTemplate.objects.all()
     queryset = ConfigTemplate.objects.all()
@@ -1318,6 +1367,14 @@ class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
+        data_source = DataSource.objects.create(name='Data Source 1', type='local', source_url='file:///tmp/source1/')
+        data_files = (
+            DataFile(source=data_source, path='file1.txt', last_updated=datetime(2023, 1, 1, tzinfo=UTC), size=1000),
+            DataFile(source=data_source, path='file2.txt', last_updated=datetime(2023, 1, 2, tzinfo=UTC), size=2000),
+            DataFile(source=data_source, path='file3.txt', last_updated=datetime(2023, 1, 3, tzinfo=UTC), size=3000),
+        )
+        DataFile.objects.bulk_create(data_files)
+
         config_templates = (
         config_templates = (
             ConfigTemplate(
             ConfigTemplate(
                 name='Config Template 1',
                 name='Config Template 1',
@@ -1327,6 +1384,7 @@ class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 file_name='foo',
                 file_name='foo',
                 file_extension='foo',
                 file_extension='foo',
                 as_attachment=True,
                 as_attachment=True,
+                data_file=data_files[0],
             ),
             ),
             ConfigTemplate(
             ConfigTemplate(
                 name='Config Template 2',
                 name='Config Template 2',
@@ -1336,6 +1394,7 @@ class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 file_name='bar',
                 file_name='bar',
                 file_extension='bar',
                 file_extension='bar',
                 as_attachment=True,
                 as_attachment=True,
+                data_file=data_files[1],
             ),
             ),
             ConfigTemplate(
             ConfigTemplate(
                 name='Config Template 3',
                 name='Config Template 3',
@@ -1344,6 +1403,7 @@ class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
                 file_name='baz',
                 file_name='baz',
                 file_extension='baz',
                 file_extension='baz',
                 as_attachment=False,
                 as_attachment=False,
+                data_file=data_files[2],
             ),
             ),
         )
         )
         ConfigTemplate.objects.bulk_create(config_templates)
         ConfigTemplate.objects.bulk_create(config_templates)
@@ -1376,6 +1436,12 @@ class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
         params = {'as_attachment': True}
         params = {'as_attachment': True}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_data_file(self):
+        """Filter config templates by data file ID."""
+        data_files = DataFile.objects.all()[:2]
+        params = {'data_file_id': [data_files[0].pk, data_files[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
 
 
 class TagTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
 class TagTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
     queryset = Tag.objects.all()
     queryset = Tag.objects.all()