Quellcode durchsuchen

Closes #21257: Introduce & adopt MultiValueContentTypeFilter (#21417)

Jeremy Stretch vor 2 Tagen
Ursprung
Commit
dc738c7102

+ 3 - 3
netbox/circuits/filtersets.py

@@ -9,7 +9,7 @@ from ipam.models import ASN
 from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
 from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
 from utilities.filters import (
 from utilities.filters import (
-    ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
+    MultiValueCharFilter, MultiValueContentTypeFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
 )
 )
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from .choices import *
 from .choices import *
@@ -281,7 +281,7 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
         queryset=Circuit.objects.all(),
         queryset=Circuit.objects.all(),
         label=_('Circuit'),
         label=_('Circuit'),
     )
     )
-    termination_type = ContentTypeFilter()
+    termination_type = MultiValueContentTypeFilter()
     region_id = TreeNodeMultipleChoiceFilter(
     region_id = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='_region',
         field_name='_region',
@@ -381,7 +381,7 @@ class CircuitGroupAssignmentFilterSet(NetBoxModelFilterSet):
         method='search',
         method='search',
         label=_('Search'),
         label=_('Search'),
     )
     )
-    member_type = ContentTypeFilter()
+    member_type = MultiValueContentTypeFilter()
     circuit = MultiValueCharFilter(
     circuit = MultiValueCharFilter(
         method='filter_circuit',
         method='filter_circuit',
         field_name='cid',
         field_name='cid',

+ 4 - 4
netbox/core/filtersets.py

@@ -6,7 +6,7 @@ from django.utils.translation import gettext as _
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, PrimaryModelFilterSet
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, PrimaryModelFilterSet
 from netbox.utils import get_data_backend_choices
 from netbox.utils import get_data_backend_choices
 from users.models import User
 from users.models import User
-from utilities.filters import ContentTypeFilter
+from utilities.filters import MultiValueContentTypeFilter
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from .choices import *
 from .choices import *
 from .models import *
 from .models import *
@@ -88,7 +88,7 @@ class JobFilterSet(BaseFilterSet):
         queryset=ObjectType.objects.with_feature('jobs'),
         queryset=ObjectType.objects.with_feature('jobs'),
         field_name='object_type_id',
         field_name='object_type_id',
     )
     )
-    object_type = ContentTypeFilter()
+    object_type = MultiValueContentTypeFilter()
     created = django_filters.DateTimeFilter()
     created = django_filters.DateTimeFilter()
     created__before = django_filters.DateTimeFilter(
     created__before = django_filters.DateTimeFilter(
         field_name='created',
         field_name='created',
@@ -180,11 +180,11 @@ class ObjectChangeFilterSet(BaseFilterSet):
         label=_('Search'),
         label=_('Search'),
     )
     )
     time = django_filters.DateTimeFromToRangeFilter()
     time = django_filters.DateTimeFromToRangeFilter()
-    changed_object_type = ContentTypeFilter()
+    changed_object_type = MultiValueContentTypeFilter()
     changed_object_type_id = django_filters.ModelMultipleChoiceFilter(
     changed_object_type_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ContentType.objects.all()
         queryset=ContentType.objects.all()
     )
     )
-    related_object_type = ContentTypeFilter()
+    related_object_type = MultiValueContentTypeFilter()
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         label=_('User (ID)'),
         label=_('User (ID)'),

+ 2 - 2
netbox/core/tests/test_filtersets.py

@@ -237,9 +237,9 @@ class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_changed_object_type(self):
     def test_changed_object_type(self):
-        params = {'changed_object_type': 'dcim.site'}
+        params = {'changed_object_type': ['dcim.site']}
         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_by_natural_key('dcim', 'site').pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
 
 

+ 2 - 2
netbox/dcim/base_filtersets.py

@@ -2,7 +2,7 @@ import django_filters
 
 
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 from netbox.filtersets import BaseFilterSet
 from netbox.filtersets import BaseFilterSet
