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

Establish standard test cases for all models

Jeremy Stretch 6 лет назад
Родитель
Сommit
3b1128f8f3

+ 4 - 9
netbox/circuits/tests/test_views.py

@@ -2,10 +2,10 @@ import datetime
 
 from circuits.choices import *
 from circuits.models import Circuit, CircuitType, Provider
-from utilities.testing import StandardTestCases
+from utilities.testing import ViewTestCases
 
 
-class ProviderTestCase(StandardTestCases.Views):
+class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Provider
 
     @classmethod
@@ -46,14 +46,9 @@ class ProviderTestCase(StandardTestCases.Views):
         }
 
 
-class CircuitTypeTestCase(StandardTestCases.Views):
+class CircuitTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = CircuitType
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -77,7 +72,7 @@ class CircuitTypeTestCase(StandardTestCases.Views):
         )
 
 
-class CircuitTestCase(StandardTestCases.Views):
+class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Circuit
 
     @classmethod

+ 36 - 195
netbox/dcim/tests/test_views.py

@@ -11,7 +11,7 @@ from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
 from ipam.models import VLAN
-from utilities.testing import StandardTestCases
+from utilities.testing import ViewTestCases
 
 
 def create_test_device(name):
@@ -27,14 +27,9 @@ def create_test_device(name):
     return device
 
 
-class RegionTestCase(StandardTestCases.Views):
+class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = Region
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -61,7 +56,7 @@ class RegionTestCase(StandardTestCases.Views):
         )
 
 
-class SiteTestCase(StandardTestCases.Views):
+class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Site
 
     @classmethod
@@ -118,14 +113,9 @@ class SiteTestCase(StandardTestCases.Views):
         }
 
 
-class RackGroupTestCase(StandardTestCases.Views):
+class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = RackGroup
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -152,14 +142,9 @@ class RackGroupTestCase(StandardTestCases.Views):
         )
 
 
-class RackRoleTestCase(StandardTestCases.Views):
+class RackRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = RackRole
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -184,7 +169,7 @@ class RackRoleTestCase(StandardTestCases.Views):
         )
 
 
-class RackReservationTestCase(StandardTestCases.Views):
+class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = RackReservation
 
     # Disable inapplicable tests
@@ -226,7 +211,7 @@ class RackReservationTestCase(StandardTestCases.Views):
         }
 
 
-class RackTestCase(StandardTestCases.Views):
+class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Rack
 
     @classmethod
@@ -302,14 +287,9 @@ class RackTestCase(StandardTestCases.Views):
         }
 
 
-class ManufacturerTestCase(StandardTestCases.Views):
+class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = Manufacturer
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -332,7 +312,7 @@ class ManufacturerTestCase(StandardTestCases.Views):
         )
 
 
-class DeviceTypeTestCase(StandardTestCases.Views):
+class DeviceTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = DeviceType
 
     @classmethod
@@ -528,18 +508,9 @@ device-bays:
 # DeviceType components
 #
 
-class ConsolePortTemplateTestCase(StandardTestCases.Views):
+class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = ConsolePortTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -572,18 +543,9 @@ class ConsolePortTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class ConsoleServerPortTemplateTestCase(StandardTestCases.Views):
+class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = ConsoleServerPortTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -616,18 +578,9 @@ class ConsoleServerPortTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class PowerPortTemplateTestCase(StandardTestCases.Views):
+class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = PowerPortTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -666,18 +619,9 @@ class PowerPortTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class PowerOutletTemplateTestCase(StandardTestCases.Views):
+class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = PowerOutletTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -716,18 +660,9 @@ class PowerOutletTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class InterfaceTemplateTestCase(StandardTestCases.Views):
+class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = InterfaceTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -763,18 +698,9 @@ class InterfaceTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class FrontPortTemplateTestCase(StandardTestCases.Views):
+class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = FrontPortTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -818,18 +744,9 @@ class FrontPortTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class RearPortTemplateTestCase(StandardTestCases.Views):
+class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = RearPortTemplate
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -864,19 +781,12 @@ class RearPortTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class DeviceBayTemplateTestCase(StandardTestCases.Views):
+class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
     model = DeviceBayTemplate
 
     # Disable inapplicable views
-    test_get_object = None
-    test_list_objects = None
-    test_create_object = None
-    test_import_objects = None
     test_bulk_edit_objects = None
 
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@@ -903,14 +813,9 @@ class DeviceBayTemplateTestCase(StandardTestCases.Views):
         }
 
 
