Repository: netbox-community/netbox

Issue:
Add tests for created & last_updated filters for all models
### Proposed Changes

Add tests for the `created` and `last_updated` filters for all relevant models. This should be done by introducing a standardized TestCase class that can be subclassed.

### Justification

Increases test coverage
Unable to filter circuit terminations by primary key
### NetBox version

v2.11.2

### Python version

3.7

### Steps to Reproduce

1. Create at least two circuit terminations
2. Make a request to the circuit terminations REST API endpoint filtering by at least one primary key (`id`) (e.g. `GET /api/circuit/circuit-terminations/?id=123`)

### Expected Behavior

Only the circuit terminations matching the specified primary key(s) should be returned.

### Observed Behavior

The filter has no effect.
Unable to filter circuit terminations by primary key
### NetBox version

v2.11.2

### Python version

3.7

### Steps to Reproduce

1. Create at least two circuit terminations
2. Make a request to the circuit terminations REST API endpoint filtering by at least one primary key (`id`) (e.g. `GET /api/circuit/circuit-terminations/?id=123`)

### Expected Behavior

Only the circuit terminations matching the specified primary key(s) should be returned.

### Observed Behavior

The filter has no effect.


Discussion:

Resolving patch:
diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py
--- a/netbox/circuits/filtersets.py
+++ b/netbox/circuits/filtersets.py
@@ -232,7 +232,7 @@ class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFi
 
     class Meta:
         model = CircuitTermination
-        fields = ['term_side', 'port_speed', 'upstream_speed', 'xconnect_id']
+        fields = ['id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id']
 
     def search(self, queryset, name, value):
         if not value.strip():
diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py
--- a/netbox/extras/filtersets.py
+++ b/netbox/extras/filtersets.py
@@ -70,6 +70,7 @@ class Meta:
 
 
 class ImageAttachmentFilterSet(BaseFilterSet):
+    created = django_filters.DateTimeFilter()
     content_type = ContentTypeFilter()
 
     class Meta:


Test patch:
diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filtersets.py
similarity index 95%
rename from netbox/circuits/tests/test_filters.py
rename to netbox/circuits/tests/test_filtersets.py
--- a/netbox/circuits/tests/test_filters.py
+++ b/netbox/circuits/tests/test_filtersets.py
@@ -5,9 +5,10 @@
 from circuits.models import *
 from dcim.models import Cable, Region, Site, SiteGroup
 from tenancy.models import Tenant, TenantGroup
+from utilities.testing import ChangeLoggedFilterSetTests
 
 
-class ProviderTestCase(TestCase):
+class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Provider.objects.all()
     filterset = ProviderFilterSet
 
@@ -61,10 +62,6 @@ def setUpTestData(cls):
             CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A'),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Provider 1', 'Provider 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -103,7 +100,7 @@ def test_site(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class CircuitTypeTestCase(TestCase):
+class CircuitTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = CircuitType.objects.all()
     filterset = CircuitTypeFilterSet
 
@@ -116,10 +113,6 @@ def setUpTestData(cls):
             CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
         ))
 