-from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
+from utilities.filters import MultiValueContentTypeFilter, TreeNodeMultipleChoiceFilter
 from .models import *
 from .models import *
 
 
 __all__ = (
 __all__ = (
@@ -14,7 +14,7 @@ class ScopedFilterSet(BaseFilterSet):
     """
     """
     Provides additional filtering functionality for location, site, etc.. for Scoped models.
     Provides additional filtering functionality for location, site, etc.. for Scoped models.
     """
     """
-    scope_type = ContentTypeFilter()
+    scope_type = MultiValueContentTypeFilter()
     region_id = TreeNodeMultipleChoiceFilter(
     region_id = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='_region',
         field_name='_region',

+ 8 - 8
netbox/dcim/filtersets.py

@@ -21,8 +21,8 @@ from tenancy.models import *
 from users.filterset_mixins import OwnerFilterMixin
 from users.filterset_mixins import OwnerFilterMixin
 from users.models import User
 from users.models import User
 from utilities.filters import (
 from utilities.filters import (
-    ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
-    NumericArrayFilter, TreeNodeMultipleChoiceFilter,
+    MultiValueCharFilter, MultiValueContentTypeFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
+    MultiValueWWNFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
 )
 )
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
@@ -977,7 +977,7 @@ class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeCompo
         to_field_name='slug',
         to_field_name='slug',
         label=_('Role (slug)'),
         label=_('Role (slug)'),
     )
     )
-    component_type = ContentTypeFilter()
+    component_type = MultiValueContentTypeFilter()
     component_id = MultiValueNumberFilter()
     component_id = MultiValueNumberFilter()
 
 
     class Meta:
     class Meta:
@@ -1822,7 +1822,7 @@ class PowerOutletFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSe
 @register_filterset
 @register_filterset
 class MACAddressFilterSet(PrimaryModelFilterSet):
 class MACAddressFilterSet(PrimaryModelFilterSet):
     mac_address = MultiValueMACAddressFilter()
     mac_address = MultiValueMACAddressFilter()
-    assigned_object_type = ContentTypeFilter()
+    assigned_object_type = MultiValueContentTypeFilter()
     device = MultiValueCharFilter(
     device = MultiValueCharFilter(
         method='filter_device',
         method='filter_device',
         field_name='name',
         field_name='name',
@@ -2267,7 +2267,7 @@ class InventoryItemFilterSet(DeviceComponentFilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label=_('Role (slug)'),
         label=_('Role (slug)'),
     )
     )
-    component_type = ContentTypeFilter()
+    component_type = MultiValueContentTypeFilter()
     component_id = MultiValueNumberFilter()
     component_id = MultiValueNumberFilter()
     serial = MultiValueCharFilter(
     serial = MultiValueCharFilter(
         lookup_expr='iexact'
         lookup_expr='iexact'
@@ -2381,14 +2381,14 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
 
 
 @register_filterset
 @register_filterset
 class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
 class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
-    termination_a_type = ContentTypeFilter(
+    termination_a_type = MultiValueContentTypeFilter(
         field_name='terminations__termination_type'
         field_name='terminations__termination_type'
     )
     )
     termination_a_id = MultiValueNumberFilter(
     termination_a_id = MultiValueNumberFilter(
         method='filter_by_cable_end_a',
         method='filter_by_cable_end_a',
         field_name='terminations__termination_id'
         field_name='terminations__termination_id'
     )
     )
-    termination_b_type = ContentTypeFilter(
+    termination_b_type = MultiValueContentTypeFilter(
         field_name='terminations__termination_type'
         field_name='terminations__termination_type'
     )
     )
     termination_b_id = MultiValueNumberFilter(
     termination_b_id = MultiValueNumberFilter(
@@ -2554,7 +2554,7 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
 
 
 @register_filterset
 @register_filterset
 class CableTerminationFilterSet(ChangeLoggedModelFilterSet):
 class CableTerminationFilterSet(ChangeLoggedModelFilterSet):
-    termination_type = ContentTypeFilter()
+    termination_type = MultiValueContentTypeFilter()
 
 
     class Meta:
     class Meta:
         model = CableTermination
         model = CableTermination

+ 4 - 6
netbox/dcim/tests/test_filtersets.py

@@ -6251,7 +6251,7 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_component_type(self):
     def test_component_type(self):
-        params = {'component_type': 'dcim.interface'}
+        params = {'component_type': ['dcim.interface']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
     def test_status(self):
     def test_status(self):
@@ -6723,10 +6723,8 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_termination_types(self):
     def test_termination_types(self):
-        params = {'termination_a_type': 'dcim.consoleport'}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
-        # params = {'termination_b_type': 'dcim.consoleserverport'}
-        # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+        params = {'termination_a_type': ['dcim.consoleport', 'dcim.consoleserverport']}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_termination_ids(self):
     def test_termination_ids(self):
         interface_ids = CableTermination.objects.filter(
         interface_ids = CableTermination.objects.filter(
@@ -6734,7 +6732,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
             cable_end='A'
             cable_end='A'
         ).values_list('termination_id', flat=True)
         ).values_list('termination_id', flat=True)
         params = {
         params = {
-            'termination_a_type': 'dcim.interface',
+            'termination_a_type': ['dcim.interface'],
             'termination_a_id': list(interface_ids),
             'termination_a_id': list(interface_ids),
         }
         }
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)

+ 13 - 13
netbox/extras/filtersets.py

@@ -10,7 +10,7 @@ from tenancy.models import Tenant, TenantGroup
 from users.filterset_mixins import OwnerFilterMixin
 from users.filterset_mixins import OwnerFilterMixin
 from users.models import Group, User
 from users.models import Group, User
 from utilities.filters import (
 from utilities.filters import (
-    ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
+    MultiValueCharFilter, MultiValueContentTypeFilter, MultiValueNumberFilter
 )
 )
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 from virtualization.models import Cluster, ClusterGroup, ClusterType
@@ -104,7 +104,7 @@ class EventRuleFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_types'
         field_name='object_types'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_types'
         field_name='object_types'
     )
     )
     event_type = MultiValueCharFilter(
     event_type = MultiValueCharFilter(
@@ -113,7 +113,7 @@ class EventRuleFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
     action_type = django_filters.MultipleChoiceFilter(
     action_type = django_filters.MultipleChoiceFilter(
         choices=EventRuleActionChoices
         choices=EventRuleActionChoices
     )
     )
-    action_object_type = ContentTypeFilter()
+    action_object_type = MultiValueContentTypeFilter()
     action_object_id = MultiValueNumberFilter()
     action_object_id = MultiValueNumberFilter()
 
 
     class Meta:
     class Meta:
@@ -148,14 +148,14 @@ class CustomFieldFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_types'
         field_name='object_types'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_types'
         field_name='object_types'
     )
     )
     related_object_type_id = django_filters.ModelMultipleChoiceFilter(
     related_object_type_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='related_object_type'
         field_name='related_object_type'
     )
     )
-    related_object_type = ContentTypeFilter()
+    related_object_type = MultiValueContentTypeFilter()
     choice_set_id = django_filters.ModelMultipleChoiceFilter(
     choice_set_id = django_filters.ModelMultipleChoiceFilter(
         queryset=CustomFieldChoiceSet.objects.all()
         queryset=CustomFieldChoiceSet.objects.all()
     )
     )
@@ -224,7 +224,7 @@ class CustomLinkFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_types'
         field_name='object_types'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_types'
         field_name='object_types'
     )
     )
 
 
@@ -255,7 +255,7 @@ class ExportTemplateFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_types'
         field_name='object_types'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_types'
         field_name='object_types'
     )
     )
     data_source_id = django_filters.ModelMultipleChoiceFilter(
     data_source_id = django_filters.ModelMultipleChoiceFilter(
@@ -294,7 +294,7 @@ class SavedFilterFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_types'
         field_name='object_types'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_types'
         field_name='object_types'
     )
     )
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
@@ -347,7 +347,7 @@ class TableConfigFilterSet(ChangeLoggedModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_type'
         field_name='object_type'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_type'
         field_name='object_type'
     )
     )
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
@@ -395,7 +395,7 @@ class TableConfigFilterSet(ChangeLoggedModelFilterSet):
 class BookmarkFilterSet(BaseFilterSet):
 class BookmarkFilterSet(BaseFilterSet):
     created = django_filters.DateTimeFilter()
     created = django_filters.DateTimeFilter()
     object_type_id = MultiValueNumberFilter()
     object_type_id = MultiValueNumberFilter()