-class DeviceRoleTestCase(StandardTestCases.Views):
+class DeviceRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = DeviceRole
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -936,14 +841,9 @@ class DeviceRoleTestCase(StandardTestCases.Views):
         )
 
 
-class PlatformTestCase(StandardTestCases.Views):
+class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = Platform
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -971,7 +871,7 @@ class PlatformTestCase(StandardTestCases.Views):
         )
 
 
-class DeviceTestCase(StandardTestCases.Views):
+class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Device
 
     @classmethod
@@ -1056,16 +956,9 @@ class DeviceTestCase(StandardTestCases.Views):
         }
 
 
-class ConsolePortTestCase(StandardTestCases.Views):
+class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = ConsolePort
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1105,16 +998,9 @@ class ConsolePortTestCase(StandardTestCases.Views):
         )
 
 
-class ConsoleServerPortTestCase(StandardTestCases.Views):
+class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = ConsoleServerPort
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1155,16 +1041,9 @@ class ConsoleServerPortTestCase(StandardTestCases.Views):
         )
 
 
-class PowerPortTestCase(StandardTestCases.Views):
+class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = PowerPort
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1210,16 +1089,9 @@ class PowerPortTestCase(StandardTestCases.Views):
         )
 
 
-class PowerOutletTestCase(StandardTestCases.Views):
+class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = PowerOutlet
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1272,15 +1144,12 @@ class PowerOutletTestCase(StandardTestCases.Views):
         )
 
 
-class InterfaceTestCase(StandardTestCases.Views):
+class InterfaceTestCase(
+    ViewTestCases.GetObjectViewTestCase,
+    ViewTestCases.DeviceComponentViewTestCase,
+):
     model = Interface
 
-    # Disable inapplicable views
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1356,16 +1225,9 @@ class InterfaceTestCase(StandardTestCases.Views):
         )
 
 
-class FrontPortTestCase(StandardTestCases.Views):
+class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = FrontPort
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1420,16 +1282,9 @@ class FrontPortTestCase(StandardTestCases.Views):
         )
 
 
-class RearPortTestCase(StandardTestCases.Views):
+class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = RearPort
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1471,19 +1326,12 @@ class RearPortTestCase(StandardTestCases.Views):
         )
 
 
-class DeviceBayTestCase(StandardTestCases.Views):
+class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = DeviceBay
 
     # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    # TODO
     test_bulk_edit_objects = None
 
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device1 = create_test_device('Device 1')
@@ -1520,16 +1368,9 @@ class DeviceBayTestCase(StandardTestCases.Views):
         )
 
 
-class InventoryItemTestCase(StandardTestCases.Views):
+class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = InventoryItem
 
-    # Disable inapplicable views
-    test_get_object = None
-    test_create_object = None
-
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     @classmethod
     def setUpTestData(cls):
         device = create_test_device('Device 1')
@@ -1581,7 +1422,7 @@ class InventoryItemTestCase(StandardTestCases.Views):
         )
 
 
-class CableTestCase(StandardTestCases.Views):
+class CableTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Cable
 
     # TODO: Creation URL needs termination context
@@ -1655,7 +1496,7 @@ class CableTestCase(StandardTestCases.Views):
         }
 
 
-class VirtualChassisTestCase(StandardTestCases.Views):
+class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = VirtualChassis
 
     # Disable inapplicable tests
@@ -1709,7 +1550,7 @@ class VirtualChassisTestCase(StandardTestCases.Views):
         Device.objects.filter(pk=device6.pk).update(virtual_chassis=vc3, vc_position=2)
 
 
-class PowerPanelTestCase(StandardTestCases.Views):
+class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = PowerPanel
 
     # Disable inapplicable tests
@@ -1750,7 +1591,7 @@ class PowerPanelTestCase(StandardTestCases.Views):
         )
 
 
-class PowerFeedTestCase(StandardTestCases.Views):
+class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = PowerFeed
 
     @classmethod

+ 3 - 3
netbox/extras/tests/test_views.py

@@ -7,10 +7,10 @@ from django.urls import reverse
 from dcim.models import Site
 from extras.choices import ObjectChangeActionChoices
 from extras.models import ConfigContext, ObjectChange, Tag
-from utilities.testing import StandardTestCases, TestCase
+from utilities.testing import ViewTestCases, TestCase
 
 
-class TagTestCase(StandardTestCases.Views):
+class TagTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Tag
 
     # Disable inapplicable tests
