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

Reorganize base TestCase classes

jeremystretch 4 лет назад
Родитель
Сommit
664ba55460

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

@@ -6,7 +6,7 @@ from django.urls import reverse
 from circuits.choices import *
 from circuits.choices import *
 from circuits.models import *
 from circuits.models import *
 from dcim.models import Cable, Interface, Site
 from dcim.models import Cable, Interface, Site
-from utilities.testing import ViewTestCases, create_test_device
+from utilities.testing import ViewTestCases, create_tags, create_test_device
 
 
 
 
 class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase):
 class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@@ -21,7 +21,7 @@ class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Provider(name='Provider 3', slug='provider-3', asn=65003),
             Provider(name='Provider 3', slug='provider-3', asn=65003),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Provider X',
             'name': 'Provider X',
@@ -106,7 +106,7 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Circuit(cid='Circuit 3', provider=providers[0], type=circuittypes[0]),
             Circuit(cid='Circuit 3', provider=providers[0], type=circuittypes[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'cid': 'Circuit X',
             'cid': 'Circuit X',
@@ -157,7 +157,7 @@ class ProviderNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             ProviderNetwork(name='Provider Network 3', provider=providers[0]),
             ProviderNetwork(name='Provider Network 3', provider=providers[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Provider Network X',
             'name': 'Provider Network X',

+ 18 - 18
netbox/dcim/tests/test_views.py

@@ -12,7 +12,7 @@ from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
 from dcim.models import *
 from dcim.models import *
 from ipam.models import VLAN
 from ipam.models import VLAN
-from utilities.testing import ViewTestCases, create_test_device
+from utilities.testing import ViewTestCases, create_tags, create_test_device
 
 
 
 
 class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
 class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
@@ -109,7 +109,7 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Site(name='Site 3', slug='site-3', region=regions[0], group=groups[1]),
             Site(name='Site 3', slug='site-3', region=regions[0], group=groups[1]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Site X',
             'name': 'Site X',
@@ -242,7 +242,7 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             RackReservation(rack=rack, user=user2, units=[7, 8, 9], description='Reservation 3'),
             RackReservation(rack=rack, user=user2, units=[7, 8, 9], description='Reservation 3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'rack': rack.pk,
             'rack': rack.pk,
@@ -298,7 +298,7 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Rack(name='Rack 3', site=sites[0]),
             Rack(name='Rack 3', site=sites[0]),
         ))
         ))
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Rack X',
             'name': 'Rack X',
@@ -413,7 +413,7 @@ class DeviceTypeTestCase(
             DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturers[0]),
             DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturers[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'manufacturer': manufacturers[1].pk,
             'manufacturer': manufacturers[1].pk,
@@ -1021,7 +1021,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Device(name='Device 3', site=sites[0], rack=racks[0], device_type=devicetypes[0], device_role=deviceroles[0], platform=platforms[0]),
             Device(name='Device 3', site=sites[0], rack=racks[0], device_type=devicetypes[0], device_role=deviceroles[0], platform=platforms[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device_type': devicetypes[1].pk,
             'device_type': devicetypes[1].pk,
@@ -1201,7 +1201,7 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
             ConsolePort(device=device, name='Console Port 3'),
             ConsolePort(device=device, name='Console Port 3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1259,7 +1259,7 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
             ConsoleServerPort(device=device, name='Console Server Port 3'),
             ConsoleServerPort(device=device, name='Console Server Port 3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1315,7 +1315,7 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
             PowerPort(device=device, name='Power Port 3'),
             PowerPort(device=device, name='Power Port 3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1383,7 +1383,7 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
             PowerOutlet(device=device, name='Power Outlet 3', power_port=powerports[0]),
             PowerOutlet(device=device, name='Power Outlet 3', power_port=powerports[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1452,7 +1452,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
         )
         )
         VLAN.objects.bulk_create(vlans)
         VLAN.objects.bulk_create(vlans)
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1539,7 +1539,7 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
             FrontPort(device=device, name='Front Port 3', rear_port=rearports[2]),
             FrontPort(device=device, name='Front Port 3', rear_port=rearports[2]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1600,7 +1600,7 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
             RearPort(device=device, name='Rear Port 3'),
             RearPort(device=device, name='Rear Port 3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1661,7 +1661,7 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
             DeviceBay(device=device, name='Device Bay 3'),
             DeviceBay(device=device, name='Device Bay 3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1701,7 +1701,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
         InventoryItem.objects.create(device=device, name='Inventory Item 2')
         InventoryItem.objects.create(device=device, name='Inventory Item 2')
         InventoryItem.objects.create(device=device, name='Inventory Item 3')
         InventoryItem.objects.create(device=device, name='Inventory Item 3')
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
@@ -1791,7 +1791,7 @@ class CableTestCase(
         Cable(termination_a=interfaces[1], termination_b=interfaces[4], type=CableTypeChoices.TYPE_CAT6).save()
         Cable(termination_a=interfaces[1], termination_b=interfaces[4], type=CableTypeChoices.TYPE_CAT6).save()
         Cable(termination_a=interfaces[2], termination_b=interfaces[5], type=CableTypeChoices.TYPE_CAT6).save()
         Cable(termination_a=interfaces[2], termination_b=interfaces[5], type=CableTypeChoices.TYPE_CAT6).save()
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         interface_ct = ContentType.objects.get_for_model(Interface)
         interface_ct = ContentType.objects.get_for_model(Interface)
         cls.form_data = {
         cls.form_data = {
@@ -1918,7 +1918,7 @@ class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             PowerPanel(site=sites[0], location=locations[0], name='Power Panel 3'),
             PowerPanel(site=sites[0], location=locations[0], name='Power Panel 3'),
         ))
         ))
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'site': sites[1].pk,
             'site': sites[1].pk,
@@ -1966,7 +1966,7 @@ class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             PowerFeed(name='Power Feed 3', power_panel=powerpanels[0], rack=racks[0]),
             PowerFeed(name='Power Feed 3', power_panel=powerpanels[0], rack=racks[0]),
         ))
         ))
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Power Feed X',
             'name': 'Power Feed X',

+ 4 - 4
netbox/extras/tests/test_changelog.py

@@ -7,7 +7,7 @@ from dcim.models import Site
 from extras.choices import *
 from extras.choices import *
 from extras.models import CustomField, ObjectChange, Tag
 from extras.models import CustomField, ObjectChange, Tag
 from utilities.testing import APITestCase
 from utilities.testing import APITestCase
-from utilities.testing.utils import post_data
+from utilities.testing.utils import create_tags, post_data
 from utilities.testing.views import ModelViewTestCase
 from utilities.testing.views import ModelViewTestCase
 
 
 
 
@@ -38,7 +38,7 @@ class ChangeLogViewTest(ModelViewTestCase):
         cf_select.content_types.set([ct])
         cf_select.content_types.set([ct])
 
 
     def test_create_object(self):
     def test_create_object(self):
-        tags = self.create_tags('Tag 1', 'Tag 2')
+        tags = create_tags('Tag 1', 'Tag 2')
         form_data = {
         form_data = {
             'name': 'Site 1',
             'name': 'Site 1',
             'slug': 'site-1',
             'slug': 'site-1',
@@ -72,7 +72,7 @@ class ChangeLogViewTest(ModelViewTestCase):
     def test_update_object(self):
     def test_update_object(self):
         site = Site(name='Site 1', slug='site-1')
         site = Site(name='Site 1', slug='site-1')
         site.save()
         site.save()
-        tags = self.create_tags('Tag 1', 'Tag 2', 'Tag 3')
+        tags = create_tags('Tag 1', 'Tag 2', 'Tag 3')
         site.tags.set('Tag 1', 'Tag 2')
         site.tags.set('Tag 1', 'Tag 2')
 
 
         form_data = {
         form_data = {
@@ -116,7 +116,7 @@ class ChangeLogViewTest(ModelViewTestCase):
             }
             }
         )
         )
         site.save()
         site.save()
-        self.create_tags('Tag 1', 'Tag 2')
+        create_tags('Tag 1', 'Tag 2')
         site.tags.set('Tag 1', 'Tag 2')
         site.tags.set('Tag 1', 'Tag 2')
 
 
         request = {
         request = {

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

@@ -2,7 +2,7 @@ from django.urls import reverse
 from rest_framework import status
 from rest_framework import status
 
 
 from dcim.models import Site
 from dcim.models import Site
-from utilities.testing import APITestCase
+from utilities.testing import APITestCase, create_tags
 
 
 
 
 class TaggedItemTest(APITestCase):
 class TaggedItemTest(APITestCase):
@@ -10,7 +10,7 @@ class TaggedItemTest(APITestCase):
     Test the application of Tags to and item (a Site, for example) upon creation (POST) and modification (PATCH).
     Test the application of Tags to and item (a Site, for example) upon creation (POST) and modification (PATCH).
     """
     """
     def test_create_tagged_item(self):
     def test_create_tagged_item(self):
-        tags = self.create_tags("Foo", "Bar", "Baz")
+        tags = create_tags("Foo", "Bar", "Baz")
         data = {
         data = {
             'name': 'Test Site',
             'name': 'Test Site',
             'slug': 'test-site',
             'slug': 'test-site',
@@ -37,7 +37,7 @@ class TaggedItemTest(APITestCase):
             slug='test-site'
             slug='test-site'
         )
         )
         site.tags.add("Foo", "Bar", "Baz")
         site.tags.add("Foo", "Bar", "Baz")
-        self.create_tags("New Tag")
+        create_tags("New Tag")
         data = {
         data = {
             'tags': [
             'tags': [
                 {"name": "Foo"},
                 {"name": "Foo"},

+ 8 - 8
netbox/ipam/tests/test_views.py

@@ -6,7 +6,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from ipam.choices import *
 from ipam.choices import *
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from tenancy.models import Tenant
 from tenancy.models import Tenant
-from utilities.testing import ViewTestCases
+from utilities.testing import ViewTestCases, create_tags
 
 
 
 
 class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
 class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@@ -27,7 +27,7 @@ class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             VRF(name='VRF 3', rd='65000:3'),
             VRF(name='VRF 3', rd='65000:3'),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'VRF X',
             'name': 'VRF X',
@@ -64,7 +64,7 @@ class RouteTargetTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         )
         )
         Tenant.objects.bulk_create(tenants)
         Tenant.objects.bulk_create(tenants)
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         route_targets = (
         route_targets = (
             RouteTarget(name='65000:1001', tenant=tenants[0]),
             RouteTarget(name='65000:1001', tenant=tenants[0]),
@@ -141,7 +141,7 @@ class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Aggregate(prefix=IPNetwork('10.3.0.0/16'), rir=rirs[0]),
             Aggregate(prefix=IPNetwork('10.3.0.0/16'), rir=rirs[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'prefix': IPNetwork('10.99.0.0/16'),
             'prefix': IPNetwork('10.99.0.0/16'),
@@ -226,7 +226,7 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Prefix(prefix=IPNetwork('10.3.0.0/16'), vrf=vrfs[0], site=sites[0], role=roles[0]),
             Prefix(prefix=IPNetwork('10.3.0.0/16'), vrf=vrfs[0], site=sites[0], role=roles[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'prefix': IPNetwork('192.0.2.0/24'),
             'prefix': IPNetwork('192.0.2.0/24'),
@@ -277,7 +277,7 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             IPAddress(address=IPNetwork('192.0.2.3/24'), vrf=vrfs[0]),
             IPAddress(address=IPNetwork('192.0.2.3/24'), vrf=vrfs[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'vrf': vrfs[1].pk,
             'vrf': vrfs[1].pk,
@@ -374,7 +374,7 @@ class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             VLAN(group=vlangroups[0], vid=103, name='VLAN103', site=sites[0], role=roles[0]),
             VLAN(group=vlangroups[0], vid=103, name='VLAN103', site=sites[0], role=roles[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'site': sites[1].pk,
             'site': sites[1].pk,
@@ -434,7 +434,7 @@ class ServiceTestCase(
             Service(device=device, name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[103]),
             Service(device=device, name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[103]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,

+ 2 - 2
netbox/tenancy/tests/test_views.py

@@ -1,5 +1,5 @@
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
-from utilities.testing import ViewTestCases
+from utilities.testing import ViewTestCases, create_tags
 
 
 
 
 class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
 class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
@@ -53,7 +53,7 @@ class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[0]),
             Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Tenant X',
             'name': 'Tenant X',

+ 1 - 0
netbox/utilities/testing/__init__.py

@@ -1,3 +1,4 @@
 from .api import *
 from .api import *
+from .base import *
 from .utils import *
 from .utils import *
 from .views import *
 from .views import *

+ 1 - 1
netbox/utilities/testing/api.py

@@ -9,8 +9,8 @@ from rest_framework.test import APIClient
 from extras.choices import ObjectChangeActionChoices
 from extras.choices import ObjectChangeActionChoices
 from extras.models import ObjectChange
 from extras.models import ObjectChange
 from users.models import ObjectPermission, Token
 from users.models import ObjectPermission, Token
+from .base import ModelTestCase
 from .utils import disable_warnings
 from .utils import disable_warnings
-from .views import ModelTestCase
 
 
 
 
 __all__ = (
 __all__ = (

+ 161 - 0
netbox/utilities/testing/base.py

@@ -0,0 +1,161 @@
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.postgres.fields import ArrayField
+from django.core.exceptions import FieldDoesNotExist
+from django.db.models import ManyToManyField
+from django.forms.models import model_to_dict
+from django.test import Client, TestCase as _TestCase
+from netaddr import IPNetwork
+from taggit.managers import TaggableManager
+
+from users.models import ObjectPermission
+from utilities.permissions import resolve_permission_ct
+from .utils import extract_form_failures
+
+__all__ = (
+    'ModelTestCase',
+    'TestCase',
+)
+
+
+class TestCase(_TestCase):
+    user_permissions = ()
+
+    def setUp(self):
+
+        # Create the test user and assign permissions
+        self.user = User.objects.create_user(username='testuser')
+        self.add_permissions(*self.user_permissions)
+
+        # Initialize the test client
+        self.client = Client()
+        self.client.force_login(self.user)
+
+    #
+    # Permissions management
+    #
+
+    def add_permissions(self, *names):
+        """
+        Assign a set of permissions to the test user. Accepts permission names in the form <app>.<action>_<model>.
+        """
+        for name in names:
+            ct, action = resolve_permission_ct(name)
+            obj_perm = ObjectPermission(name=name, actions=[action])
+            obj_perm.save()
+            obj_perm.users.add(self.user)
+            obj_perm.object_types.add(ct)
+
+    #
+    # Custom assertions
+    #
+
+    def assertHttpStatus(self, response, expected_status):
+        """
+        TestCase method. Provide more detail in the event of an unexpected HTTP response.
+        """
+        err_message = None
+        # Construct an error message only if we know the test is going to fail
+        if response.status_code != expected_status:
+            if hasattr(response, 'data'):
+                # REST API response; pass the response data through directly
+                err = response.data
+            else:
+                # Attempt to extract form validation errors from the response HTML
+                form_errors = extract_form_failures(response.content)
+                err = form_errors or response.content or 'No data'
+            err_message = f"Expected HTTP status {expected_status}; received {response.status_code}: {err}"
+        self.assertEqual(response.status_code, expected_status, err_message)
+
+
+class ModelTestCase(TestCase):
+    """
+    Parent class for TestCases which deal with models.
+    """
+    model = None
+
+    def _get_queryset(self):
+        """
+        Return a base queryset suitable for use in test methods.
+        """
+        return self.model.objects.all()
+
+    def prepare_instance(self, instance):
+        """
+        Test cases can override this method to perform any necessary manipulation of an instance prior to its evaluation
+        against test data. For example, it can be used to decrypt a Secret's plaintext attribute.
+        """
+        return instance
+
+    def model_to_dict(self, instance, fields, api=False):
+        """
+        Return a dictionary representation of an instance.
+        """
+        # Prepare the instance and call Django's model_to_dict() to extract all fields
+        model_dict = model_to_dict(self.prepare_instance(instance), fields=fields)
+
+        # Map any additional (non-field) instance attributes that were specified
+        for attr in fields:
+            if hasattr(instance, attr) and attr not in model_dict:
+                model_dict[attr] = getattr(instance, attr)
+
+        for key, value in list(model_dict.items()):
+            try:
+                field = instance._meta.get_field(key)
+            except FieldDoesNotExist:
+                # Attribute is not a model field
+                continue
+
+            # Handle ManyToManyFields
+            if value and type(field) in (ManyToManyField, TaggableManager):
+
+                if field.related_model is ContentType:
+                    model_dict[key] = sorted([f'{ct.app_label}.{ct.model}' for ct in value])
+                else:
+                    model_dict[key] = sorted([obj.pk for obj in value])
+
+            if api:
+
+                # Replace ContentType numeric IDs with <app_label>.<model>
+                if type(getattr(instance, key)) is ContentType:
+                    ct = ContentType.objects.get(pk=value)
+                    model_dict[key] = f'{ct.app_label}.{ct.model}'
+
+                # Convert IPNetwork instances to strings
+                elif type(value) is IPNetwork:
+                    model_dict[key] = str(value)
+
+            else:
+
+                # Convert ArrayFields to CSV strings
+                if type(instance._meta.get_field(key)) is ArrayField:
+                    model_dict[key] = ','.join([str(v) for v in value])
+
+        return model_dict
+
+    #
+    # Custom assertions
+    #
+
+    def assertInstanceEqual(self, instance, data, exclude=None, api=False):
+        """
+        Compare a model instance to a dictionary, checking that its attribute values match those specified
+        in the dictionary.
+
+        :param instance: Python object instance
+        :param data: Dictionary of test data used to define the instance
+        :param exclude: List of fields to exclude from comparison (e.g. passwords, which get hashed)
+        :param api: Set to True is the data is a JSON representation of the instance
+        """
+        if exclude is None:
+            exclude = []
+
+        fields = [k for k in data.keys() if k not in exclude]
+        model_dict = self.model_to_dict(instance, fields=fields, api=api)
+
+        # Omit any dictionary keys which are not instance attributes or have been excluded
+        relevant_data = {
+            k: v for k, v in data.items() if hasattr(instance, k) and k not in exclude
+        }
+
+        self.assertDictEqual(model_dict, relevant_data)

+ 11 - 0
netbox/utilities/testing/utils.py

@@ -3,8 +3,10 @@ import re
 from contextlib import contextmanager
 from contextlib import contextmanager
 
 
 from django.contrib.auth.models import Permission, User
 from django.contrib.auth.models import Permission, User
+from django.utils.text import slugify
 
 
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
+from extras.models import Tag
 
 
 
 
 def post_data(data):
 def post_data(data):
@@ -59,6 +61,15 @@ def create_test_user(username='testuser', permissions=None):
     return user
     return user
 
 
 
 
+def create_tags(*names):
+    """
+    Create and return a Tag instance for each name given.
+    """
+    tags = [Tag(name=name, slug=slugify(name)) for name in names]
+    Tag.objects.bulk_create(tags)
+    return tags
+
+
 def extract_form_failures(content):
 def extract_form_failures(content):
     """
     """
     Given raw HTML content from an HTTP response, return a list of form errors.
     Given raw HTML content from an HTTP response, return a list of form errors.

+ 5 - 168
netbox/utilities/testing/views.py

@@ -1,182 +1,20 @@
-from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.contrib.postgres.fields import ArrayField
-from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
-from django.db.models import ManyToManyField
-from django.forms.models import model_to_dict
-from django.test import Client, TestCase as _TestCase, override_settings
+from django.core.exceptions import ObjectDoesNotExist
+from django.test import override_settings
 from django.urls import reverse
 from django.urls import reverse
-from django.utils.text import slugify
-from netaddr import IPNetwork
-from taggit.managers import TaggableManager
 
 
 from extras.choices import ObjectChangeActionChoices
 from extras.choices import ObjectChangeActionChoices
-from extras.models import ObjectChange, Tag
+from extras.models import ObjectChange
 from users.models import ObjectPermission
 from users.models import ObjectPermission
-from utilities.permissions import resolve_permission_ct
-from .utils import disable_warnings, extract_form_failures, post_data
-
+from .base import ModelTestCase
+from .utils import disable_warnings, post_data
 
 
 __all__ = (
 __all__ = (
-    'TestCase',
-    'ModelTestCase',
     'ModelViewTestCase',
     'ModelViewTestCase',
     'ViewTestCases',
     'ViewTestCases',
 )
 )
 
 
 
 
-class TestCase(_TestCase):
-    user_permissions = ()
-
-    def setUp(self):
-
-        # Create the test user and assign permissions
-        self.user = User.objects.create_user(username='testuser')
-        self.add_permissions(*self.user_permissions)
-
-        # Initialize the test client
-        self.client = Client()
-        self.client.force_login(self.user)
-
-    def prepare_instance(self, instance):
-        """
-        Test cases can override this method to perform any necessary manipulation of an instance prior to its evaluation
-        against test data. For example, it can be used to decrypt a Secret's plaintext attribute.
-        """
-        return instance
-
-    def model_to_dict(self, instance, fields, api=False):
-        """
-        Return a dictionary representation of an instance.
-        """
-        # Prepare the instance and call Django's model_to_dict() to extract all fields
-        model_dict = model_to_dict(self.prepare_instance(instance), fields=fields)
-
-        # Map any additional (non-field) instance attributes that were specified
-        for attr in fields:
-            if hasattr(instance, attr) and attr not in model_dict:
-                model_dict[attr] = getattr(instance, attr)
-
-        for key, value in list(model_dict.items()):
-            try:
-                field = instance._meta.get_field(key)
-            except FieldDoesNotExist:
-                # Attribute is not a model field
-                continue
-
-            # Handle ManyToManyFields
-            if value and type(field) in (ManyToManyField, TaggableManager):
-
-                if field.related_model is ContentType:
-                    model_dict[key] = sorted([f'{ct.app_label}.{ct.model}' for ct in value])
-                else:
-                    model_dict[key] = sorted([obj.pk for obj in value])
-
-            if api:
-
-                # Replace ContentType numeric IDs with <app_label>.<model>
-                if type(getattr(instance, key)) is ContentType:
-                    ct = ContentType.objects.get(pk=value)
-                    model_dict[key] = f'{ct.app_label}.{ct.model}'
-
-                # Convert IPNetwork instances to strings
-                elif type(value) is IPNetwork:
-                    model_dict[key] = str(value)
-
-            else:
-
-                # Convert ArrayFields to CSV strings
-                if type(instance._meta.get_field(key)) is ArrayField:
-                    model_dict[key] = ','.join([str(v) for v in value])
-
-        return model_dict
-
-    #
-    # Permissions management
-    #
-
-    def add_permissions(self, *names):
-        """
-        Assign a set of permissions to the test user. Accepts permission names in the form <app>.<action>_<model>.
-        """
-        for name in names:
-            ct, action = resolve_permission_ct(name)
-            obj_perm = ObjectPermission(name=name, actions=[action])
-            obj_perm.save()
-            obj_perm.users.add(self.user)
-            obj_perm.object_types.add(ct)
-
-    #
-    # Custom assertions
-    #
-
-    def assertHttpStatus(self, response, expected_status):
-        """
-        TestCase method. Provide more detail in the event of an unexpected HTTP response.
-        """
-        err_message = None
-        # Construct an error message only if we know the test is going to fail
-        if response.status_code != expected_status:
-            if hasattr(response, 'data'):
-                # REST API response; pass the response data through directly
-                err = response.data
-            else:
-                # Attempt to extract form validation errors from the response HTML
-                form_errors = extract_form_failures(response.content)
-                err = form_errors or response.content or 'No data'
-            err_message = f"Expected HTTP status {expected_status}; received {response.status_code}: {err}"
-        self.assertEqual(response.status_code, expected_status, err_message)
-
-    def assertInstanceEqual(self, instance, data, exclude=None, api=False):
-        """
-        Compare a model instance to a dictionary, checking that its attribute values match those specified
-        in the dictionary.
-
-        :param instance: Python object instance
-        :param data: Dictionary of test data used to define the instance
-        :param exclude: List of fields to exclude from comparison (e.g. passwords, which get hashed)
-        :param api: Set to True is the data is a JSON representation of the instance
-        """
-        if exclude is None:
-            exclude = []
-
-        fields = [k for k in data.keys() if k not in exclude]
-        model_dict = self.model_to_dict(instance, fields=fields, api=api)
-
-        # Omit any dictionary keys which are not instance attributes or have been excluded
-        relevant_data = {
-            k: v for k, v in data.items() if hasattr(instance, k) and k not in exclude
-        }
-
-        self.assertDictEqual(model_dict, relevant_data)
-
-    #
-    # Convenience methods
-    #
-
-    @classmethod
-    def create_tags(cls, *names):
-        """
-        Create and return a Tag instance for each name given.
-        """
-        tags = [Tag(name=name, slug=slugify(name)) for name in names]
-        Tag.objects.bulk_create(tags)
-        return tags
-
-
-class ModelTestCase(TestCase):
-    """
-    Parent class for TestCases which deal with models.
-    """
-    model = None
-
-    def _get_queryset(self):
-        """
-        Return a base queryset suitable for use in test methods.
-        """
-        return self.model.objects.all()
-
-
 #
 #
 # UI Tests
 # UI Tests
 #
 #
@@ -185,7 +23,6 @@ class ModelViewTestCase(ModelTestCase):
     """
     """
     Base TestCase for model views. Subclass to test individual views.
     Base TestCase for model views. Subclass to test individual views.
     """
     """
-
     def _get_base_url(self):
     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
         Return the base format for a URL for the test's model. Override this to test for a model which belongs

+ 4 - 4
netbox/virtualization/tests/test_views.py

@@ -5,7 +5,7 @@ from netaddr import EUI
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.models import DeviceRole, Platform, Site
 from dcim.models import DeviceRole, Platform, Site
 from ipam.models import VLAN
 from ipam.models import VLAN
-from utilities.testing import ViewTestCases
+from utilities.testing import ViewTestCases, create_tags
 from virtualization.choices import *
 from virtualization.choices import *
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
@@ -100,7 +100,7 @@ class ClusterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             Cluster(name='Cluster 3', group=clustergroups[0], type=clustertypes[0], site=sites[0]),
             Cluster(name='Cluster 3', group=clustergroups[0], type=clustertypes[0], site=sites[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'name': 'Cluster X',
             'name': 'Cluster X',
@@ -174,7 +174,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]),
             VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]),
         ])
         ])
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'cluster': clusters[1].pk,
             'cluster': clusters[1].pk,
@@ -256,7 +256,7 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
         )
         )
         VLAN.objects.bulk_create(vlans)
         VLAN.objects.bulk_create(vlans)
 
 
-        tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
+        tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
         cls.form_data = {
         cls.form_data = {
             'virtual_machine': virtualmachines[1].pk,
             'virtual_machine': virtualmachines[1].pk,