-    object_type = ContentTypeFilter()
+    object_type = MultiValueContentTypeFilter()
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         label=_('User (ID)'),
         label=_('User (ID)'),
@@ -462,7 +462,7 @@ class ImageAttachmentFilterSet(ChangeLoggedModelFilterSet):
         method='search',
         method='search',
         label=_('Search'),
         label=_('Search'),
     )
     )
-    object_type = ContentTypeFilter()
+    object_type = MultiValueContentTypeFilter()
 
 
     class Meta:
     class Meta:
         model = ImageAttachment
         model = ImageAttachment
@@ -481,7 +481,7 @@ class ImageAttachmentFilterSet(ChangeLoggedModelFilterSet):
 @register_filterset
 @register_filterset
 class JournalEntryFilterSet(NetBoxModelFilterSet):
 class JournalEntryFilterSet(NetBoxModelFilterSet):
     created = django_filters.DateTimeFromToRangeFilter()
     created = django_filters.DateTimeFromToRangeFilter()
-    assigned_object_type = ContentTypeFilter()
+    assigned_object_type = MultiValueContentTypeFilter()
     assigned_object_type_id = django_filters.ModelMultipleChoiceFilter(
     assigned_object_type_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ContentType.objects.all()
         queryset=ContentType.objects.all()
     )
     )