@@ -38,7 +38,7 @@ class TagTestCase(StandardTestCases.Views):
         }
 
 
-class ConfigContextTestCase(StandardTestCases.Views):
+class ConfigContextTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = ConfigContext
 
     # Disable inapplicable tests

+ 10 - 25
netbox/ipam/tests/test_views.py

@@ -5,10 +5,10 @@ from netaddr import IPNetwork
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from ipam.choices import *
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
-from utilities.testing import StandardTestCases
+from utilities.testing import ViewTestCases
 
 
-class VRFTestCase(StandardTestCases.Views):
+class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = VRF
 
     @classmethod
@@ -43,14 +43,9 @@ class VRFTestCase(StandardTestCases.Views):
         }
 
 
-class RIRTestCase(StandardTestCases.Views):
+class RIRTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = RIR
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -74,7 +69,7 @@ class RIRTestCase(StandardTestCases.Views):
         )
 
 
-class AggregateTestCase(StandardTestCases.Views):
+class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Aggregate
 
     @classmethod
@@ -115,14 +110,9 @@ class AggregateTestCase(StandardTestCases.Views):
         }
 
 
-class RoleTestCase(StandardTestCases.Views):
+class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = Role
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -147,7 +137,7 @@ class RoleTestCase(StandardTestCases.Views):
         )
 
 
-class PrefixTestCase(StandardTestCases.Views):
+class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Prefix
 
     @classmethod
@@ -207,7 +197,7 @@ class PrefixTestCase(StandardTestCases.Views):
         }
 
 
-class IPAddressTestCase(StandardTestCases.Views):
+class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = IPAddress
 
     @classmethod
@@ -254,14 +244,9 @@ class IPAddressTestCase(StandardTestCases.Views):
         }
 
 
-class VLANGroupTestCase(StandardTestCases.Views):
+class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = VLANGroup
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -287,7 +272,7 @@ class VLANGroupTestCase(StandardTestCases.Views):
         )
 
 
-class VLANTestCase(StandardTestCases.Views):
+class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = VLAN
 
     @classmethod
@@ -346,7 +331,7 @@ class VLANTestCase(StandardTestCases.Views):
         }
 
 
-class ServiceTestCase(StandardTestCases.Views):
+class ServiceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Service
 
     # Disable inapplicable tests

+ 3 - 8
netbox/secrets/tests/test_views.py

@@ -4,18 +4,13 @@ from django.urls import reverse
 
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
-from utilities.testing import StandardTestCases
+from utilities.testing import ViewTestCases
 from .constants import PRIVATE_KEY, PUBLIC_KEY
 
 
-class SecretRoleTestCase(StandardTestCases.Views):
+class SecretRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = SecretRole
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -41,7 +36,7 @@ class SecretRoleTestCase(StandardTestCases.Views):
         )
 
 
-class SecretTestCase(StandardTestCases.Views):
+class SecretTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Secret
 
     # Disable inapplicable tests

+ 3 - 8
netbox/tenancy/tests/test_views.py

@@ -1,15 +1,10 @@
 from tenancy.models import Tenant, TenantGroup
-from utilities.testing import StandardTestCases
+from utilities.testing import ViewTestCases
 
 
-class TenantGroupTestCase(StandardTestCases.Views):
+class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = TenantGroup
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -32,7 +27,7 @@ class TenantGroupTestCase(StandardTestCases.Views):
         )
 
 
-class TenantTestCase(StandardTestCases.Views):
+class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Tenant
 
     @classmethod

+ 185 - 122
netbox/utilities/testing/testcases.py

@@ -57,6 +57,53 @@ class TestCase(_TestCase):
             expected_status, response.status_code, getattr(response, 'data', 'No data')
         ))
 