-    def test_id(self):
-        params = {'id': [self.queryset.first().pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
-
     def test_name(self):
         params = {'name': ['Circuit Type 1']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
@@ -129,7 +122,7 @@ def test_slug(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class CircuitTestCase(TestCase):
+class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Circuit.objects.all()
     filterset = CircuitFilterSet
 
@@ -213,10 +206,6 @@ def setUpTestData(cls):
         ))
         CircuitTermination.objects.bulk_create(circuit_terminations)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_cid(self):
         params = {'cid': ['Test Circuit 1', 'Test Circuit 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -288,7 +277,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class CircuitTerminationTestCase(TestCase):
+class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = CircuitTermination.objects.all()
     filterset = CircuitTerminationFilterSet
 
@@ -382,7 +371,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ProviderNetworkTestCase(TestCase):
+class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ProviderNetwork.objects.all()
     filterset = ProviderNetworkFilterSet
 
@@ -403,10 +392,6 @@ def setUpTestData(cls):
         )
         ProviderNetwork.objects.bulk_create(provider_networks)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Provider Network 1', 'Provider Network 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filtersets.py
similarity index 95%
rename from netbox/dcim/tests/test_filters.py
rename to netbox/dcim/tests/test_filtersets.py
--- a/netbox/dcim/tests/test_filters.py
+++ b/netbox/dcim/tests/test_filtersets.py
@@ -6,10 +6,11 @@
 from dcim.models import *
 from ipam.models import IPAddress
 from tenancy.models import Tenant, TenantGroup
+from utilities.testing import ChangeLoggedFilterSetTests
 from virtualization.models import Cluster, ClusterType
 
 
-class RegionTestCase(TestCase):
+class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Region.objects.all()
     filterset = RegionFilterSet
 
@@ -35,10 +36,6 @@ def setUpTestData(cls):
         for region in child_regions:
             region.save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Region 1', 'Region 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -59,7 +56,7 @@ def test_parent(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class SiteGroupTestCase(TestCase):
+class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = SiteGroup.objects.all()
     filterset = SiteGroupFilterSet
 
@@ -85,10 +82,6 @@ def setUpTestData(cls):
         for sitegroup in child_sitegroups:
             sitegroup.save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Site Group 1', 'Site Group 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -109,7 +102,7 @@ def test_parent(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class SiteTestCase(TestCase):
+class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Site.objects.all()
     filterset = SiteFilterSet
 
@@ -154,10 +147,6 @@ def setUpTestData(cls):
         )
         Site.objects.bulk_create(sites)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Site 1', 'Site 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -227,7 +216,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class LocationTestCase(TestCase):
+class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Location.objects.all()
     filterset = LocationFilterSet
 
@@ -273,10 +262,6 @@ def setUpTestData(cls):
         for location in locations:
             location.save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Location 1', 'Location 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -318,7 +303,7 @@ def test_parent(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class RackRoleTestCase(TestCase):
+class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = RackRole.objects.all()
     filterset = RackRoleFilterSet
 
@@ -332,10 +317,6 @@ def setUpTestData(cls):
         )
         RackRole.objects.bulk_create(rack_roles)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Rack Role 1', 'Rack Role 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -349,7 +330,7 @@ def test_color(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class RackTestCase(TestCase):
+class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Rack.objects.all()
     filterset = RackFilterSet
 
@@ -416,10 +397,6 @@ def setUpTestData(cls):
         )
         Rack.objects.bulk_create(racks)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Rack 1', 'Rack 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -523,7 +500,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class RackReservationTestCase(TestCase):
+class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = RackReservation.objects.all()
     filterset = RackReservationFilterSet
 
@@ -581,10 +558,6 @@ def setUpTestData(cls):
         )
         RackReservation.objects.bulk_create(reservations)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_site(self):
         sites = Site.objects.all()[:2]
         params = {'site_id': [sites[0].pk, sites[1].pk]}
@@ -621,7 +594,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ManufacturerTestCase(TestCase):
+class ManufacturerTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Manufacturer.objects.all()
     filterset = ManufacturerFilterSet
 
@@ -635,10 +608,6 @@ def setUpTestData(cls):
         )
         Manufacturer.objects.bulk_create(manufacturers)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Manufacturer 1', 'Manufacturer 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -652,7 +621,7 @@ def test_description(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class DeviceTypeTestCase(TestCase):
+class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = DeviceType.objects.all()
     filterset = DeviceTypeFilterSet
 
@@ -708,10 +677,6 @@ def setUpTestData(cls):
             DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_model(self):
         params = {'model': ['Model 1', 'Model 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -788,7 +753,7 @@ def test_device_bays(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class ConsolePortTemplateTestCase(TestCase):
+class ConsolePortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ConsolePortTemplate.objects.all()
     filterset = ConsolePortTemplateFilterSet
 
@@ -810,10 +775,6 @@ def setUpTestData(cls):
             ConsolePortTemplate(device_type=device_types[2], name='Console Port 3'),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Console Port 1', 'Console Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -824,7 +785,7 @@ def test_devicetype_id(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ConsoleServerPortTemplateTestCase(TestCase):
+class ConsoleServerPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ConsoleServerPortTemplate.objects.all()
     filterset = ConsoleServerPortTemplateFilterSet
 
@@ -846,10 +807,6 @@ def setUpTestData(cls):
             ConsoleServerPortTemplate(device_type=device_types[2], name='Console Server Port 3'),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -860,7 +817,7 @@ def test_devicetype_id(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class PowerPortTemplateTestCase(TestCase):
+class PowerPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = PowerPortTemplate.objects.all()
     filterset = PowerPortTemplateFilterSet
 
@@ -882,10 +839,6 @@ def setUpTestData(cls):
             PowerPortTemplate(device_type=device_types[2], name='Power Port 3', maximum_draw=300, allocated_draw=150),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Power Port 1', 'Power Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -904,7 +857,7 @@ def test_allocated_draw(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class PowerOutletTemplateTestCase(TestCase):
+class PowerOutletTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = PowerOutletTemplate.objects.all()
     filterset = PowerOutletTemplateFilterSet
 
@@ -926,10 +879,6 @@ def setUpTestData(cls):
             PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -944,7 +893,7 @@ def test_feed_leg(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class InterfaceTemplateTestCase(TestCase):
+class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = InterfaceTemplate.objects.all()
     filterset = InterfaceTemplateFilterSet
 
@@ -966,10 +915,6 @@ def setUpTestData(cls):
             InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Interface 1', 'Interface 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -990,7 +935,7 @@ def test_mgmt_only(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class FrontPortTemplateTestCase(TestCase):
+class FrontPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = FrontPortTemplate.objects.all()
     filterset = FrontPortTemplateFilterSet
 
@@ -1019,10 +964,6 @@ def setUpTestData(cls):
             FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PortTypeChoices.TYPE_BNC),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Front Port 1', 'Front Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1037,7 +978,7 @@ def test_type(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class RearPortTemplateTestCase(TestCase):
+class RearPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = RearPortTemplate.objects.all()
     filterset = RearPortTemplateFilterSet
 
@@ -1059,10 +1000,6 @@ def setUpTestData(cls):
             RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Rear Port 1', 'Rear Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1081,7 +1018,7 @@ def test_positions(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class DeviceBayTemplateTestCase(TestCase):
+class DeviceBayTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = DeviceBayTemplate.objects.all()
     filterset = DeviceBayTemplateFilterSet
 
@@ -1103,10 +1040,6 @@ def setUpTestData(cls):
             DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3'),
         ))
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Device Bay 1', 'Device Bay 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1117,7 +1050,7 @@ def test_devicetype_id(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class DeviceRoleTestCase(TestCase):
+class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = DeviceRole.objects.all()
     filterset = DeviceRoleFilterSet
 
@@ -1131,10 +1064,6 @@ def setUpTestData(cls):
         )
         DeviceRole.objects.bulk_create(device_roles)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Device Role 1', 'Device Role 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1154,7 +1083,7 @@ def test_vm_role(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class PlatformTestCase(TestCase):
+class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Platform.objects.all()
     filterset = PlatformFilterSet
 
@@ -1175,10 +1104,6 @@ def setUpTestData(cls):
         )
         Platform.objects.bulk_create(platforms)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Platform 1', 'Platform 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1203,7 +1128,7 @@ def test_manufacturer(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class DeviceTestCase(TestCase):
+class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Device.objects.all()
     filterset = DeviceFilterSet
 
@@ -1356,10 +1281,6 @@ def setUpTestData(cls):
         Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1)
         Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Device 1', 'Device 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1549,7 +1470,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ConsolePortTestCase(TestCase):
+class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ConsolePort.objects.all()
     filterset = ConsolePortFilterSet
 
@@ -1608,10 +1529,6 @@ def setUpTestData(cls):
         Cable(termination_a=console_ports[1], termination_b=console_server_ports[1]).save()
         # Third port is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Console Port 1', 'Console Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1665,7 +1582,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class ConsoleServerPortTestCase(TestCase):
+class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ConsoleServerPort.objects.all()
     filterset = ConsoleServerPortFilterSet
 
@@ -1724,10 +1641,6 @@ def setUpTestData(cls):
         Cable(termination_a=console_server_ports[1], termination_b=console_ports[1]).save()
         # Third port is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1781,7 +1694,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class PowerPortTestCase(TestCase):
+class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = PowerPort.objects.all()
     filterset = PowerPortFilterSet
 
@@ -1840,10 +1753,6 @@ def setUpTestData(cls):
         Cable(termination_a=power_ports[1], termination_b=power_outlets[1]).save()
         # Third port is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Power Port 1', 'Power Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1905,7 +1814,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class PowerOutletTestCase(TestCase):
+class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = PowerOutlet.objects.all()
     filterset = PowerOutletFilterSet
 
@@ -1964,10 +1873,6 @@ def setUpTestData(cls):
         Cable(termination_a=power_outlets[1], termination_b=power_ports[1]).save()
         # Third port is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2025,7 +1930,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class InterfaceTestCase(TestCase):
+class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Interface.objects.all()
     filterset = InterfaceFilterSet
 
@@ -2081,10 +1986,6 @@ def setUpTestData(cls):
         Cable(termination_a=interfaces[1], termination_b=interfaces[4]).save()
         # Third pair is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Interface 1', 'Interface 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2200,7 +2101,7 @@ def test_type(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class FrontPortTestCase(TestCase):
+class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = FrontPort.objects.all()
     filterset = FrontPortFilterSet
 
@@ -2266,10 +2167,6 @@ def setUpTestData(cls):
         Cable(termination_a=front_ports[1], termination_b=front_ports[4]).save()
         # Third port is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Front Port 1', 'Front Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2321,7 +2218,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class RearPortTestCase(TestCase):
+class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = RearPort.objects.all()
     filterset = RearPortFilterSet
 
@@ -2377,10 +2274,6 @@ def setUpTestData(cls):
         Cable(termination_a=rear_ports[1], termination_b=rear_ports[4]).save()
         # Third port is not connected
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Rear Port 1', 'Rear Port 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2436,7 +2329,7 @@ def test_cabled(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class DeviceBayTestCase(TestCase):
+class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = DeviceBay.objects.all()
     filterset = DeviceBayFilterSet
 
@@ -2483,10 +2376,6 @@ def setUpTestData(cls):
         )
         DeviceBay.objects.bulk_create(device_bays)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Device Bay 1', 'Device Bay 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2528,7 +2417,7 @@ def test_device(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class InventoryItemTestCase(TestCase):
+class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = InventoryItem.objects.all()
     filterset = InventoryItemFilterSet
 
@@ -2591,10 +2480,6 @@ def setUpTestData(cls):
         for i in child_inventory_items:
             i.save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Inventory Item 1', 'Inventory Item 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2666,7 +2551,7 @@ def test_serial(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class VirtualChassisTestCase(TestCase):
+class VirtualChassisTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = VirtualChassis.objects.all()
     filterset = VirtualChassisFilterSet
 
@@ -2721,10 +2606,6 @@ def setUpTestData(cls):
         Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=virtual_chassis[1])
         Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[2])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_domain(self):
         params = {'domain': ['Domain 1', 'Domain 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2762,7 +2643,7 @@ def test_site(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class CableTestCase(TestCase):
+class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Cable.objects.all()
     filterset = CableFilterSet
 
@@ -2827,10 +2708,6 @@ def setUpTestData(cls):
         Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
         Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_label(self):
         params = {'label': ['Cable 1', 'Cable 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2886,7 +2763,7 @@ def test_tenant(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class PowerPanelTestCase(TestCase):
+class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = PowerPanel.objects.all()
     filterset = PowerPanelFilterSet
 
@@ -2931,10 +2808,6 @@ def setUpTestData(cls):
         )
         PowerPanel.objects.bulk_create(power_panels)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Power Panel 1', 'Power Panel 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -2966,7 +2839,7 @@ def test_location(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class PowerFeedTestCase(TestCase):
+class PowerFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = PowerFeed.objects.all()
     filterset = PowerFeedFilterSet
 
@@ -3029,10 +2902,6 @@ def setUpTestData(cls):
         Cable(termination_a=power_feeds[0], termination_b=power_ports[0]).save()
         Cable(termination_a=power_feeds[1], termination_b=power_ports[1]).save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Power Feed 1', 'Power Feed 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filtersets.py
similarity index 94%
rename from netbox/extras/tests/test_filters.py
rename to netbox/extras/tests/test_filtersets.py
--- a/netbox/extras/tests/test_filters.py
+++ b/netbox/extras/tests/test_filtersets.py
@@ -1,4 +1,5 @@
 import uuid
+from datetime import datetime, timezone
 
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
@@ -10,10 +11,11 @@
 from extras.models import *
 from ipam.models import IPAddress
 from tenancy.models import Tenant, TenantGroup
+from utilities.testing import BaseFilterSetTests, ChangeLoggedFilterSetTests
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 
 
-class WebhookTestCase(TestCase):
+class WebhookTestCase(TestCase, BaseFilterSetTests):
     queryset = Webhook.objects.all()
     filterset = WebhookFilterSet
 
@@ -52,10 +54,6 @@ def setUpTestData(cls):
         webhooks[1].content_types.add(content_types[1])
         webhooks[2].content_types.add(content_types[2])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Webhook 1', 'Webhook 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -89,7 +87,7 @@ def test_ssl_verification(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class CustomLinkTestCase(TestCase):
+class CustomLinkTestCase(TestCase, BaseFilterSetTests):
     queryset = CustomLink.objects.all()
     filterset = CustomLinkFilterSet
 
@@ -125,10 +123,6 @@ def setUpTestData(cls):
         )
         CustomLink.objects.bulk_create(custom_links)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Custom Link 1', 'Custom Link 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -148,7 +142,7 @@ def test_new_window(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class ExportTemplateTestCase(TestCase):
+class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
     queryset = ExportTemplate.objects.all()
     filterset = ExportTemplateFilterSet
 
@@ -164,10 +158,6 @@ def setUpTestData(cls):
         )
         ExportTemplate.objects.bulk_create(export_templates)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Export Template 1', 'Export Template 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -177,7 +167,7 @@ def test_content_type(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class ImageAttachmentTestCase(TestCase):
+class ImageAttachmentTestCase(TestCase, BaseFilterSetTests):
     queryset = ImageAttachment.objects.all()
     filterset = ImageAttachmentFilterSet
 
@@ -235,10 +225,6 @@ def setUpTestData(cls):
         )
         ImageAttachment.objects.bulk_create(image_attachments)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Image Attachment 1', 'Image Attachment 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -254,8 +240,14 @@ def test_content_type_id_and_object_id(self):
         }
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
+    def test_created(self):
+        pk_list = self.queryset.values_list('pk', flat=True)[:2]
+        self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
+        params = {'created': '2021-01-01T00:00:00'}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
-class JournalEntryTestCase(TestCase):
+
+class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = JournalEntry.objects.all()
     filterset = JournalEntryFilterSet
 
@@ -320,10 +312,6 @@ def setUpTestData(cls):
         )
         JournalEntry.objects.bulk_create(journal_entries)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_created_by(self):
         users = User.objects.filter(username__in=['Alice', 'Bob'])
         params = {'created_by': [users[0].username, users[1].username]}
@@ -348,8 +336,17 @@ def test_kind(self):
         params = {'kind': [JournalEntryKindChoices.KIND_INFO, JournalEntryKindChoices.KIND_SUCCESS]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
+    def test_created(self):
+        pk_list = self.queryset.values_list('pk', flat=True)[:2]
+        self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
+        params = {
+            'created_after': '2020-12-31T00:00:00',
+            'created_before': '2021-01-02T00:00:00',
+        }
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
 
-class ConfigContextTestCase(TestCase):
+class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ConfigContext.objects.all()
     filterset = ConfigContextFilterSet
 
@@ -449,10 +446,6 @@ def setUpTestData(cls):
             c.tenant_groups.set([tenant_groups[i]])
             c.tenants.set([tenants[i]])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Config Context 1', 'Config Context 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -530,7 +523,7 @@ def test_tenant_(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class TagTestCase(TestCase):
+class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Tag.objects.all()
     filterset = TagFilterSet
 
@@ -544,10 +537,6 @@ def setUpTestData(cls):
         )
         Tag.objects.bulk_create(tags)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Tag 1', 'Tag 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -561,7 +550,7 @@ def test_color(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ObjectChangeTestCase(TestCase):
+class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
     queryset = ObjectChange.objects.all()
     filterset = ObjectChangeFilterSet
 
@@ -635,10 +624,6 @@ def setUpTestData(cls):
         )
         ObjectChange.objects.bulk_create(object_changes)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:3]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
-
     def test_user(self):
         params = {'user_id': User.objects.filter(username__in=['user1', 'user2']).values_list('pk', flat=True)}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filtersets.py
similarity index 96%
rename from netbox/ipam/tests/test_filters.py
rename to netbox/ipam/tests/test_filtersets.py
--- a/netbox/ipam/tests/test_filters.py
+++ b/netbox/ipam/tests/test_filtersets.py
@@ -4,11 +4,12 @@
 from ipam.choices import *
 from ipam.filtersets import *
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
+from utilities.testing import ChangeLoggedFilterSetTests
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from tenancy.models import Tenant, TenantGroup
 
 
-class VRFTestCase(TestCase):
+class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = VRF.objects.all()
     filterset = VRFFilterSet
 
@@ -53,10 +54,6 @@ def setUpTestData(cls):
         vrfs[2].import_targets.add(route_targets[2])
         vrfs[2].export_targets.add(route_targets[2])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['VRF 1', 'VRF 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -100,7 +97,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class RouteTargetTestCase(TestCase):
+class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = RouteTarget.objects.all()
     filterset = RouteTargetFilterSet
 
@@ -149,10 +146,6 @@ def setUpTestData(cls):
         vrfs[1].import_targets.add(route_targets[4], route_targets[5])
         vrfs[1].export_targets.add(route_targets[6], route_targets[7])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['65000:1001', '65000:1002', '65000:1003']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
@@ -186,7 +179,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
 
 
-class RIRTestCase(TestCase):
+class RIRTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = RIR.objects.all()
     filterset = RIRFilterSet
 
@@ -203,10 +196,6 @@ def setUpTestData(cls):
         )
         RIR.objects.bulk_create(rirs)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['RIR 1', 'RIR 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -226,7 +215,7 @@ def test_is_private(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
-class AggregateTestCase(TestCase):
+class AggregateTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Aggregate.objects.all()
     filterset = AggregateFilterSet
 
@@ -265,10 +254,6 @@ def setUpTestData(cls):
         )
         Aggregate.objects.bulk_create(aggregates)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_family(self):
         params = {'family': '4'}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
@@ -304,7 +289,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class RoleTestCase(TestCase):
+class RoleTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Role.objects.all()
     filterset = RoleFilterSet
 
@@ -318,10 +303,6 @@ def setUpTestData(cls):
         )
         Role.objects.bulk_create(roles)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Role 1', 'Role 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -331,7 +312,7 @@ def test_slug(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class PrefixTestCase(TestCase):
+class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Prefix.objects.all()
     filterset = PrefixFilterSet
 
@@ -421,10 +402,6 @@ def setUpTestData(cls):
         )
         Prefix.objects.bulk_create(prefixes)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_family(self):
         params = {'family': '6'}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
@@ -528,7 +505,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class IPAddressTestCase(TestCase):
+class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = IPAddress.objects.all()
     filterset = IPAddressFilterSet
 
@@ -607,10 +584,6 @@ def setUpTestData(cls):
         )
         IPAddress.objects.bulk_create(ipaddresses)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_family(self):
         params = {'family': '6'}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
@@ -708,7 +681,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
-class VLANGroupTestCase(TestCase):
+class VLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = VLANGroup.objects.all()
     filterset = VLANGroupFilterSet
 
@@ -751,10 +724,6 @@ def setUpTestData(cls):
         )
         VLANGroup.objects.bulk_create(vlan_groups)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['VLAN Group 1', 'VLAN Group 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -796,7 +765,7 @@ def test_cluster(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
-class VLANTestCase(TestCase):
+class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = VLAN.objects.all()
     filterset = VLANFilterSet
 
@@ -965,10 +934,6 @@ def setUpTestData(cls):
         )
         VLAN.objects.bulk_create(vlans)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['VLAN 101', 'VLAN 102']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -1041,7 +1006,7 @@ def test_available_on_virtualmachine(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)  # 5 scoped + 1 global
 
 
-class ServiceTestCase(TestCase):
+class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Service.objects.all()
     filterset = ServiceFilterSet
 
@@ -1080,10 +1045,6 @@ def setUpTestData(cls):
         )
         Service.objects.bulk_create(services)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:3]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
-
     def test_name(self):
         params = {'name': ['Service 1', 'Service 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/secrets/tests/test_filters.py b/netbox/secrets/tests/test_filtersets.py
similarity index 91%
rename from netbox/secrets/tests/test_filters.py
rename to netbox/secrets/tests/test_filtersets.py
--- a/netbox/secrets/tests/test_filters.py
+++ b/netbox/secrets/tests/test_filtersets.py
@@ -3,10 +3,11 @@
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from secrets.filtersets import *
 from secrets.models import Secret, SecretRole
+from utilities.testing import ChangeLoggedFilterSetTests
 from virtualization.models import Cluster, ClusterType, VirtualMachine
 
 
-class SecretRoleTestCase(TestCase):
+class SecretRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = SecretRole.objects.all()
     filterset = SecretRoleFilterSet
 
@@ -20,10 +21,6 @@ def setUpTestData(cls):
         )
         SecretRole.objects.bulk_create(roles)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Secret Role 1', 'Secret Role 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -33,7 +30,7 @@ def test_slug(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class SecretTestCase(TestCase):
+class SecretTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Secret.objects.all()
     filterset = SecretFilterSet
 
@@ -80,10 +77,6 @@ def setUpTestData(cls):
         for s in secrets:
             s.save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Secret 1', 'Secret 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/tenancy/tests/test_filters.py b/netbox/tenancy/tests/test_filtersets.py
similarity index 89%
rename from netbox/tenancy/tests/test_filters.py
rename to netbox/tenancy/tests/test_filtersets.py
--- a/netbox/tenancy/tests/test_filters.py
+++ b/netbox/tenancy/tests/test_filtersets.py
@@ -2,9 +2,10 @@
 
 from tenancy.filtersets import *
 from tenancy.models import Tenant, TenantGroup
+from utilities.testing import ChangeLoggedFilterSetTests
 
 
-class TenantGroupTestCase(TestCase):
+class TenantGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = TenantGroup.objects.all()
     filterset = TenantGroupFilterSet
 
@@ -27,10 +28,6 @@ def setUpTestData(cls):
         for tenantgroup in tenant_groups:
             tenantgroup.save()
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Tenant Group 1', 'Tenant Group 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -51,7 +48,7 @@ def test_parent(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class TenantTestCase(TestCase):
+class TenantTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Tenant.objects.all()
     filterset = TenantFilterSet
 
@@ -73,10 +70,6 @@ def setUpTestData(cls):
         )
         Tenant.objects.bulk_create(tenants)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Tenant 1', 'Tenant 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/users/tests/test_filters.py b/netbox/users/tests/test_filtersets.py
similarity index 91%
rename from netbox/users/tests/test_filters.py
rename to netbox/users/tests/test_filtersets.py
--- a/netbox/users/tests/test_filters.py
+++ b/netbox/users/tests/test_filtersets.py
@@ -4,9 +4,10 @@
 
 from users.filtersets import GroupFilterSet, ObjectPermissionFilterSet, UserFilterSet
 from users.models import ObjectPermission
+from utilities.testing import BaseFilterSetTests
 
 
-class UserTestCase(TestCase):
+class UserTestCase(TestCase, BaseFilterSetTests):
     queryset = User.objects.all()
     filterset = UserFilterSet
 
@@ -59,10 +60,6 @@ def setUpTestData(cls):
         users[1].groups.set([groups[1]])
         users[2].groups.set([groups[2]])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_username(self):
         params = {'username': ['User1', 'User2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -95,7 +92,7 @@ def test_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class GroupTestCase(TestCase):
+class GroupTestCase(TestCase, BaseFilterSetTests):
     queryset = Group.objects.all()
     filterset = GroupFilterSet
 
@@ -109,16 +106,12 @@ def setUpTestData(cls):
         )
         Group.objects.bulk_create(groups)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Group 1', 'Group 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ObjectPermissionTestCase(TestCase):
+class ObjectPermissionTestCase(TestCase, BaseFilterSetTests):
     queryset = ObjectPermission.objects.all()
     filterset = ObjectPermissionFilterSet
 
@@ -160,10 +153,6 @@ def setUpTestData(cls):
             permissions[i].users.set([users[i]])
             permissions[i].object_types.set([object_types[i]])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Permission 1', 'Permission 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/utilities/testing/__init__.py b/netbox/utilities/testing/__init__.py
--- a/netbox/utilities/testing/__init__.py
+++ b/netbox/utilities/testing/__init__.py
@@ -1,4 +1,5 @@
 from .api import *
 from .base import *
+from .filtersets import *
 from .utils import *
 from .views import *
diff --git a/netbox/utilities/testing/filtersets.py b/netbox/utilities/testing/filtersets.py
new file mode 100644
--- /dev/null
+++ b/netbox/utilities/testing/filtersets.py
@@ -0,0 +1,35 @@
+from datetime import date, datetime, timezone
+
+
+__all__ = (
+    'BaseFilterSetTests',
+    'ChangeLoggedFilterSetTests',
+)
+
+
+class BaseFilterSetTests:
+    queryset = None
+    filterset = None
+
+    def test_id(self):
+        """
+        Test filtering for two PKs from a set of >2 objects.
+        """
+        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
+        self.assertGreater(self.queryset.count(), 2)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class ChangeLoggedFilterSetTests(BaseFilterSetTests):
+
+    def test_created(self):
+        pk_list = self.queryset.values_list('pk', flat=True)[:2]
+        self.queryset.filter(pk__in=pk_list).update(created=date(2021, 1, 1))
+        params = {'created': '2021-01-01'}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+    def test_last_updated(self):
+        pk_list = self.queryset.values_list('pk', flat=True)[:2]
+        self.queryset.filter(pk__in=pk_list).update(last_updated=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
+        params = {'last_updated': '2021-01-01T00:00:00'}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/virtualization/tests/test_filters.py b/netbox/virtualization/tests/test_filtersets.py
similarity index 95%
rename from netbox/virtualization/tests/test_filters.py
rename to netbox/virtualization/tests/test_filtersets.py
--- a/netbox/virtualization/tests/test_filters.py
+++ b/netbox/virtualization/tests/test_filtersets.py
@@ -3,12 +3,13 @@
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from ipam.models import IPAddress
 from tenancy.models import Tenant, TenantGroup
+from utilities.testing import ChangeLoggedFilterSetTests
 from virtualization.choices import *
 from virtualization.filtersets import *
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
-class ClusterTypeTestCase(TestCase):
+class ClusterTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ClusterType.objects.all()
     filterset = ClusterTypeFilterSet
 
@@ -22,10 +23,6 @@ def setUpTestData(cls):
         )
         ClusterType.objects.bulk_create(cluster_types)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Cluster Type 1', 'Cluster Type 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -39,7 +36,7 @@ def test_description(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ClusterGroupTestCase(TestCase):
+class ClusterGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = ClusterGroup.objects.all()
     filterset = ClusterGroupFilterSet
 
@@ -53,10 +50,6 @@ def setUpTestData(cls):
         )
         ClusterGroup.objects.bulk_create(cluster_groups)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Cluster Group 1', 'Cluster Group 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -70,7 +63,7 @@ def test_description(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class ClusterTestCase(TestCase):
+class ClusterTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = Cluster.objects.all()
     filterset = ClusterFilterSet
 
@@ -136,10 +129,6 @@ def setUpTestData(cls):
         )
         Cluster.objects.bulk_create(clusters)
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Cluster 1', 'Cluster 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -194,7 +183,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class VirtualMachineTestCase(TestCase):
+class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = VirtualMachine.objects.all()
     filterset = VirtualMachineFilterSet
 
@@ -297,10 +286,6 @@ def setUpTestData(cls):
         VirtualMachine.objects.filter(pk=vms[0].pk).update(primary_ip4=ipaddresses[0])
         VirtualMachine.objects.filter(pk=vms[1].pk).update(primary_ip4=ipaddresses[1])
 
-    def test_id(self):
-        params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Virtual Machine 1', 'Virtual Machine 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -409,7 +394,7 @@ def test_tenant_group(self):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-class VMInterfaceTestCase(TestCase):
+class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = VMInterface.objects.all()
     filterset = VMInterfaceFilterSet
 
@@ -444,11 +429,6 @@ def setUpTestData(cls):
         )
         VMInterface.objects.bulk_create(interfaces)
 
-    def test_id(self):
-        id_list = self.queryset.values_list('id', flat=True)[:2]
-        params = {'id': [str(id) for id in id_list]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
     def test_name(self):
         params = {'name': ['Interface 1', 'Interface 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)