@@ -576,7 +576,7 @@ class TaggedItemFilterSet(BaseFilterSet):
         method='search',
         method='search',
         label=_('Search'),
         label=_('Search'),
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='content_type'
         field_name='content_type'
     )
     )
     object_type_id = django_filters.ModelMultipleChoiceFilter(
     object_type_id = django_filters.ModelMultipleChoiceFilter(

+ 1 - 1
netbox/extras/tests/test_conditions.py

@@ -304,7 +304,7 @@ class ConditionSetTest(TestCase):
         Test Event Rule with incorrect condition (key "foo" is wrong). Must return false.
         Test Event Rule with incorrect condition (key "foo" is wrong). Must return false.
         """
         """
 
 
-        ct = ContentType.objects.get(app_label='extras', model='webhook')
+        ct = ContentType.objects.get_by_natural_key('extras', 'webhook')
         site_ct = ContentType.objects.get_for_model(Site)
         site_ct = ContentType.objects.get_for_model(Site)
         webhook = Webhook.objects.create(name='Webhook 100', payload_url='http://example.com/?1', http_method='POST')
         webhook = Webhook.objects.create(name='Webhook 100', payload_url='http://example.com/?1', http_method='POST')
         form = EventRuleForm({
         form = EventRuleForm({

+ 17 - 17
netbox/extras/tests/test_filtersets.py

@@ -111,13 +111,13 @@ class CustomFieldTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         params = {'object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
         params = {'object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
     def test_related_object_type(self):
     def test_related_object_type(self):
-        params = {'related_object_type': 'dcim.site'}
+        params = {'related_object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         params = {'related_object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
         params = {'related_object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -348,7 +348,7 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.region'}
+        params = {'object_type': ['dcim.region']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         params = {'object_type_id': [ObjectType.objects.get_for_model(Region).pk]}
         params = {'object_type_id': [ObjectType.objects.get_for_model(Region).pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -417,7 +417,7 @@ class CustomLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         params = {'object_type_id': [ObjectType.objects.get_for_model(Site).pk]}
         params = {'object_type_id': [ObjectType.objects.get_for_model(Site).pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -508,7 +508,7 @@ class SavedFilterTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         params = {'object_type_id': [ObjectType.objects.get_for_model(Site).pk]}
         params = {'object_type_id': [ObjectType.objects.get_for_model(Site).pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -600,7 +600,7 @@ class BookmarkTestCase(TestCase, BaseFilterSetTests):
         Bookmark.objects.bulk_create(bookmarks)
         Bookmark.objects.bulk_create(bookmarks)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         params = {'object_type_id': [ContentType.objects.get_for_model(Site).pk]}
         params = {'object_type_id': [ContentType.objects.get_for_model(Site).pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
@@ -663,7 +663,7 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         params = {'object_type_id': [ObjectType.objects.get_for_model(Site).pk]}
         params = {'object_type_id': [ObjectType.objects.get_for_model(Site).pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -697,8 +697,8 @@ class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTests):
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
 
 
-        site_ct = ContentType.objects.get(app_label='dcim', model='site')
-        rack_ct = ContentType.objects.get(app_label='dcim', model='rack')
+        site_ct = ContentType.objects.get_by_natural_key('dcim', 'site')
+        rack_ct = ContentType.objects.get_by_natural_key('dcim', 'rack')
 
 
         sites = (
         sites = (
             Site(name='Site 1', slug='site-1'),
             Site(name='Site 1', slug='site-1'),
@@ -757,12 +757,12 @@ class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type(self):
     def test_object_type(self):
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_object_type_id_and_object_id(self):
     def test_object_type_id_and_object_id(self):
         params = {
         params = {
-            'object_type_id': ContentType.objects.get(app_label='dcim', model='site').pk,
+            'object_type_id': ContentType.objects.get_by_natural_key('dcim', 'site').pk,
             'object_id': [Site.objects.first().pk],
             'object_id': [Site.objects.first().pk],
         }
         }
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -845,14 +845,14 @@ class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_assigned_object_type(self):
     def test_assigned_object_type(self):
-        params = {'assigned_object_type': 'dcim.site'}
+        params = {'assigned_object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
-        params = {'assigned_object_type_id': [ContentType.objects.get(app_label='dcim', model='site').pk]}
+        params = {'assigned_object_type_id': [ContentType.objects.get_by_natural_key('dcim', 'site').pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
     def test_assigned_object(self):
     def test_assigned_object(self):
         params = {
         params = {
-            'assigned_object_type': 'dcim.site',
+            'assigned_object_type': ['dcim.site'],
             'assigned_object_id': [Site.objects.first().pk],
             'assigned_object_id': [Site.objects.first().pk],
         }
         }
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1426,15 +1426,15 @@ class TaggedItemFilterSetTestCase(TestCase):
 
 
     def test_object_type(self):
     def test_object_type(self):
         object_type = ObjectType.objects.get_for_model(Site)
         object_type = ObjectType.objects.get_for_model(Site)
-        params = {'object_type': 'dcim.site'}
+        params = {'object_type': ['dcim.site']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         params = {'object_type_id': [object_type.pk]}
         params = {'object_type_id': [object_type.pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
-    def test_object_id(self):
+    def test_object(self):
         site_ids = Site.objects.values_list('pk', flat=True)
         site_ids = Site.objects.values_list('pk', flat=True)
         params = {
         params = {
-            'object_type': 'dcim.site',
+            'object_type': ['dcim.site'],
             'object_id': site_ids[:2],
             'object_id': site_ids[:2],
         }
         }
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 1 - 1
netbox/extras/tests/test_models.py

@@ -17,7 +17,7 @@ from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMac
 class ImageAttachmentTests(TestCase):
 class ImageAttachmentTests(TestCase):
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
-        cls.ct_rack = ContentType.objects.get(app_label='dcim', model='rack')
+        cls.ct_rack = ContentType.objects.get_by_natural_key('dcim', 'rack')
         cls.image_content = b''
         cls.image_content = b''
 
 
     def _stub_image_attachment(self, object_id, image_filename, name=None):
     def _stub_image_attachment(self, object_id, image_filename, name=None):

+ 1 - 1
netbox/extras/tests/test_utils.py

@@ -27,7 +27,7 @@ class ImageUploadTests(TestCase):
     def setUpTestData(cls):
     def setUpTestData(cls):
         # We only need a ContentType with model="rack" for the prefix;
         # We only need a ContentType with model="rack" for the prefix;
         # this doesn't require creating a Rack object.
         # this doesn't require creating a Rack object.
-        cls.ct_rack = ContentType.objects.get(app_label='dcim', model='rack')
+        cls.ct_rack = ContentType.objects.get_by_natural_key('dcim', 'rack')
 
 
     def _stub_instance(self, object_id=12, name=None):
     def _stub_instance(self, object_id=12, name=None):
         """
         """

+ 6 - 5
netbox/ipam/filtersets.py

@@ -16,7 +16,8 @@ from netbox.filtersets import (
 )
 )
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
 from utilities.filters import (
 from utilities.filters import (
-    ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
+    MultiValueCharFilter, MultiValueContentTypeFilter, MultiValueNumberFilter, NumericArrayFilter,
+    TreeNodeMultipleChoiceFilter,
 )
 )
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from virtualization.models import VirtualMachine, VMInterface
 from virtualization.models import VirtualMachine, VMInterface
@@ -607,7 +608,7 @@ class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFi
         to_field_name='rd',
         to_field_name='rd',
         label=_('VRF (RD)'),
         label=_('VRF (RD)'),
     )
     )
-    assigned_object_type = ContentTypeFilter()
+    assigned_object_type = MultiValueContentTypeFilter()
     device = MultiValueCharFilter(
     device = MultiValueCharFilter(
         method='filter_device',
         method='filter_device',
         field_name='name',
         field_name='name',
@@ -846,7 +847,7 @@ class FHRPGroupFilterSet(PrimaryModelFilterSet):
 
 
 @register_filterset
 @register_filterset
 class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet):
 class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet):
-    interface_type = ContentTypeFilter()
+    interface_type = MultiValueContentTypeFilter()
     group_id = django_filters.ModelMultipleChoiceFilter(
     group_id = django_filters.ModelMultipleChoiceFilter(
         queryset=FHRPGroup.objects.all(),
         queryset=FHRPGroup.objects.all(),
         label=_('Group (ID)'),
         label=_('Group (ID)'),
@@ -901,7 +902,7 @@ class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet):
 
 
 @register_filterset
 @register_filterset
 class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
 class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
-    scope_type = ContentTypeFilter()
+    scope_type = MultiValueContentTypeFilter()
     region = django_filters.NumberFilter(
     region = django_filters.NumberFilter(
         method='filter_scope'
         method='filter_scope'
     )
     )
@@ -1173,7 +1174,7 @@ class ServiceTemplateFilterSet(PrimaryModelFilterSet):
 
 
 @register_filterset
 @register_filterset
 class ServiceFilterSet(ContactModelFilterSet, PrimaryModelFilterSet):
 class ServiceFilterSet(ContactModelFilterSet, PrimaryModelFilterSet):
-    parent_object_type = ContentTypeFilter()
+    parent_object_type = MultiValueContentTypeFilter()
     device = MultiValueCharFilter(
     device = MultiValueCharFilter(
         method='filter_device',
         method='filter_device',
         field_name='name',
         field_name='name',

+ 2 - 2
netbox/ipam/tests/test_filtersets.py

@@ -1572,12 +1572,12 @@ class FHRPGroupAssignmentTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_interface_type(self):
     def test_interface_type(self):
-        params = {'interface_type': 'dcim.interface'}
+        params = {'interface_type': ['dcim.interface']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
     def test_interface(self):
     def test_interface(self):
         interfaces = Interface.objects.all()[:2]
         interfaces = Interface.objects.all()[:2]
-        params = {'interface_type': 'dcim.interface', 'interface_id': [interfaces[0].pk, interfaces[1].pk]}
+        params = {'interface_type': ['dcim.interface'], 'interface_id': [interfaces[0].pk, interfaces[1].pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_priority(self):
     def test_priority(self):

+ 2 - 2
netbox/tenancy/filtersets.py

@@ -5,7 +5,7 @@ from django.utils.translation import gettext as _
 from netbox.filtersets import (
 from netbox.filtersets import (
     NestedGroupModelFilterSet, NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
     NestedGroupModelFilterSet, NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
 )
 )
-from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
+from utilities.filters import MultiValueContentTypeFilter, TreeNodeMultipleChoiceFilter
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from .models import *
 from .models import *
 
 
@@ -110,7 +110,7 @@ class ContactAssignmentFilterSet(NetBoxModelFilterSet):
         method='search',
         method='search',
         label=_('Search'),
         label=_('Search'),
     )
     )
-    object_type = ContentTypeFilter()
+    object_type = MultiValueContentTypeFilter()
     contact_id = django_filters.ModelMultipleChoiceFilter(
     contact_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Contact.objects.all(),
         queryset=Contact.objects.all(),
         label=_('Contact (ID)'),
         label=_('Contact (ID)'),

+ 2 - 0
netbox/tenancy/tests/test_filtersets.py

@@ -355,6 +355,8 @@ class ContactAssignmentTestCase(TestCase, ChangeLoggedFilterSetTests):
         ContactAssignment.objects.bulk_create(assignments)
         ContactAssignment.objects.bulk_create(assignments)
 
 
     def test_object_type(self):
     def test_object_type(self):
+        params = {'object_type': ['dcim.site']}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         params = {'object_type_id': ObjectType.objects.get_by_natural_key('dcim', 'site')}
         params = {'object_type_id': ObjectType.objects.get_by_natural_key('dcim', 'site')}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 

+ 2 - 2
netbox/users/filtersets.py

@@ -6,7 +6,7 @@ from core.models import ObjectType
 from extras.models import NotificationGroup
 from extras.models import NotificationGroup
 from netbox.filtersets import BaseFilterSet
 from netbox.filtersets import BaseFilterSet
 from users.models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
 from users.models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
-from utilities.filters import ContentTypeFilter
+from utilities.filters import MultiValueContentTypeFilter
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 
 
 __all__ = (
 __all__ = (
@@ -194,7 +194,7 @@ class ObjectPermissionFilterSet(BaseFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='object_types'
         field_name='object_types'
     )
     )
-    object_type = ContentTypeFilter(
+    object_type = MultiValueContentTypeFilter(
         field_name='object_types'
         field_name='object_types'
     )
     )
     can_view = django_filters.BooleanFilter(
     can_view = django_filters.BooleanFilter(

+ 26 - 0
netbox/utilities/filters.py

@@ -1,6 +1,7 @@
 import django_filters
 import django_filters
 from django import forms
 from django import forms
 from django.conf import settings
 from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django_filters.constants import EMPTY_VALUES
 from django_filters.constants import EMPTY_VALUES
 from drf_spectacular.types import OpenApiTypes
 from drf_spectacular.types import OpenApiTypes
@@ -10,6 +11,7 @@ __all__ = (
     'ContentTypeFilter',
     'ContentTypeFilter',
     'MultiValueArrayFilter',
     'MultiValueArrayFilter',
     'MultiValueCharFilter',
     'MultiValueCharFilter',
+    'MultiValueContentTypeFilter',
     'MultiValueDateFilter',
     'MultiValueDateFilter',
     'MultiValueDateTimeFilter',
     'MultiValueDateTimeFilter',
     'MultiValueDecimalFilter',
     'MultiValueDecimalFilter',
@@ -171,3 +173,27 @@ class ContentTypeFilter(django_filters.CharFilter):
                 f'{self.field_name}__model': model
                 f'{self.field_name}__model': model
             }
             }
         )
         )
+
+
+class MultiValueContentTypeFilter(MultiValueCharFilter):
+    """
+    A multi-value version of ContentTypeFilter.
+    """
+    def filter(self, qs, value):
+        if value in EMPTY_VALUES:
+            return qs
+
+        content_types = []
+        for key in value:
+            try:
+                app_label, model = key.lower().split('.')
+                ct = ContentType.objects.get_by_natural_key(app_label, model)
+                content_types.append(ct)
+            except (ValueError, ContentType.DoesNotExist):
+                continue
+
+        return qs.filter(
+            **{
+                f'{self.field_name}__in': content_types,
+            }
+        )

+ 2 - 2
netbox/utilities/testing/filtersets.py

@@ -10,7 +10,7 @@ from mptt.models import MPTTModel
 from taggit.managers import TaggableManager
 from taggit.managers import TaggableManager
 
 
 from extras.filters import TagFilter
 from extras.filters import TagFilter
-from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
+from utilities.filters import MultiValueContentTypeFilter, TreeNodeMultipleChoiceFilter
 
 
 __all__ = (
 __all__ = (
     'BaseFilterSetTests',
     'BaseFilterSetTests',
@@ -75,7 +75,7 @@ class BaseFilterSetTests:
                 # Standardize on object_type for filter name even though it's technically a ContentType
                 # Standardize on object_type for filter name even though it's technically a ContentType
                 filter_name = 'object_type'
                 filter_name = 'object_type'
                 return [
                 return [
-                    (filter_name, ContentTypeFilter),
+                    (filter_name, MultiValueContentTypeFilter),
                     (f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter),
                     (f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter),
                 ]
                 ]
 
 

+ 3 - 3
netbox/vpn/filtersets.py

@@ -7,7 +7,7 @@ from dcim.models import Device, Interface
 from ipam.models import IPAddress, RouteTarget, VLAN
 from ipam.models import IPAddress, RouteTarget, VLAN
 from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
 from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
-from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
+from utilities.filters import MultiValueCharFilter, MultiValueContentTypeFilter, MultiValueNumberFilter
 from utilities.filtersets import register_filterset
 from utilities.filtersets import register_filterset
 from virtualization.models import VirtualMachine, VMInterface
 from virtualization.models import VirtualMachine, VMInterface
 from .choices import *
 from .choices import *
@@ -94,7 +94,7 @@ class TunnelTerminationFilterSet(NetBoxModelFilterSet):
     role = django_filters.MultipleChoiceFilter(
     role = django_filters.MultipleChoiceFilter(
         choices=TunnelTerminationRoleChoices
         choices=TunnelTerminationRoleChoices
     )
     )
-    termination_type = ContentTypeFilter()
+    termination_type = MultiValueContentTypeFilter()
     interface = django_filters.ModelMultipleChoiceFilter(
     interface = django_filters.ModelMultipleChoiceFilter(
         field_name='interface__name',
         field_name='interface__name',
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
@@ -445,7 +445,7 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
         queryset=ObjectType.objects.all(),
         queryset=ObjectType.objects.all(),
         field_name='assigned_object_type'
         field_name='assigned_object_type'
     )
     )
-    assigned_object_type = ContentTypeFilter()
+    assigned_object_type = MultiValueContentTypeFilter()
 
 
     class Meta:
     class Meta:
         model = L2VPNTermination
         model = L2VPNTermination

+ 3 - 3
netbox/vpn/tests/test_filtersets.py

@@ -268,9 +268,9 @@ class TunnelTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_termination_type(self):
     def test_termination_type(self):
-        params = {'termination_type': 'dcim.interface'}
+        params = {'termination_type': ['dcim.interface']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
-        params = {'termination_type': 'virtualization.vminterface'}
+        params = {'termination_type': ['virtualization.vminterface']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
     def test_interface(self):
     def test_interface(self):
@@ -902,7 +902,7 @@ class L2VPNTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
 
 
     def test_termination_type(self):
     def test_termination_type(self):
-        params = {'assigned_object_type': 'ipam.vlan'}
+        params = {'assigned_object_type': ['ipam.vlan']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
     def test_interface(self):
     def test_interface(self):

+ 1 - 1
netbox/wireless/tests/test_filtersets.py

@@ -305,7 +305,7 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
     def test_scope_type(self):
     def test_scope_type(self):
-        params = {'scope_type': 'dcim.location'}
+        params = {'scope_type': ['dcim.location']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)