+
+class ModelViewTestCase(TestCase):
+    """
+    Base TestCase for model views. Subclass to test individual views.
+    """
+    model = None
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        if self.model is None:
+            raise Exception("Test case requires model to be defined")
+
+    def _get_base_url(self):
+        """
+        Return the base format for a URL for the test's model. Override this to test for a model which belongs
+        to a different app (e.g. testing Interfaces within the virtualization app).
+        """
+        return '{}:{}_{{}}'.format(
+            self.model._meta.app_label,
+            self.model._meta.model_name
+        )
+
+    def _get_url(self, action, instance=None):
+        """
+        Return the URL name for a specific action. An instance must be specified for
+        get/edit/delete views.
+        """
+        url_format = self._get_base_url()
+
+        if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'):
+            return reverse(url_format.format(action))
+
+        elif action in ('get', 'edit', 'delete'):
+            if instance is None:
+                raise Exception("Resolving {} URL requires specifying an instance".format(action))
+            # Attempt to resolve using slug first
+            if hasattr(self.model, 'slug'):
+                try:
+                    return reverse(url_format.format(action), kwargs={'slug': instance.slug})
+                except NoReverseMatch:
+                    pass
+            return reverse(url_format.format(action), kwargs={'pk': instance.pk})
+
+        else:
+            raise Exception("Invalid action for URL resolution: {}".format(action))
+
     def assertInstanceEqual(self, instance, data):
         """
         Compare a model instance to a dictionary, checking that its attribute values match those specified
@@ -94,108 +141,14 @@ class APITestCase(TestCase):
         self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)}
 
 
-class StandardTestCases:
+class ViewTestCases:
     """
     We keep any TestCases with test_* methods inside a class to prevent unittest from trying to run them.
     """
-
-    class Views(TestCase):
+    class GetObjectViewTestCase(ModelViewTestCase):
         """
-        Stock TestCase suitable for testing all standard View functions:
-            - List objects
-            - View single object
-            - Create new object
-            - Modify existing object
-            - Delete existing object
-            - Import multiple new objects
+        Retrieve a single instance.
         """
-        model = None
-
-        # Data to be sent when creating/editing individual objects
-        form_data = {}
-
-        # CSV lines used for bulk import of new objects
-        csv_data = ()
-
-        # Form data used when creating multiple objects
-        bulk_create_data = {}
-
-        # Form data to be used when editing multiple objects at once
-        bulk_edit_data = {}
-
-        maxDiff = None
-
-        def __init__(self, *args, **kwargs):
-
-            super().__init__(*args, **kwargs)
-
-            if self.model is None:
-                raise Exception("Test case requires model to be defined")
-
-        #
-        # URL functions
-        #
-
-        def _get_base_url(self):
-            """
-            Return the base format for a URL for the test's model. Override this to test for a model which belongs
-            to a different app (e.g. testing Interfaces within the virtualization app).
-            """
-            return '{}:{}_{{}}'.format(
-                self.model._meta.app_label,
-                self.model._meta.model_name
-            )
-
-        def _get_url(self, action, instance=None):
-            """
-            Return the URL name for a specific action. An instance must be specified for
-            get/edit/delete views.
-            """
-            url_format = self._get_base_url()
-
-            if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'):
-                return reverse(url_format.format(action))
-
-            elif action in ('get', 'edit', 'delete'):
-                if instance is None:
-                    raise Exception("Resolving {} URL requires specifying an instance".format(action))
-                # Attempt to resolve using slug first
-                if hasattr(self.model, 'slug'):
-                    try:
-                        return reverse(url_format.format(action), kwargs={'slug': instance.slug})
-                    except NoReverseMatch:
-                        pass
-                return reverse(url_format.format(action), kwargs={'pk': instance.pk})
-
-            else:
-                raise Exception("Invalid action for URL resolution: {}".format(action))
-
-        #
-        # Standard view tests
-        # These methods will run by default. To disable a test, nullify its method on the subclasses TestCase:
-        #
-        #     test_list_objects = None
-        #
-
-        @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
-        def test_list_objects(self):
-            # Attempt to make the request without required permissions
-            with disable_warnings('django.request'):
-                self.assertHttpStatus(self.client.get(self._get_url('list')), 403)
-
-            # Assign the required permission and submit again
-            self.add_permissions(
-                '{}.view_{}'.format(self.model._meta.app_label, self.model._meta.model_name)
-            )
-            response = self.client.get(self._get_url('list'))
-            self.assertHttpStatus(response, 200)
-
-            # Built-in CSV export
-            if hasattr(self.model, 'csv_headers'):
-                response = self.client.get('{}?export'.format(self._get_url('list')))
-                self.assertHttpStatus(response, 200)
-                self.assertEqual(response.get('Content-Type'), 'text/csv')
-
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_get_object(self):
             instance = self.model.objects.first()
@@ -211,6 +164,12 @@ class StandardTestCases:
             response = self.client.get(instance.get_absolute_url())
             self.assertHttpStatus(response, 200)
 
+    class CreateObjectViewTestCase(ModelViewTestCase):
+        """
+        Create a single new instance.
+        """
+        form_data = {}
+
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_create_object(self):
             initial_count = self.model.objects.count()
@@ -235,6 +194,12 @@ class StandardTestCases:
             instance = self.model.objects.order_by('-pk').first()
             self.assertInstanceEqual(instance, self.form_data)
 
+    class EditObjectViewTestCase(ModelViewTestCase):
+        """
+        Edit a single existing instance.
+        """
+        form_data = {}
+
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_edit_object(self):
             instance = self.model.objects.first()
@@ -259,6 +224,10 @@ class StandardTestCases:
             instance = self.model.objects.get(pk=instance.pk)
             self.assertInstanceEqual(instance, self.form_data)
 
+    class DeleteObjectViewTestCase(ModelViewTestCase):
+        """
+        Delete a single instance.
+        """
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_delete_object(self):
             instance = self.model.objects.first()
@@ -283,6 +252,66 @@ class StandardTestCases:
             with self.assertRaises(ObjectDoesNotExist):
                 self.model.objects.get(pk=instance.pk)
 
+    class ListObjectsViewTestCase(ModelViewTestCase):
+        """
+        Retrieve multiple instances.
+        """
+        @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
+        def test_list_objects(self):
+            # Attempt to make the request without required permissions
+            with disable_warnings('django.request'):
+                self.assertHttpStatus(self.client.get(self._get_url('list')), 403)
+
+            # Assign the required permission and submit again
+            self.add_permissions(
+                '{}.view_{}'.format(self.model._meta.app_label, self.model._meta.model_name)
+            )
+            response = self.client.get(self._get_url('list'))
+            self.assertHttpStatus(response, 200)
+
+            # Built-in CSV export
+            if hasattr(self.model, 'csv_headers'):
+                response = self.client.get('{}?export'.format(self._get_url('list')))
+                self.assertHttpStatus(response, 200)
+                self.assertEqual(response.get('Content-Type'), 'text/csv')
+
+    class BulkCreateObjectsViewTestCase(ModelViewTestCase):
+        """
+        Create multiple instances using a single form. Expects the creation of three new instances by default.
+        """
+        bulk_create_count = 3
+        bulk_create_data = {}
+
+        @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
+        def test_bulk_create_objects(self):
+            initial_count = self.model.objects.count()
+            request = {
+                'path': self._get_url('add'),
+                'data': post_data(self.bulk_create_data),
+                'follow': False,  # Do not follow 302 redirects
+            }
+
+            # Attempt to make the request without required permissions
+            with disable_warnings('django.request'):
+                self.assertHttpStatus(self.client.post(**request), 403)
+
+            # Assign the required permission and submit again
+            self.add_permissions(
+                '{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name)
+            )
+            response = self.client.post(**request)
+            self.assertHttpStatus(response, 302)
+
+            self.assertEqual(initial_count + self.bulk_create_count, self.model.objects.count())
+            for instance in self.model.objects.order_by('-pk')[:self.bulk_create_count]:
+                self.assertInstanceEqual(instance, self.bulk_create_data)
+
+    class ImportObjectsViewTestCase(ModelViewTestCase):
+        """
+        Create multiple instances from imported data.
+        """
+        csv_data = ()
+
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_import_objects(self):
             initial_count = self.model.objects.count()
@@ -307,6 +336,12 @@ class StandardTestCases:
 
             self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1)
 
+    class BulkEditObjectsViewTestCase(ModelViewTestCase):
+        """
+        Edit multiple instances.
+        """
+        bulk_edit_data = {}
+
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_bulk_edit_objects(self):
             # Bulk edit the first three objects only
@@ -338,6 +373,10 @@ class StandardTestCases:
             for i, instance in enumerate(self.model.objects.filter(pk__in=pk_list)):
                 self.assertInstanceEqual(instance, self.bulk_edit_data)
 
+    class BulkDeleteObjectsViewTestCase(ModelViewTestCase):
+        """
+        Delete multiple instances.
+        """
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_bulk_delete_objects(self):
             pk_list = self.model.objects.values_list('pk', flat=True)
@@ -366,31 +405,55 @@ class StandardTestCases:
             # Check that all objects were deleted
             self.assertEqual(self.model.objects.count(), 0)
 
-        #
-        # Optional view tests
-        # These methods will run only if the required data
-        #
-
-        @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
-        def _test_bulk_create_objects(self, expected_count):
-            initial_count = self.model.objects.count()
-            request = {
-                'path': self._get_url('add'),
-                'data': post_data(self.bulk_create_data),
-                'follow': False,  # Do not follow 302 redirects
-            }
+    class PrimaryObjectViewTestCase(
+        GetObjectViewTestCase,
+        CreateObjectViewTestCase,
+        EditObjectViewTestCase,
+        DeleteObjectViewTestCase,
+        ListObjectsViewTestCase,
+        ImportObjectsViewTestCase,
+        BulkEditObjectsViewTestCase,
+        BulkDeleteObjectsViewTestCase,
+    ):
+        """
+        TestCase suitable for testing all standard View functions for primary objects
+        """
+        maxDiff = None
 
-            # Attempt to make the request without required permissions
-            with disable_warnings('django.request'):
-                self.assertHttpStatus(self.client.post(**request), 403)
+    class OrganizationalObjectViewTestCase(
+        CreateObjectViewTestCase,
+        EditObjectViewTestCase,
+        ListObjectsViewTestCase,
+        ImportObjectsViewTestCase,
+        BulkDeleteObjectsViewTestCase,
+    ):
+        """
+        TestCase suitable for all organizational objects
+        """
+        maxDiff = None
 
-            # Assign the required permission and submit again
-            self.add_permissions(
-                '{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name)
-            )
-            response = self.client.post(**request)
-            self.assertHttpStatus(response, 302)
+    class DeviceComponentTemplateViewTestCase(
+        EditObjectViewTestCase,
+        DeleteObjectViewTestCase,
+        BulkCreateObjectsViewTestCase,
+        BulkEditObjectsViewTestCase,
+        BulkDeleteObjectsViewTestCase,
+    ):
+        """
+        TestCase suitable for testing device component template models (ConsolePortTemplates, InterfaceTemplates, etc.)
+        """
+        maxDiff = None
 
-            self.assertEqual(initial_count + expected_count, self.model.objects.count())
-            for instance in self.model.objects.order_by('-pk')[:expected_count]:
-                self.assertInstanceEqual(instance, self.bulk_create_data)
+    class DeviceComponentViewTestCase(
+        EditObjectViewTestCase,
+        DeleteObjectViewTestCase,
+        ListObjectsViewTestCase,
+        BulkCreateObjectsViewTestCase,
+        ImportObjectsViewTestCase,
+        BulkEditObjectsViewTestCase,
+        BulkDeleteObjectsViewTestCase,
+    ):
+        """
+        TestCase suitable for testing device component models (ConsolePorts, Interfaces, etc.)
+        """
+        maxDiff = None

+ 9 - 20
netbox/virtualization/tests/test_views.py

@@ -3,19 +3,14 @@ from netaddr import EUI
 from dcim.choices import InterfaceModeChoices
 from dcim.models import DeviceRole, Interface, Platform, Site
 from ipam.models import VLAN
-from utilities.testing import StandardTestCases
+from utilities.testing import ViewTestCases
 from virtualization.choices import *
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
-class ClusterGroupTestCase(StandardTestCases.Views):
+class ClusterGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = ClusterGroup
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -38,14 +33,9 @@ class ClusterGroupTestCase(StandardTestCases.Views):
         )
 
 
-class ClusterTypeTestCase(StandardTestCases.Views):
+class ClusterTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     model = ClusterType
 
-    # Disable inapplicable tests
-    test_get_object = None
-    test_delete_object = None
-    test_bulk_edit_objects = None
-
     @classmethod
     def setUpTestData(cls):
 
@@ -68,7 +58,7 @@ class ClusterTypeTestCase(StandardTestCases.Views):
         )
 
 
-class ClusterTestCase(StandardTestCases.Views):
+class ClusterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Cluster
 
     @classmethod
@@ -124,7 +114,7 @@ class ClusterTestCase(StandardTestCases.Views):
         }
 
 
-class VirtualMachineTestCase(StandardTestCases.Views):
+class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = VirtualMachine
 
     @classmethod
@@ -193,17 +183,16 @@ class VirtualMachineTestCase(StandardTestCases.Views):
         }
 
 
-class InterfaceTestCase(StandardTestCases.Views):
+class InterfaceTestCase(
+    ViewTestCases.GetObjectViewTestCase,
+    ViewTestCases.DeviceComponentViewTestCase,
+):
     model = Interface
 
     # Disable inapplicable tests
     test_list_objects = None
-    test_create_object = None
     test_import_objects = None
 
-    def test_bulk_create_objects(self):
-        return self._test_bulk_create_objects(expected_count=3)
-
     def _get_base_url(self):
         # Interface belongs to the DCIM app, so we have to override the base URL
         return 'virtualization:interface_{}'