Repository: netbox-community/netbox

Issue:
Standardize test cases for all REST API views
### Proposed Changes
Create a standard test suite to be reused for all REST API views, similar to what we currently do for the UI views.

### Justification
Using a standard test suite across all views ensures consistent coverage and allows for easier long-term maintenance.


Discussion:

Resolving patch:
diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py
--- a/netbox/dcim/api/nested_serializers.py
+++ b/netbox/dcim/api/nested_serializers.py
@@ -302,6 +302,7 @@ class Meta:
 
 class NestedInventoryItemSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
+    device = NestedDeviceSerializer(read_only=True)
 
     class Meta:
         model = models.InventoryItem
diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py
--- a/netbox/utilities/api.py
+++ b/netbox/utilities/api.py
@@ -6,14 +6,13 @@
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
 from django.db.models import ManyToManyField, ProtectedError
-from django.http import Http404
 from django.urls import reverse
 from rest_framework.exceptions import APIException
 from rest_framework.permissions import BasePermission
 from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
 from rest_framework.response import Response
 from rest_framework.serializers import Field, ModelSerializer, ValidationError
-from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet
+from rest_framework.viewsets import ModelViewSet as _ModelViewSet
 
 from .utils import dict_to_filter_params, dynamic_import
 


Test patch:
diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py
--- a/netbox/circuits/tests/test_api.py
+++ b/netbox/circuits/tests/test_api.py
@@ -1,443 +1,188 @@
 from django.contrib.contenttypes.models import ContentType
 from django.urls import reverse
-from rest_framework import status
 
 from circuits.choices import *
 from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
 from dcim.models import Site
 from extras.models import Graph
-from utilities.testing import APITestCase
+from utilities.testing import APITestCase, APIViewTestCases
 
 
 class AppTest(APITestCase):
 
     def test_root(self):
-
         url = reverse('circuits-api:api-root')
         response = self.client.get('{}?format=api'.format(url), **self.header)
 
         self.assertEqual(response.status_code, 200)
 
 
-class ProviderTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
-        self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
-        self.provider3 = Provider.objects.create(name='Test Provider 3', slug='test-provider-3')
-
-    def test_get_provider(self):
-
-        url = reverse('circuits-api:provider-detail', kwargs={'pk': self.provider1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.provider1.name)
+class ProviderTest(APIViewTestCases.APIViewTestCase):
+    model = Provider
+    brief_fields = ['circuit_count', 'id', 'name', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Provider 4',
+            'slug': 'provider-4',
+        },
+        {
+            'name': 'Provider 5',
+            'slug': 'provider-5',
+        },
+        {
+            'name': 'Provider 6',
+            'slug': 'provider-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        providers = (
+            Provider(name='Provider 1', slug='provider-1'),
+            Provider(name='Provider 2', slug='provider-2'),
+            Provider(name='Provider 3', slug='provider-3'),
+        )
+        Provider.objects.bulk_create(providers)
 
     def test_get_provider_graphs(self):
-
-        provider_ct = ContentType.objects.get(app_label='circuits', model='provider')
-        self.graph1 = Graph.objects.create(
-            type=provider_ct,
-            name='Test Graph 1',
-            source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=1'
-        )
-        self.graph2 = Graph.objects.create(
-            type=provider_ct,
-            name='Test Graph 2',
-            source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=2'
-        )
-        self.graph3 = Graph.objects.create(
-            type=provider_ct,
-            name='Test Graph 3',
-            source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=3'
+        """
+        Test retrieval of Graphs assigned to Providers.
+        """
+        provider = self.model.objects.first()
+        ct = ContentType.objects.get(app_label='circuits', model='provider')
+        graphs = (
+            Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=1'),
+            Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=2'),
+            Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=3'),
         )
+        Graph.objects.bulk_create(graphs)
 
-        url = reverse('circuits-api:provider-graphs', kwargs={'pk': self.provider1.pk})
+        url = reverse('circuits-api:provider-graphs', kwargs={'pk': provider.pk})
         response = self.client.get(url, **self.header)
 
         self.assertEqual(len(response.data), 3)
-        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?provider=test-provider-1&foo=1')
-
-    def test_list_providers(self):
-
-        url = reverse('circuits-api:provider-list')
-        response = self.client.get(url, **self.header)
+        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?provider=provider-1&foo=1')
+
+
+class CircuitTypeTest(APIViewTestCases.APIViewTestCase):
+    model = CircuitType
+    brief_fields = ['circuit_count', 'id', 'name', 'slug', 'url']
+    create_data = (
+        {
+            'name': 'Circuit Type 4',
+            'slug': 'circuit-type-4',
+        },
+        {
+            'name': 'Circuit Type 5',
+            'slug': 'circuit-type-5',
+        },
+        {
+            'name': 'Circuit Type 6',
+            'slug': 'circuit-type-6',
+        },
+    )
+
+    @classmethod
+    def setUpTestData(cls):
+
+        circuit_types = (
+            CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
+            CircuitType(name='Circuit Type 2', slug='circuit-type-2'),
+            CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
+        )
+        CircuitType.objects.bulk_create(circuit_types)
 
-        self.assertEqual(response.data['count'], 3)
 
-    def test_list_providers_brief(self):
+class CircuitTest(APIViewTestCases.APIViewTestCase):
+    model = Circuit
+    brief_fields = ['cid', 'id', 'url']
 
-        url = reverse('circuits-api:provider-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+    @classmethod
+    def setUpTestData(cls):
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['circuit_count', 'id', 'name', 'slug', 'url']
+        providers = (
+            Provider(name='Provider 1', slug='provider-1'),
+            Provider(name='Provider 2', slug='provider-2'),
         )
+        Provider.objects.bulk_create(providers)
 
-    def test_create_provider(self):
-
-        data = {
-            'name': 'Test Provider 4',
-            'slug': 'test-provider-4',
-        }
-
-        url = reverse('circuits-api:provider-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Provider.objects.count(), 4)
-        provider4 = Provider.objects.get(pk=response.data['id'])
-        self.assertEqual(provider4.name, data['name'])
-        self.assertEqual(provider4.slug, data['slug'])
+        circuit_types = (
+            CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
+            CircuitType(name='Circuit Type 2', slug='circuit-type-2'),
+        )
+        CircuitType.objects.bulk_create(circuit_types)
 
-    def test_create_provider_bulk(self):
+        circuits = (
+            Circuit(cid='Circuit 1', provider=providers[0], type=circuit_types[0]),
+            Circuit(cid='Circuit 2', provider=providers[0], type=circuit_types[0]),
+            Circuit(cid='Circuit 3', provider=providers[0], type=circuit_types[0]),
+        )
+        Circuit.objects.bulk_create(circuits)
 
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Provider 4',
-                'slug': 'test-provider-4',
+                'cid': 'Circuit 4',
+                'provider': providers[1].pk,
+                'type': circuit_types[1].pk,
             },
             {
-                'name': 'Test Provider 5',
-                'slug': 'test-provider-5',
+                'cid': 'Circuit 5',
+                'provider': providers[1].pk,
+                'type': circuit_types[1].pk,
             },
             {
-                'name': 'Test Provider 6',
-                'slug': 'test-provider-6',
+                'cid': 'Circuit 6',
+                'provider': providers[1].pk,
+                'type': circuit_types[1].pk,
             },
         ]
 
-        url = reverse('circuits-api:provider-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Provider.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_provider(self):
-
-        data = {
-            'name': 'Test Provider X',
-            'slug': 'test-provider-x',
-        }
-
-        url = reverse('circuits-api:provider-detail', kwargs={'pk': self.provider1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Provider.objects.count(), 3)
-        provider1 = Provider.objects.get(pk=response.data['id'])
-        self.assertEqual(provider1.name, data['name'])
-        self.assertEqual(provider1.slug, data['slug'])
-
-    def test_delete_provider(self):
-
-        url = reverse('circuits-api:provider-detail', kwargs={'pk': self.provider1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Provider.objects.count(), 2)
 
+class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
+    model = CircuitTermination
+    brief_fields = ['circuit', 'id', 'term_side', 'url']
 
-class CircuitTypeTest(APITestCase):
+    @classmethod
+    def setUpTestData(cls):
+        SIDE_A = CircuitTerminationSideChoices.SIDE_A
+        SIDE_Z = CircuitTerminationSideChoices.SIDE_Z
 
-    def setUp(self):
-
-        super().setUp()
-
-        self.circuittype1 = CircuitType.objects.create(name='Test Circuit Type 1', slug='test-circuit-type-1')
-        self.circuittype2 = CircuitType.objects.create(name='Test Circuit Type 2', slug='test-circuit-type-2')
-        self.circuittype3 = CircuitType.objects.create(name='Test Circuit Type 3', slug='test-circuit-type-3')
-
-    def test_get_circuittype(self):
-
-        url = reverse('circuits-api:circuittype-detail', kwargs={'pk': self.circuittype1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.circuittype1.name)
-
-    def test_list_circuittypes(self):
-
-        url = reverse('circuits-api:circuittype-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_circuittypes_brief(self):
-
-        url = reverse('circuits-api:circuittype-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['circuit_count', 'id', 'name', 'slug', 'url']
+        sites = (
+            Site(name='Site 1', slug='site-1'),
+            Site(name='Site 2', slug='site-2'),
         )
+        Site.objects.bulk_create(sites)
 
-    def test_create_circuittype(self):
-
-        data = {
-            'name': 'Test Circuit Type 4',
-            'slug': 'test-circuit-type-4',
-        }
-
-        url = reverse('circuits-api:circuittype-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(CircuitType.objects.count(), 4)
-        circuittype4 = CircuitType.objects.get(pk=response.data['id'])
-        self.assertEqual(circuittype4.name, data['name'])
-        self.assertEqual(circuittype4.slug, data['slug'])
-
-    def test_update_circuittype(self):
-
-        data = {
-            'name': 'Test Circuit Type X',
-            'slug': 'test-circuit-type-x',
-        }
-
-        url = reverse('circuits-api:circuittype-detail', kwargs={'pk': self.circuittype1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(CircuitType.objects.count(), 3)
-        circuittype1 = CircuitType.objects.get(pk=response.data['id'])
-        self.assertEqual(circuittype1.name, data['name'])
-        self.assertEqual(circuittype1.slug, data['slug'])
-
-    def test_delete_circuittype(self):
-
-        url = reverse('circuits-api:circuittype-detail', kwargs={'pk': self.circuittype1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(CircuitType.objects.count(), 2)
-
-
-class CircuitTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
-        self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
-        self.circuittype1 = CircuitType.objects.create(name='Test Circuit Type 1', slug='test-circuit-type-1')
-        self.circuittype2 = CircuitType.objects.create(name='Test Circuit Type 2', slug='test-circuit-type-2')
-        self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=self.provider1, type=self.circuittype1)
-        self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=self.provider1, type=self.circuittype1)
-        self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=self.provider1, type=self.circuittype1)
+        provider = Provider.objects.create(name='Provider 1', slug='provider-1')
+        circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
 
-    def test_get_circuit(self):
-
-        url = reverse('circuits-api:circuit-detail', kwargs={'pk': self.circuit1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['cid'], self.circuit1.cid)
-
-    def test_list_circuits(self):
-
-        url = reverse('circuits-api:circuit-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_circuits_brief(self):
-
-        url = reverse('circuits-api:circuit-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cid', 'id', 'url']
+        circuits = (
+            Circuit(cid='Circuit 1', provider=provider, type=circuit_type),
+            Circuit(cid='Circuit 2', provider=provider, type=circuit_type),
+            Circuit(cid='Circuit 3', provider=provider, type=circuit_type),
         )
+        Circuit.objects.bulk_create(circuits)
 
-    def test_create_circuit(self):
-
-        data = {
-            'cid': 'TEST0004',
-            'provider': self.provider1.pk,
-            'type': self.circuittype1.pk,
-            'status': CircuitStatusChoices.STATUS_ACTIVE,
-        }
-
-        url = reverse('circuits-api:circuit-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Circuit.objects.count(), 4)
-        circuit4 = Circuit.objects.get(pk=response.data['id'])
-        self.assertEqual(circuit4.cid, data['cid'])
-        self.assertEqual(circuit4.provider_id, data['provider'])
-        self.assertEqual(circuit4.type_id, data['type'])
-
-    def test_create_circuit_bulk(self):
+        circuit_terminations = (
+            CircuitTermination(circuit=circuits[0], site=sites[0], port_speed=100000, term_side=SIDE_A),
+            CircuitTermination(circuit=circuits[0], site=sites[1], port_speed=100000, term_side=SIDE_Z),
+            CircuitTermination(circuit=circuits[1], site=sites[0], port_speed=100000, term_side=SIDE_A),
+            CircuitTermination(circuit=circuits[1], site=sites[1], port_speed=100000, term_side=SIDE_Z),
+        )
+        CircuitTermination.objects.bulk_create(circuit_terminations)
 
-        data = [
-            {
-                'cid': 'TEST0004',
-                'provider': self.provider1.pk,
-                'type': self.circuittype1.pk,
-                'status': CircuitStatusChoices.STATUS_ACTIVE,
-            },
+        cls.create_data = [
             {
-                'cid': 'TEST0005',
-                'provider': self.provider1.pk,
-                'type': self.circuittype1.pk,
-                'status': CircuitStatusChoices.STATUS_ACTIVE,
+                'circuit': circuits[2].pk,
+                'term_side': SIDE_A,
+                'site': sites[1].pk,
+                'port_speed': 200000,
             },
             {
-                'cid': 'TEST0006',
-                'provider': self.provider1.pk,
-                'type': self.circuittype1.pk,
-                'status': CircuitStatusChoices.STATUS_ACTIVE,
+                'circuit': circuits[2].pk,
+                'term_side': SIDE_Z,
+                'site': sites[1].pk,
+                'port_speed': 200000,
             },
         ]
-
-        url = reverse('circuits-api:circuit-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Circuit.objects.count(), 6)
-        self.assertEqual(response.data[0]['cid'], data[0]['cid'])
-        self.assertEqual(response.data[1]['cid'], data[1]['cid'])
-        self.assertEqual(response.data[2]['cid'], data[2]['cid'])
-
-    def test_update_circuit(self):
-
-        data = {
-            'cid': 'TEST000X',
-            'provider': self.provider2.pk,
-            'type': self.circuittype2.pk,
-        }
-
-        url = reverse('circuits-api:circuit-detail', kwargs={'pk': self.circuit1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Circuit.objects.count(), 3)
-        circuit1 = Circuit.objects.get(pk=response.data['id'])
-        self.assertEqual(circuit1.cid, data['cid'])
-        self.assertEqual(circuit1.provider_id, data['provider'])
-        self.assertEqual(circuit1.type_id, data['type'])
-
-    def test_delete_circuit(self):
-
-        url = reverse('circuits-api:circuit-detail', kwargs={'pk': self.circuit1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Circuit.objects.count(), 2)
-
-
-class CircuitTerminationTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
-        provider = Provider.objects.create(name='Test Provider', slug='test-provider')
-        circuittype = CircuitType.objects.create(name='Test Circuit Type', slug='test-circuit-type')
-        self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=provider, type=circuittype)
-        self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
-        self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
-        self.circuittermination1 = CircuitTermination.objects.create(
-            circuit=self.circuit1,
-            term_side=CircuitTerminationSideChoices.SIDE_A,
-            site=self.site1,
-            port_speed=1000000
-        )
-        self.circuittermination2 = CircuitTermination.objects.create(
-            circuit=self.circuit1,
-            term_side=CircuitTerminationSideChoices.SIDE_Z,
-            site=self.site2,
-            port_speed=1000000
-        )
-        self.circuittermination3 = CircuitTermination.objects.create(
-            circuit=self.circuit2,
-            term_side=CircuitTerminationSideChoices.SIDE_A,
-            site=self.site1,
-            port_speed=1000000
-        )
-        self.circuittermination4 = CircuitTermination.objects.create(
-            circuit=self.circuit2,
-            term_side=CircuitTerminationSideChoices.SIDE_Z,
-            site=self.site2,
-            port_speed=1000000
-        )
-
-    def test_get_circuittermination(self):
-
-        url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': self.circuittermination1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['id'], self.circuittermination1.pk)
-
-    def test_list_circuitterminations(self):
-
-        url = reverse('circuits-api:circuittermination-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 4)
-
-    def test_create_circuittermination(self):
-
-        data = {
-            'circuit': self.circuit3.pk,
-            'term_side': CircuitTerminationSideChoices.SIDE_A,
-            'site': self.site1.pk,
-            'port_speed': 1000000,
-        }
-
-        url = reverse('circuits-api:circuittermination-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(CircuitTermination.objects.count(), 5)
-        circuittermination4 = CircuitTermination.objects.get(pk=response.data['id'])
-        self.assertEqual(circuittermination4.circuit_id, data['circuit'])
-        self.assertEqual(circuittermination4.term_side, data['term_side'])
-        self.assertEqual(circuittermination4.site_id, data['site'])
-        self.assertEqual(circuittermination4.port_speed, data['port_speed'])
-
-    def test_update_circuittermination(self):
-
-        circuittermination5 = CircuitTermination.objects.create(
-            circuit=self.circuit3,
-            term_side=CircuitTerminationSideChoices.SIDE_A,
-            site=self.site1,
-            port_speed=1000000
-        )
-
-        data = {
-            'circuit': self.circuit3.pk,
-            'term_side': CircuitTerminationSideChoices.SIDE_Z,
-            'site': self.site2.pk,
-            'port_speed': 1000000,
-        }
-
-        url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': circuittermination5.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(CircuitTermination.objects.count(), 5)
-        circuittermination1 = CircuitTermination.objects.get(pk=response.data['id'])
-        self.assertEqual(circuittermination1.term_side, data['term_side'])
-        self.assertEqual(circuittermination1.site_id, data['site'])
-        self.assertEqual(circuittermination1.port_speed, data['port_speed'])
-
-    def test_delete_circuittermination(self):
-
-        url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': self.circuittermination1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(CircuitTermination.objects.count(), 3)
diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py
--- a/netbox/dcim/tests/test_api.py
+++ b/netbox/dcim/tests/test_api.py
@@ -1,6 +1,6 @@
+from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.urls import reverse
-from netaddr import IPNetwork
 from rest_framework import status
 
 from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
@@ -12,9 +12,9 @@
     InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel,
     Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
 )
-from ipam.models import IPAddress, VLAN
+from ipam.models import VLAN
 from extras.models import Graph
-from utilities.testing import APITestCase
+from utilities.testing import APITestCase, APIViewTestCases
 from virtualization.models import Cluster, ClusterType
 
 
@@ -28,3410 +28,1376 @@ def test_root(self):
         self.assertEqual(response.status_code, 200)
 
 
-class RegionTest(APITestCase):
+class RegionTest(APIViewTestCases.APIViewTestCase):
+    model = Region
+    brief_fields = ['id', 'name', 'site_count', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Region 4',
+            'slug': 'region-4',
+        },
+        {
+            'name': 'Region 5',
+            'slug': 'region-5',
+        },
+        {
+            'name': 'Region 6',
+            'slug': 'region-6',
+        },
+    ]
 
-    def setUp(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        super().setUp()
+        Region.objects.create(name='Region 1', slug='region-1')
+        Region.objects.create(name='Region 2', slug='region-2')
+        Region.objects.create(name='Region 3', slug='region-3')
 
-        self.region1 = Region.objects.create(name='Test Region 1', slug='test-region-1')
-        self.region2 = Region.objects.create(name='Test Region 2', slug='test-region-2')
-        self.region3 = Region.objects.create(name='Test Region 3', slug='test-region-3')
 
-    def test_get_region(self):
+class SiteTest(APIViewTestCases.APIViewTestCase):
+    model = Site
+    brief_fields = ['id', 'name', 'slug', 'url']
 
-        url = reverse('dcim-api:region-detail', kwargs={'pk': self.region1.pk})
-        response = self.client.get(url, **self.header)
+    @classmethod
+    def setUpTestData(cls):
 
-        self.assertEqual(response.data['name'], self.region1.name)
+        regions = (
+            Region.objects.create(name='Test Region 1', slug='test-region-1'),
+            Region.objects.create(name='Test Region 2', slug='test-region-2'),
+        )
 
-    def test_list_regions(self):
+        sites = (
+            Site(region=regions[0], name='Site 1', slug='site-1'),
+            Site(region=regions[0], name='Site 2', slug='site-2'),
+            Site(region=regions[0], name='Site 3', slug='site-3'),
+        )
+        Site.objects.bulk_create(sites)
 
-        url = reverse('dcim-api:region-list')
-        response = self.client.get(url, **self.header)
+        cls.create_data = [
+            {
+                'name': 'Site 4',
+                'slug': 'site-4',
+                'region': regions[1].pk,
+                'status': SiteStatusChoices.STATUS_ACTIVE,
+            },
+            {
+                'name': 'Site 5',
+                'slug': 'site-5',
+                'region': regions[1].pk,
+                'status': SiteStatusChoices.STATUS_ACTIVE,
+            },
+            {
+                'name': 'Site 6',
+                'slug': 'site-6',
+                'region': regions[1].pk,
+                'status': SiteStatusChoices.STATUS_ACTIVE,
+            },
+        ]
 
-        self.assertEqual(response.data['count'], 3)
+    def test_get_site_graphs(self):
+        """
+        Test retrieval of Graphs assigned to Sites.
+        """
+        ct = ContentType.objects.get_for_model(Site)
+        graphs = (
+            Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?site={{ obj.slug }}&foo=1'),
+            Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?site={{ obj.slug }}&foo=2'),
+            Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?site={{ obj.slug }}&foo=3'),
+        )
+        Graph.objects.bulk_create(graphs)
 
-    def test_list_regions_brief(self):
+        url = reverse('dcim-api:site-graphs', kwargs={'pk': Site.objects.first().pk})
+        response = self.client.get(url, **self.header)
 
-        url = reverse('dcim-api:region-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+        self.assertEqual(len(response.data), 3)
+        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?site=site-1&foo=1')
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'site_count', 'slug', 'url']
-        )
 
-    def test_create_region(self):
+class RackGroupTest(APIViewTestCases.APIViewTestCase):
+    model = RackGroup
+    brief_fields = ['id', 'name', 'rack_count', 'slug', 'url']
 
-        data = {
-            'name': 'Test Region 4',
-            'slug': 'test-region-4',
-        }
+    @classmethod
+    def setUpTestData(cls):
 
-        url = reverse('dcim-api:region-list')
-        response = self.client.post(url, data, format='json', **self.header)
+        sites = (
+            Site(name='Site 1', slug='site-1'),
+            Site(name='Site 2', slug='site-2'),
+        )
+        Site.objects.bulk_create(sites)
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Region.objects.count(), 4)
-        region4 = Region.objects.get(pk=response.data['id'])
-        self.assertEqual(region4.name, data['name'])
-        self.assertEqual(region4.slug, data['slug'])
+        parent_rack_groups = (
+            RackGroup.objects.create(site=sites[0], name='Parent Rack Group 1', slug='parent-rack-group-1'),
+            RackGroup.objects.create(site=sites[1], name='Parent Rack Group 2', slug='parent-rack-group-2'),
+        )
 
-    def test_create_region_bulk(self):
+        RackGroup.objects.create(site=sites[0], name='Rack Group 1', slug='rack-group-1', parent=parent_rack_groups[0])
+        RackGroup.objects.create(site=sites[0], name='Rack Group 2', slug='rack-group-2', parent=parent_rack_groups[0])
+        RackGroup.objects.create(site=sites[0], name='Rack Group 3', slug='rack-group-3', parent=parent_rack_groups[0])
 
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Region 4',
-                'slug': 'test-region-4',
+                'name': 'Test Rack Group 4',
+                'slug': 'test-rack-group-4',
+                'site': sites[1].pk,
+                'parent': parent_rack_groups[1].pk,
             },
             {
-                'name': 'Test Region 5',
-                'slug': 'test-region-5',
+                'name': 'Test Rack Group 5',
+                'slug': 'test-rack-group-5',
+                'site': sites[1].pk,
+                'parent': parent_rack_groups[1].pk,
             },
             {
-                'name': 'Test Region 6',
-                'slug': 'test-region-6',
+                'name': 'Test Rack Group 6',
+                'slug': 'test-rack-group-6',
+                'site': sites[1].pk,
+                'parent': parent_rack_groups[1].pk,
             },
         ]
 
-        url = reverse('dcim-api:region-list')
-        response = self.client.post(url, data, format='json', **self.header)
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Region.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
+class RackRoleTest(APIViewTestCases.APIViewTestCase):
+    model = RackRole
+    brief_fields = ['id', 'name', 'rack_count', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Rack Role 4',
+            'slug': 'rack-role-4',
+            'color': 'ffff00',
+        },
+        {
+            'name': 'Rack Role 5',
+            'slug': 'rack-role-5',
+            'color': 'ffff00',
+        },
+        {
+            'name': 'Rack Role 6',
+            'slug': 'rack-role-6',
+            'color': 'ffff00',
+        },
+    ]
 
-    def test_update_region(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        data = {
-            'name': 'Test Region X',
-            'slug': 'test-region-x',
-        }
+        rack_roles = (
+            RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
+            RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
+            RackRole(name='Rack Role 3', slug='rack-role-3', color='0000ff'),
+        )
+        RackRole.objects.bulk_create(rack_roles)
 
-        url = reverse('dcim-api:region-detail', kwargs={'pk': self.region1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
 
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Region.objects.count(), 3)
-        region1 = Region.objects.get(pk=response.data['id'])
-        self.assertEqual(region1.name, data['name'])
-        self.assertEqual(region1.slug, data['slug'])
+class RackTest(APIViewTestCases.APIViewTestCase):
+    model = Rack
+    brief_fields = ['device_count', 'display_name', 'id', 'name', 'url']
 
-    def test_delete_region(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        url = reverse('dcim-api:region-detail', kwargs={'pk': self.region1.pk})
-        response = self.client.delete(url, **self.header)
+        sites = (
+            Site(name='Site 1', slug='site-1'),
+            Site(name='Site 2', slug='site-2'),
+        )
+        Site.objects.bulk_create(sites)
 
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Region.objects.count(), 2)
+        rack_groups = (
+            RackGroup.objects.create(site=sites[0], name='Rack Group 1', slug='rack-group-1'),
+            RackGroup.objects.create(site=sites[1], name='Rack Group 2', slug='rack-group-2'),
+        )
 
+        rack_roles = (
+            RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
+            RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
+        )
+        RackRole.objects.bulk_create(rack_roles)
 
-class SiteTest(APITestCase):
+        racks = (
+            Rack(site=sites[0], group=rack_groups[0], role=rack_roles[0], name='Rack 1'),
+            Rack(site=sites[0], group=rack_groups[0], role=rack_roles[0], name='Rack 2'),
+            Rack(site=sites[0], group=rack_groups[0], role=rack_roles[0], name='Rack 3'),
+        )
+        Rack.objects.bulk_create(racks)
 
-    def setUp(self):
+        cls.create_data = [
+            {
+                'name': 'Test Rack 4',
+                'site': sites[1].pk,
+                'group': rack_groups[1].pk,
+                'role': rack_roles[1].pk,
+            },
+            {
+                'name': 'Test Rack 5',
+                'site': sites[1].pk,
+                'group': rack_groups[1].pk,
+                'role': rack_roles[1].pk,
+            },
+            {
+                'name': 'Test Rack 6',
+                'site': sites[1].pk,
+                'group': rack_groups[1].pk,
+                'role': rack_roles[1].pk,
+            },
+        ]
 
-        super().setUp()
+    # TODO: Document this test
+    def test_get_elevation_rack_units(self):
+        rack = Rack.objects.first()
 
-        self.region1 = Region.objects.create(name='Test Region 1', slug='test-region-1')
-        self.region2 = Region.objects.create(name='Test Region 2', slug='test-region-2')
-        self.site1 = Site.objects.create(region=self.region1, name='Test Site 1', slug='test-site-1')
-        self.site2 = Site.objects.create(region=self.region1, name='Test Site 2', slug='test-site-2')
-        self.site3 = Site.objects.create(region=self.region1, name='Test Site 3', slug='test-site-3')
+        url = '{}?q=3'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk}))
+        response = self.client.get(url, **self.header)
 
-    def test_get_site(self):
+        self.assertEqual(response.data['count'], 13)
 
-        url = reverse('dcim-api:site-detail', kwargs={'pk': self.site1.pk})
+        url = '{}?q=U3'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk}))
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['name'], self.site1.name)
+        self.assertEqual(response.data['count'], 11)
 
-    def test_get_site_graphs(self):
+        url = '{}?q=10'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk}))
+        response = self.client.get(url, **self.header)
 
-        site_ct = ContentType.objects.get_for_model(Site)
-        self.graph1 = Graph.objects.create(
-            type=site_ct,
-            name='Test Graph 1',
-            source='http://example.com/graphs.py?site={{ obj.slug }}&foo=1'
-        )
-        self.graph2 = Graph.objects.create(
-            type=site_ct,
-            name='Test Graph 2',
-            source='http://example.com/graphs.py?site={{ obj.slug }}&foo=2'
-        )
-        self.graph3 = Graph.objects.create(
-            type=site_ct,
-            name='Test Graph 3',
-            source='http://example.com/graphs.py?site={{ obj.slug }}&foo=3'
-        )
+        self.assertEqual(response.data['count'], 1)
 
-        url = reverse('dcim-api:site-graphs', kwargs={'pk': self.site1.pk})
+        url = '{}?q=U20'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk}))
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(len(response.data), 3)
-        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?site=test-site-1&foo=1')
+        self.assertEqual(response.data['count'], 1)
+
+    def test_get_rack_elevation(self):
+        """
+        GET a single rack elevation.
+        """
+        rack = Rack.objects.first()
+        url = reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk})
+        response = self.client.get(url, **self.header)
 
-    def test_list_sites(self):
+        self.assertEqual(response.data['count'], 42)
 
-        url = reverse('dcim-api:site-list')
+    def test_get_rack_elevation_svg(self):
+        """
+        GET a single rack elevation in SVG format.
+        """
+        rack = Rack.objects.first()
+        url = '{}?render=svg'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk}))
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['count'], 3)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.get('Content-Type'), 'image/svg+xml')
 
-    def test_list_sites_brief(self):
 
-        url = reverse('dcim-api:site-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+class RackReservationTest(APIViewTestCases.APIViewTestCase):
+    model = RackReservation
+    brief_fields = ['id', 'units', 'url', 'user']
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'slug', 'url']
-        )
+    @classmethod
+    def setUpTestData(cls):
 
-    def test_create_site(self):
+        user = User.objects.create(username='user1', is_active=True)
 
-        data = {
-            'name': 'Test Site 4',
-            'slug': 'test-site-4',
-            'region': self.region1.pk,
-            'status': SiteStatusChoices.STATUS_ACTIVE,
-        }
+        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
 
-        url = reverse('dcim-api:site-list')
-        response = self.client.post(url, data, format='json', **self.header)
+        cls.racks = (
+            Rack(site=site, name='Rack 1'),
+            Rack(site=site, name='Rack 2'),
+        )
+        Rack.objects.bulk_create(cls.racks)
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Site.objects.count(), 4)
-        site4 = Site.objects.get(pk=response.data['id'])
-        self.assertEqual(site4.name, data['name'])
-        self.assertEqual(site4.slug, data['slug'])
-        self.assertEqual(site4.region_id, data['region'])
+        rack_reservations = (
+            RackReservation(rack=cls.racks[0], units=[1, 2, 3], user=user, description='Reservation #1'),
+            RackReservation(rack=cls.racks[0], units=[4, 5, 6], user=user, description='Reservation #2'),
+            RackReservation(rack=cls.racks[0], units=[7, 8, 9], user=user, description='Reservation #3'),
+        )
+        RackReservation.objects.bulk_create(rack_reservations)
 
-    def test_create_site_bulk(self):
+    def setUp(self):
+        super().setUp()
 
-        data = [
+        # We have to set creation data under setUp() because we need access to the test user.
+        self.create_data = [
             {
-                'name': 'Test Site 4',
-                'slug': 'test-site-4',
-                'region': self.region1.pk,
-                'status': SiteStatusChoices.STATUS_ACTIVE,
+                'rack': self.racks[1].pk,
+                'units': [10, 11, 12],
+                'user': self.user.pk,
+                'description': 'Reservation #4',
             },
             {
-                'name': 'Test Site 5',
-                'slug': 'test-site-5',
-                'region': self.region1.pk,
-                'status': SiteStatusChoices.STATUS_ACTIVE,
+                'rack': self.racks[1].pk,
+                'units': [13, 14, 15],
+                'user': self.user.pk,
+                'description': 'Reservation #5',
             },
             {
-                'name': 'Test Site 6',
-                'slug': 'test-site-6',
-                'region': self.region1.pk,
-                'status': SiteStatusChoices.STATUS_ACTIVE,
+                'rack': self.racks[1].pk,
+                'units': [16, 17, 18],
+                'user': self.user.pk,
+                'description': 'Reservation #6',
             },
         ]
 
-        url = reverse('dcim-api:site-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Site.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_site(self):
-
-        data = {
-            'name': 'Test Site X',
-            'slug': 'test-site-x',
-            'region': self.region2.pk,
-        }
 
-        url = reverse('dcim-api:site-detail', kwargs={'pk': self.site1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Site.objects.count(), 3)
-        site1 = Site.objects.get(pk=response.data['id'])
-        self.assertEqual(site1.name, data['name'])
-        self.assertEqual(site1.slug, data['slug'])
-        self.assertEqual(site1.region_id, data['region'])
+class ManufacturerTest(APIViewTestCases.APIViewTestCase):
+    model = Manufacturer
+    brief_fields = ['devicetype_count', 'id', 'name', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Manufacturer 4',
+            'slug': 'manufacturer-4',
+        },
+        {
+            'name': 'Manufacturer 5',
+            'slug': 'manufacturer-5',
+        },
+        {
+            'name': 'Manufacturer 6',
+            'slug': 'manufacturer-6',
+        },
+    ]
 
-    def test_delete_site(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        url = reverse('dcim-api:site-detail', kwargs={'pk': self.site1.pk})
-        response = self.client.delete(url, **self.header)
+        manufacturers = (
+            Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+            Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+            Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
+        )
+        Manufacturer.objects.bulk_create(manufacturers)
 
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Site.objects.count(), 2)
 
+class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
+    model = DeviceType
+    brief_fields = ['device_count', 'display_name', 'id', 'manufacturer', 'model', 'slug', 'url']
 
-class RackGroupTest(APITestCase):
+    @classmethod
+    def setUpTestData(cls):
 
-    def setUp(self):
+        manufacturers = (
+            Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+            Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+        )
+        Manufacturer.objects.bulk_create(manufacturers)
 
-        super().setUp()
+        device_types = (
+            DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
+            DeviceType(manufacturer=manufacturers[0], model='Device Type 2', slug='device-type-2'),
+            DeviceType(manufacturer=manufacturers[0], model='Device Type 3', slug='device-type-3'),
+        )
+        DeviceType.objects.bulk_create(device_types)
 
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
-        self.parent_rackgroup1 = RackGroup.objects.create(site=self.site1, name='Parent Rack Group 1', slug='parent-rack-group-1')
-        self.parent_rackgroup2 = RackGroup.objects.create(site=self.site2, name='Parent Rack Group 2', slug='parent-rack-group-2')
-        self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Rack Group 1', slug='rack-group-1', parent=self.parent_rackgroup1)
-        self.rackgroup2 = RackGroup.objects.create(site=self.site1, name='Rack Group 2', slug='rack-group-2', parent=self.parent_rackgroup1)
-        self.rackgroup3 = RackGroup.objects.create(site=self.site1, name='Rack Group 3', slug='rack-group-3', parent=self.parent_rackgroup1)
+        cls.create_data = [
+            {
+                'manufacturer': manufacturers[1].pk,
+                'model': 'Device Type 4',
+                'slug': 'device-type-4',
+            },
+            {
+                'manufacturer': manufacturers[1].pk,
+                'model': 'Device Type 5',
+                'slug': 'device-type-5',
+            },
+            {
+                'manufacturer': manufacturers[1].pk,
+                'model': 'Device Type 6',
+                'slug': 'device-type-6',
+            },
+        ]
 
-    def test_get_rackgroup(self):
 
-        url = reverse('dcim-api:rackgroup-detail', kwargs={'pk': self.rackgroup1.pk})
-        response = self.client.get(url, **self.header)
+class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = ConsolePortTemplate
+    brief_fields = ['id', 'name', 'url']
 
-        self.assertEqual(response.data['name'], self.rackgroup1.name)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
+        )
 
-    def test_list_rackgroups(self):
+        console_port_templates = (
+            ConsolePortTemplate(device_type=devicetype, name='Console Port Template 1'),
+            ConsolePortTemplate(device_type=devicetype, name='Console Port Template 2'),
+            ConsolePortTemplate(device_type=devicetype, name='Console Port Template 3'),
+        )
+        ConsolePortTemplate.objects.bulk_create(console_port_templates)
 
-        url = reverse('dcim-api:rackgroup-list')
-        response = self.client.get(url, **self.header)
+        cls.create_data = [
+            {
+                'device_type': devicetype.pk,
+                'name': 'Console Port Template 4',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Console Port Template 5',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Console Port Template 6',
+            },
+        ]
 
-        self.assertEqual(response.data['count'], 5)
 
-    def test_list_rackgroups_brief(self):
+class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = ConsoleServerPortTemplate
+    brief_fields = ['id', 'name', 'url']
 
-        url = reverse('dcim-api:rackgroup-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
+        )
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'rack_count', 'slug', 'url']
+        console_server_port_templates = (
+            ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 1'),
+            ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 2'),
+            ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 3'),
         )
+        ConsoleServerPortTemplate.objects.bulk_create(console_server_port_templates)
 
-    def test_create_rackgroup(self):
+        cls.create_data = [
+            {
+                'device_type': devicetype.pk,
+                'name': 'Console Server Port Template 4',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Console Server Port Template 5',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Console Server Port Template 6',
+            },
+        ]
 
-        data = {
-            'name': 'Rack Group 4',
-            'slug': 'rack-group-4',
-            'site': self.site1.pk,
-            'parent': self.parent_rackgroup1.pk,
-        }
 
-        url = reverse('dcim-api:rackgroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
+class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = PowerPortTemplate
+    brief_fields = ['id', 'name', 'url']
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RackGroup.objects.count(), 6)
-        rackgroup4 = RackGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(rackgroup4.name, data['name'])
-        self.assertEqual(rackgroup4.slug, data['slug'])
-        self.assertEqual(rackgroup4.site_id, data['site'])
-        self.assertEqual(rackgroup4.parent_id, data['parent'])
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
+        )
 
-    def test_create_rackgroup_bulk(self):
+        power_port_templates = (
+            PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
+            PowerPortTemplate(device_type=devicetype, name='Power Port Template 2'),
+            PowerPortTemplate(device_type=devicetype, name='Power Port Template 3'),
+        )
+        PowerPortTemplate.objects.bulk_create(power_port_templates)
 
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Rack Group 4',
-                'slug': 'test-rack-group-4',
-                'site': self.site1.pk,
-                'parent': self.parent_rackgroup1.pk,
+                'device_type': devicetype.pk,
+                'name': 'Power Port Template 4',
             },
             {
-                'name': 'Test Rack Group 5',
-                'slug': 'test-rack-group-5',
-                'site': self.site1.pk,
-                'parent': self.parent_rackgroup1.pk,
+                'device_type': devicetype.pk,
+                'name': 'Power Port Template 5',
             },
             {
-                'name': 'Test Rack Group 6',
-                'slug': 'test-rack-group-6',
-                'site': self.site1.pk,
-                'parent': self.parent_rackgroup1.pk,
+                'device_type': devicetype.pk,
+                'name': 'Power Port Template 6',
             },
         ]
 
-        url = reverse('dcim-api:rackgroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RackGroup.objects.count(), 8)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
+class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = PowerOutletTemplate
+    brief_fields = ['id', 'name', 'url']
 
-    def test_update_rackgroup(self):
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
+        )
 
-        data = {
-            'name': 'Test Rack Group X',
-            'slug': 'test-rack-group-x',
-            'site': self.site2.pk,
-            'parent': self.parent_rackgroup2.pk,
-        }
+        power_outlet_templates = (
+            PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 1'),
+            PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 2'),
+            PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 3'),
+        )
+        PowerOutletTemplate.objects.bulk_create(power_outlet_templates)
 
-        url = reverse('dcim-api:rackgroup-detail', kwargs={'pk': self.rackgroup1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
+        cls.create_data = [
+            {
+                'device_type': devicetype.pk,
+                'name': 'Power Outlet Template 4',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Power Outlet Template 5',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Power Outlet Template 6',
+            },
+        ]
 
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(RackGroup.objects.count(), 5)
-        rackgroup1 = RackGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(rackgroup1.name, data['name'])
-        self.assertEqual(rackgroup1.slug, data['slug'])
-        self.assertEqual(rackgroup1.site_id, data['site'])
-        self.assertEqual(rackgroup1.parent_id, data['parent'])
 
-    def test_delete_rackgroup(self):
+class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = InterfaceTemplate
+    brief_fields = ['id', 'name', 'url']
 
-        url = reverse('dcim-api:rackgroup-detail', kwargs={'pk': self.rackgroup1.pk})
-        response = self.client.delete(url, **self.header)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
+        )
 
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(RackGroup.objects.count(), 4)
+        interface_templates = (
+            InterfaceTemplate(device_type=devicetype, name='Interface Template 1', type='1000base-t'),
+            InterfaceTemplate(device_type=devicetype, name='Interface Template 2', type='1000base-t'),
+            InterfaceTemplate(device_type=devicetype, name='Interface Template 3', type='1000base-t'),
+        )
+        InterfaceTemplate.objects.bulk_create(interface_templates)
 
+        cls.create_data = [
+            {
+                'device_type': devicetype.pk,
+                'name': 'Interface Template 4',
+                'type': '1000base-t',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Interface Template 5',
+                'type': '1000base-t',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Interface Template 6',
+                'type': '1000base-t',
+            },
+        ]
 
-class RackRoleTest(APITestCase):
 
-    def setUp(self):
+class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = DeviceBayTemplate
+    brief_fields = ['id', 'name', 'url']
 
-        super().setUp()
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
+        )
 
-        self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
-        self.rackrole2 = RackRole.objects.create(name='Test Rack Role 2', slug='test-rack-role-2', color='00ff00')
-        self.rackrole3 = RackRole.objects.create(name='Test Rack Role 3', slug='test-rack-role-3', color='0000ff')
+        device_bay_templates = (
+            DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1'),
+            DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2'),
+            DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3'),
+        )
+        DeviceBayTemplate.objects.bulk_create(device_bay_templates)
 
-    def test_get_rackrole(self):
+        cls.create_data = [
+            {
+                'device_type': devicetype.pk,
+                'name': 'Device Bay Template 4',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Device Bay Template 5',
+            },
+            {
+                'device_type': devicetype.pk,
+                'name': 'Device Bay Template 6',
+            },
+        ]
 
-        url = reverse('dcim-api:rackrole-detail', kwargs={'pk': self.rackrole1.pk})
-        response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['name'], self.rackrole1.name)
+class DeviceRoleTest(APIViewTestCases.APIViewTestCase):
+    model = DeviceRole
+    brief_fields = ['device_count', 'id', 'name', 'slug', 'url', 'virtualmachine_count']
+    create_data = [
+        {
+            'name': 'Device Role 4',
+            'slug': 'device-role-4',
+            'color': 'ffff00',
+        },
+        {
+            'name': 'Device Role 5',
+            'slug': 'device-role-5',
+            'color': 'ffff00',
+        },
+        {
+            'name': 'Device Role 6',
+            'slug': 'device-role-6',
+            'color': 'ffff00',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        device_roles = (
+            DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000'),
+            DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00'),
+            DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff'),
+        )
+        DeviceRole.objects.bulk_create(device_roles)
+
+
+class PlatformTest(APIViewTestCases.APIViewTestCase):
+    model = Platform
+    brief_fields = ['device_count', 'id', 'name', 'slug', 'url', 'virtualmachine_count']
+    create_data = [
+        {
+            'name': 'Platform 4',
+            'slug': 'platform-4',
+        },
+        {
+            'name': 'Platform 5',
+            'slug': 'platform-5',
+        },
+        {
+            'name': 'Platform 6',
+            'slug': 'platform-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        platforms = (
+            Platform(name='Platform 1', slug='platform-1'),
+            Platform(name='Platform 2', slug='platform-2'),
+            Platform(name='Platform 3', slug='platform-3'),
+        )
+        Platform.objects.bulk_create(platforms)
+
+
+class DeviceTest(APIViewTestCases.APIViewTestCase):
+    model = Device
+    brief_fields = ['display_name', 'id', 'name', 'url']
+
+    @classmethod
+    def setUpTestData(cls):
+
+        sites = (
+            Site(name='Site 1', slug='site-1'),
+            Site(name='Site 2', slug='site-2'),
+        )
+        Site.objects.bulk_create(sites)
+
+        racks = (
+            Rack(name='Rack 1', site=sites[0]),
+            Rack(name='Rack 2', site=sites[1]),
+        )
+        Rack.objects.bulk_create(racks)
+
+        manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+        device_types = (
+            DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
+            DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
+        )
+        DeviceType.objects.bulk_create(device_types)
+
+        device_roles = (
+            DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000'),
+            DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00'),
+        )
+        DeviceRole.objects.bulk_create(device_roles)
+
+        cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
+
+        clusters = (
+            Cluster(name='Cluster 1', type=cluster_type),
+            Cluster(name='Cluster 2', type=cluster_type),
+        )
+        Cluster.objects.bulk_create(clusters)
+
+        devices = (
+            Device(
+                device_type=device_types[0],
+                device_role=device_roles[0],
+                name='Device 1',
+                site=sites[0],
+                rack=racks[0],
+                cluster=clusters[0],
+                local_context_data={'A': 1}
+            ),
+            Device(
+                device_type=device_types[0],
+                device_role=device_roles[0],
+                name='Device 2',
+                site=sites[0],
+                rack=racks[0],
+                cluster=clusters[0],
+                local_context_data={'B': 2}
+            ),
+            Device(
+                device_type=device_types[0],
+                device_role=device_roles[0],
+                name='Device 3',
+                site=sites[0],
+                rack=racks[0],
+                cluster=clusters[0],
+                local_context_data={'C': 3}
+            ),
+        )
+        Device.objects.bulk_create(devices)
+
+        cls.create_data = [
+            {
+                'device_type': device_types[1].pk,
+                'device_role': device_roles[1].pk,
+                'name': 'Test Device 4',
+                'site': sites[1].pk,
+                'rack': racks[1].pk,
+                'cluster': clusters[1].pk,
+            },
+            {
+                'device_type': device_types[1].pk,
+                'device_role': device_roles[1].pk,
+                'name': 'Test Device 5',
+                'site': sites[1].pk,
+                'rack': racks[1].pk,
+                'cluster': clusters[1].pk,
+            },
+            {
+                'device_type': device_types[1].pk,
+                'device_role': device_roles[1].pk,
+                'name': 'Test Device 6',
+                'site': sites[1].pk,
+                'rack': racks[1].pk,
+                'cluster': clusters[1].pk,
+            },
+        ]
 
-    def test_list_rackroles(self):
+    def test_get_device_graphs(self):
+        """
+        Test retrieval of Graphs assigned to Devices.
+        """
+        ct = ContentType.objects.get_for_model(Device)
+        graphs = (
+            Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?device={{ obj.name }}&foo=1'),
+            Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?device={{ obj.name }}&foo=2'),
+            Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?device={{ obj.name }}&foo=3'),
+        )
+        Graph.objects.bulk_create(graphs)
 
-        url = reverse('dcim-api:rackrole-list')
+        url = reverse('dcim-api:device-graphs', kwargs={'pk': Device.objects.first().pk})
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['count'], 3)
+        self.assertEqual(len(response.data), 3)
+        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?device=Device 1&foo=1')
 
-    def test_list_rackroles_brief(self):
+    def test_config_context_included_by_default_in_list_view(self):
+        """
+        Check that config context data is included by default in the devices list.
+        """
+        url = reverse('dcim-api:device-list') + '?slug=device-with-context-data'
+        response = self.client.get(url, **self.header)
 
-        url = reverse('dcim-api:rackrole-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+        self.assertEqual(response.data['results'][0].get('config_context', {}).get('A'), 1)
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'rack_count', 'slug', 'url']
-        )
+    def test_config_context_excluded(self):
+        """
+        Check that config context data can be excluded by passing ?exclude=config_context.
+        """
+        url = reverse('dcim-api:device-list') + '?exclude=config_context'
+        response = self.client.get(url, **self.header)
 
-    def test_create_rackrole(self):
+        self.assertFalse('config_context' in response.data['results'][0])
 
+    def test_unique_name_per_site_constraint(self):
+        """
+        Check that creating a device with a duplicate name within a site fails.
+        """
+        device = Device.objects.first()
         data = {
-            'name': 'Test Rack Role 4',
-            'slug': 'test-rack-role-4',
-            'color': 'ffff00',
+            'device_type': device.device_type.pk,
+            'device_role': device.device_role.pk,
+            'site': device.site.pk,
+            'name': device.name,
         }
 
-        url = reverse('dcim-api:rackrole-list')
+        url = reverse('dcim-api:device-list')
         response = self.client.post(url, data, format='json', **self.header)
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RackRole.objects.count(), 4)
-        rackrole1 = RackRole.objects.get(pk=response.data['id'])
-        self.assertEqual(rackrole1.name, data['name'])
-        self.assertEqual(rackrole1.slug, data['slug'])
-        self.assertEqual(rackrole1.color, data['color'])
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
 
-    def test_create_rackrole_bulk(self):
 
-        data = [
+class ConsolePortTest(APIViewTestCases.APIViewTestCase):
+    model = ConsolePort
+    brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
+
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
+
+        console_ports = (
+            ConsolePort(device=device, name='Console Port 1'),
+            ConsolePort(device=device, name='Console Port 2'),
+            ConsolePort(device=device, name='Console Port 3'),
+        )
+        ConsolePort.objects.bulk_create(console_ports)
+
+        cls.create_data = [
             {
-                'name': 'Test Rack Role 4',
-                'slug': 'test-rack-role-4',
-                'color': 'ffff00',
+                'device': device.pk,
+                'name': 'Console Port 4',
             },
             {
-                'name': 'Test Rack Role 5',
-                'slug': 'test-rack-role-5',
-                'color': 'ffff00',
+                'device': device.pk,
+                'name': 'Console Port 5',
             },
             {
-                'name': 'Test Rack Role 6',
-                'slug': 'test-rack-role-6',
-                'color': 'ffff00',
+                'device': device.pk,
+                'name': 'Console Port 6',
             },
         ]
 
-        url = reverse('dcim-api:rackrole-list')
-        response = self.client.post(url, data, format='json', **self.header)
+    def test_trace_consoleport(self):
+        """
+        Test tracing a ConsolePort cable.
+        """
+        consoleport = ConsolePort.objects.first()
+        peer_device = Device.objects.create(
+            site=Site.objects.first(),
+            device_type=DeviceType.objects.first(),
+            device_role=DeviceRole.objects.first(),
+            name='Peer Device'
+        )
+        consoleserverport = ConsoleServerPort.objects.create(
+            device=peer_device,
+            name='Console Server Port 1'
+        )
+        cable = Cable(termination_a=consoleport, termination_b=consoleserverport, label='Cable 1')
+        cable.save()
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RackRole.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
+        url = reverse('dcim-api:consoleport-trace', kwargs={'pk': consoleport.pk})
+        response = self.client.get(url, **self.header)
 
-    def test_update_rackrole(self):
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(len(response.data), 1)
+        segment1 = response.data[0]
+        self.assertEqual(segment1[0]['name'], consoleport.name)
+        self.assertEqual(segment1[1]['label'], cable.label)
+        self.assertEqual(segment1[2]['name'], consoleserverport.name)
 
-        data = {
-            'name': 'Test Rack Role X',
-            'slug': 'test-rack-role-x',
-            'color': 'ffff00',
-        }
-
-        url = reverse('dcim-api:rackrole-detail', kwargs={'pk': self.rackrole1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(RackRole.objects.count(), 3)
-        rackrole1 = RackRole.objects.get(pk=response.data['id'])
-        self.assertEqual(rackrole1.name, data['name'])
-        self.assertEqual(rackrole1.slug, data['slug'])
-        self.assertEqual(rackrole1.color, data['color'])
-
-    def test_delete_rackrole(self):
-
-        url = reverse('dcim-api:rackrole-detail', kwargs={'pk': self.rackrole1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(RackRole.objects.count(), 2)
-
-
-class RackTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
-        self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1')
-        self.rackgroup2 = RackGroup.objects.create(site=self.site2, name='Test Rack Group 2', slug='test-rack-group-2')
-        self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
-        self.rackrole2 = RackRole.objects.create(name='Test Rack Role 2', slug='test-rack-role-2', color='00ff00')
-        self.rack1 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 1', u_height=42,
-        )
-        self.rack2 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 2', u_height=42,
-        )
-        self.rack3 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 3', u_height=42,
-        )
-
-    def test_get_rack(self):
-
-        url = reverse('dcim-api:rack-detail', kwargs={'pk': self.rack1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.rack1.name)
-
-    def test_get_elevation_rack_units(self):
-
-        url = '{}?q=3'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk}))
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 13)
-
-        url = '{}?q=U3'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk}))
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 11)
-
-        url = '{}?q=10'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk}))
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 1)
-
-        url = '{}?q=U20'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk}))
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 1)
-
-    def test_get_rack_elevation(self):
-
-        url = reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 42)
-
-    def test_get_rack_elevation_svg(self):
-
-        url = '{}?render=svg'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk}))
-        response = self.client.get(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(response.get('Content-Type'), 'image/svg+xml')
-
-    def test_list_racks(self):
-
-        url = reverse('dcim-api:rack-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_racks_brief(self):
-
-        url = reverse('dcim-api:rack-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['device_count', 'display_name', 'id', 'name', 'url']
-        )
-
-    def test_create_rack(self):
-
-        data = {
-            'name': 'Test Rack 4',
-            'facility_id': '1234',
-            'site': self.site1.pk,
-            'group': self.rackgroup1.pk,
-            'role': self.rackrole1.pk,
-        }
-
-        url = reverse('dcim-api:rack-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Rack.objects.count(), 4)
-        rack4 = Rack.objects.get(pk=response.data['id'])
-        self.assertEqual(rack4.name, data['name'])
-        self.assertEqual(rack4.site_id, data['site'])
-        self.assertEqual(rack4.group_id, data['group'])
-        self.assertEqual(rack4.role_id, data['role'])
-
-    def test_create_rack_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Rack 4',
-                'site': self.site1.pk,
-                'group': self.rackgroup1.pk,
-                'role': self.rackrole1.pk,
-            },
-            {
-                'name': 'Test Rack 5',
-                'site': self.site1.pk,
-                'group': self.rackgroup1.pk,
-                'role': self.rackrole1.pk,
-            },
-            {
-                'name': 'Test Rack 6',
-                'site': self.site1.pk,
-                'group': self.rackgroup1.pk,
-                'role': self.rackrole1.pk,
-            },
-        ]
-
-        url = reverse('dcim-api:rack-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Rack.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_rack(self):
-
-        data = {
-            'name': 'Test Rack X',
-            'site': self.site2.pk,
-            'group': self.rackgroup2.pk,
-            'role': self.rackrole2.pk,
-        }
-
-        url = reverse('dcim-api:rack-detail', kwargs={'pk': self.rack1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Rack.objects.count(), 3)
-        rack1 = Rack.objects.get(pk=response.data['id'])
-        self.assertEqual(rack1.name, data['name'])
-        self.assertEqual(rack1.site_id, data['site'])
-        self.assertEqual(rack1.group_id, data['group'])
-        self.assertEqual(rack1.role_id, data['role'])
-
-    def test_delete_rack(self):
-
-        url = reverse('dcim-api:rack-detail', kwargs={'pk': self.rack1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Rack.objects.count(), 2)
-
-
-class RackReservationTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.rack1 = Rack.objects.create(site=self.site1, name='Test Rack 1')
-        self.rackreservation1 = RackReservation.objects.create(
-            rack=self.rack1, units=[1, 2, 3], user=self.user, description='Reservation #1',
-        )
-        self.rackreservation2 = RackReservation.objects.create(
-            rack=self.rack1, units=[4, 5, 6], user=self.user, description='Reservation #2',
-        )
-        self.rackreservation3 = RackReservation.objects.create(
-            rack=self.rack1, units=[7, 8, 9], user=self.user, description='Reservation #3',
-        )
-
-    def test_get_rackreservation(self):
-
-        url = reverse('dcim-api:rackreservation-detail', kwargs={'pk': self.rackreservation1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['id'], self.rackreservation1.pk)
-
-    def test_list_rackreservations(self):
-
-        url = reverse('dcim-api:rackreservation-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_rackreservation(self):
-
-        data = {
-            'rack': self.rack1.pk,
-            'units': [10, 11, 12],
-            'user': self.user.pk,
-            'description': 'Fourth reservation',
-        }
-
-        url = reverse('dcim-api:rackreservation-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RackReservation.objects.count(), 4)
-        rackreservation4 = RackReservation.objects.get(pk=response.data['id'])
-        self.assertEqual(rackreservation4.rack_id, data['rack'])
-        self.assertEqual(rackreservation4.units, data['units'])
-        self.assertEqual(rackreservation4.user_id, data['user'])
-        self.assertEqual(rackreservation4.description, data['description'])
-
-    def test_create_rackreservation_bulk(self):
-
-        data = [
-            {
-                'rack': self.rack1.pk,
-                'units': [10, 11, 12],
-                'user': self.user.pk,
-                'description': 'Reservation #4',
-            },
-            {
-                'rack': self.rack1.pk,
-                'units': [13, 14, 15],
-                'user': self.user.pk,
-                'description': 'Reservation #5',
-            },
-            {
-                'rack': self.rack1.pk,
-                'units': [16, 17, 18],
-                'user': self.user.pk,
-                'description': 'Reservation #6',
-            },
-        ]
-
-        url = reverse('dcim-api:rackreservation-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RackReservation.objects.count(), 6)
-        self.assertEqual(response.data[0]['description'], data[0]['description'])
-        self.assertEqual(response.data[1]['description'], data[1]['description'])
-        self.assertEqual(response.data[2]['description'], data[2]['description'])
-
-    def test_update_rackreservation(self):
-
-        data = {
-            'rack': self.rack1.pk,
-            'units': [10, 11, 12],
-            'user': self.user.pk,
-            'description': 'Modified reservation',
-        }
-
-        url = reverse('dcim-api:rackreservation-detail', kwargs={'pk': self.rackreservation1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(RackReservation.objects.count(), 3)
-        rackreservation1 = RackReservation.objects.get(pk=response.data['id'])
-        self.assertEqual(rackreservation1.units, data['units'])
-        self.assertEqual(rackreservation1.description, data['description'])
-
-    def test_delete_rackreservation(self):
-
-        url = reverse('dcim-api:rackreservation-detail', kwargs={'pk': self.rackreservation1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(RackReservation.objects.count(), 2)
-
-
-class ManufacturerTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer1 = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.manufacturer2 = Manufacturer.objects.create(name='Test Manufacturer 2', slug='test-manufacturer-2')
-        self.manufacturer3 = Manufacturer.objects.create(name='Test Manufacturer 3', slug='test-manufacturer-3')
-
-    def test_get_manufacturer(self):
-
-        url = reverse('dcim-api:manufacturer-detail', kwargs={'pk': self.manufacturer1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.manufacturer1.name)
-
-    def test_list_manufacturers(self):
-
-        url = reverse('dcim-api:manufacturer-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_manufacturers_brief(self):
-
-        url = reverse('dcim-api:manufacturer-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['devicetype_count', 'id', 'name', 'slug', 'url']
-        )
-
-    def test_create_manufacturer(self):
-
-        data = {
-            'name': 'Test Manufacturer 4',
-            'slug': 'test-manufacturer-4',
-        }
-
-        url = reverse('dcim-api:manufacturer-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Manufacturer.objects.count(), 4)
-        manufacturer4 = Manufacturer.objects.get(pk=response.data['id'])
-        self.assertEqual(manufacturer4.name, data['name'])
-        self.assertEqual(manufacturer4.slug, data['slug'])
-
-    def test_create_manufacturer_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Manufacturer 4',
-                'slug': 'test-manufacturer-4',
-            },
-            {
-                'name': 'Test Manufacturer 5',
-                'slug': 'test-manufacturer-5',
-            },
-            {
-                'name': 'Test Manufacturer 6',
-                'slug': 'test-manufacturer-6',
-            },
-        ]
-
-        url = reverse('dcim-api:manufacturer-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Manufacturer.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_manufacturer(self):
-
-        data = {
-            'name': 'Test Manufacturer X',
-            'slug': 'test-manufacturer-x',
-        }
-
-        url = reverse('dcim-api:manufacturer-detail', kwargs={'pk': self.manufacturer1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Manufacturer.objects.count(), 3)
-        manufacturer1 = Manufacturer.objects.get(pk=response.data['id'])
-        self.assertEqual(manufacturer1.name, data['name'])
-        self.assertEqual(manufacturer1.slug, data['slug'])
-
-    def test_delete_manufacturer(self):
-
-        url = reverse('dcim-api:manufacturer-detail', kwargs={'pk': self.manufacturer1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Manufacturer.objects.count(), 2)
-
-
-class DeviceTypeTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer1 = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.manufacturer2 = Manufacturer.objects.create(name='Test Manufacturer 2', slug='test-manufacturer-2')
-        self.devicetype1 = DeviceType.objects.create(
-            manufacturer=self.manufacturer1, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.devicetype2 = DeviceType.objects.create(
-            manufacturer=self.manufacturer1, model='Test Device Type 2', slug='test-device-type-2'
-        )
-        self.devicetype3 = DeviceType.objects.create(
-            manufacturer=self.manufacturer1, model='Test Device Type 3', slug='test-device-type-3'
-        )
-
-    def test_get_devicetype(self):
-
-        url = reverse('dcim-api:devicetype-detail', kwargs={'pk': self.devicetype1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['model'], self.devicetype1.model)
-
-    def test_list_devicetypes(self):
-
-        url = reverse('dcim-api:devicetype-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_devicetypes_brief(self):
-
-        url = reverse('dcim-api:devicetype-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['device_count', 'display_name', 'id', 'manufacturer', 'model', 'slug', 'url']
-        )
-
-    def test_create_devicetype(self):
-
-        data = {
-            'manufacturer': self.manufacturer1.pk,
-            'model': 'Test Device Type 4',
-            'slug': 'test-device-type-4',
-        }
-
-        url = reverse('dcim-api:devicetype-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceType.objects.count(), 4)
-        devicetype4 = DeviceType.objects.get(pk=response.data['id'])
-        self.assertEqual(devicetype4.manufacturer_id, data['manufacturer'])
-        self.assertEqual(devicetype4.model, data['model'])
-        self.assertEqual(devicetype4.slug, data['slug'])
-
-    def test_create_devicetype_bulk(self):
-
-        data = [
-            {
-                'manufacturer': self.manufacturer1.pk,
-                'model': 'Test Device Type 4',
-                'slug': 'test-device-type-4',
-            },
-            {
-                'manufacturer': self.manufacturer1.pk,
-                'model': 'Test Device Type 5',
-                'slug': 'test-device-type-5',
-            },
-            {
-                'manufacturer': self.manufacturer1.pk,
-                'model': 'Test Device Type 6',
-                'slug': 'test-device-type-6',
-            },
-        ]
-
-        url = reverse('dcim-api:devicetype-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceType.objects.count(), 6)
-        self.assertEqual(response.data[0]['model'], data[0]['model'])
-        self.assertEqual(response.data[1]['model'], data[1]['model'])
-        self.assertEqual(response.data[2]['model'], data[2]['model'])
-
-    def test_update_devicetype(self):
-
-        data = {
-            'manufacturer': self.manufacturer2.pk,
-            'model': 'Test Device Type X',
-            'slug': 'test-device-type-x',
-        }
-
-        url = reverse('dcim-api:devicetype-detail', kwargs={'pk': self.devicetype1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(DeviceType.objects.count(), 3)
-        devicetype1 = DeviceType.objects.get(pk=response.data['id'])
-        self.assertEqual(devicetype1.manufacturer_id, data['manufacturer'])
-        self.assertEqual(devicetype1.model, data['model'])
-        self.assertEqual(devicetype1.slug, data['slug'])
-
-    def test_delete_devicetype(self):
-
-        url = reverse('dcim-api:devicetype-detail', kwargs={'pk': self.devicetype1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(DeviceType.objects.count(), 2)
-
-
-class ConsolePortTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.consoleporttemplate1 = ConsolePortTemplate.objects.create(
-            device_type=self.devicetype, name='Test CP Template 1'
-        )
-        self.consoleporttemplate2 = ConsolePortTemplate.objects.create(
-            device_type=self.devicetype, name='Test CP Template 2'
-        )
-        self.consoleporttemplate3 = ConsolePortTemplate.objects.create(
-            device_type=self.devicetype, name='Test CP Template 3'
-        )
-
-    def test_get_consoleporttemplate(self):
-
-        url = reverse('dcim-api:consoleporttemplate-detail', kwargs={'pk': self.consoleporttemplate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.consoleporttemplate1.name)
-
-    def test_list_consoleporttemplates(self):
-
-        url = reverse('dcim-api:consoleporttemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_consoleporttemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test CP Template 4',
-        }
-
-        url = reverse('dcim-api:consoleporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsolePortTemplate.objects.count(), 4)
-        consoleporttemplate4 = ConsolePortTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleporttemplate4.device_type_id, data['device_type'])
-        self.assertEqual(consoleporttemplate4.name, data['name'])
-
-    def test_create_consoleporttemplate_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test CP Template 4',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test CP Template 5',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test CP Template 6',
-            },
-        ]
-
-        url = reverse('dcim-api:consoleporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsolePortTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_consoleporttemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test CP Template X',
-        }
-
-        url = reverse('dcim-api:consoleporttemplate-detail', kwargs={'pk': self.consoleporttemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ConsolePortTemplate.objects.count(), 3)
-        consoleporttemplate1 = ConsolePortTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleporttemplate1.name, data['name'])
-
-    def test_delete_consoleporttemplate(self):
-
-        url = reverse('dcim-api:consoleporttemplate-detail', kwargs={'pk': self.consoleporttemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ConsolePortTemplate.objects.count(), 2)
-
-
-class ConsoleServerPortTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.consoleserverporttemplate1 = ConsoleServerPortTemplate.objects.create(
-            device_type=self.devicetype, name='Test CSP Template 1'
-        )
-        self.consoleserverporttemplate2 = ConsoleServerPortTemplate.objects.create(
-            device_type=self.devicetype, name='Test CSP Template 2'
-        )
-        self.consoleserverporttemplate3 = ConsoleServerPortTemplate.objects.create(
-            device_type=self.devicetype, name='Test CSP Template 3'
-        )
-
-    def test_get_consoleserverporttemplate(self):
-
-        url = reverse('dcim-api:consoleserverporttemplate-detail', kwargs={'pk': self.consoleserverporttemplate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.consoleserverporttemplate1.name)
-
-    def test_list_consoleserverporttemplates(self):
-
-        url = reverse('dcim-api:consoleserverporttemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_consoleserverporttemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test CSP Template 4',
-        }
-
-        url = reverse('dcim-api:consoleserverporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsoleServerPortTemplate.objects.count(), 4)
-        consoleserverporttemplate4 = ConsoleServerPortTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleserverporttemplate4.device_type_id, data['device_type'])
-        self.assertEqual(consoleserverporttemplate4.name, data['name'])
-
-    def test_create_consoleserverporttemplate_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test CSP Template 4',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test CSP Template 5',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test CSP Template 6',
-            },
-        ]
-
-        url = reverse('dcim-api:consoleserverporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsoleServerPortTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_consoleserverporttemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test CSP Template X',
-        }
-
-        url = reverse('dcim-api:consoleserverporttemplate-detail', kwargs={'pk': self.consoleserverporttemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ConsoleServerPortTemplate.objects.count(), 3)
-        consoleserverporttemplate1 = ConsoleServerPortTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleserverporttemplate1.name, data['name'])
-
-    def test_delete_consoleserverporttemplate(self):
-
-        url = reverse('dcim-api:consoleserverporttemplate-detail', kwargs={'pk': self.consoleserverporttemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ConsoleServerPortTemplate.objects.count(), 2)
-
-
-class PowerPortTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.powerporttemplate1 = PowerPortTemplate.objects.create(
-            device_type=self.devicetype, name='Test PP Template 1'
-        )
-        self.powerporttemplate2 = PowerPortTemplate.objects.create(
-            device_type=self.devicetype, name='Test PP Template 2'
-        )
-        self.powerporttemplate3 = PowerPortTemplate.objects.create(
-            device_type=self.devicetype, name='Test PP Template 3'
-        )
-
-    def test_get_powerporttemplate(self):
-
-        url = reverse('dcim-api:powerporttemplate-detail', kwargs={'pk': self.powerporttemplate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.powerporttemplate1.name)
-
-    def test_list_powerporttemplates(self):
-
-        url = reverse('dcim-api:powerporttemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_powerporttemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test PP Template 4',
-        }
-
-        url = reverse('dcim-api:powerporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerPortTemplate.objects.count(), 4)
-        powerporttemplate4 = PowerPortTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(powerporttemplate4.device_type_id, data['device_type'])
-        self.assertEqual(powerporttemplate4.name, data['name'])
-
-    def test_create_powerporttemplate_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test PP Template 4',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test PP Template 5',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test PP Template 6',
-            },
-        ]
-
-        url = reverse('dcim-api:powerporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerPortTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_powerporttemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test PP Template X',
-        }
-
-        url = reverse('dcim-api:powerporttemplate-detail', kwargs={'pk': self.powerporttemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(PowerPortTemplate.objects.count(), 3)
-        powerporttemplate1 = PowerPortTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(powerporttemplate1.name, data['name'])
-
-    def test_delete_powerporttemplate(self):
-
-        url = reverse('dcim-api:powerporttemplate-detail', kwargs={'pk': self.powerporttemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(PowerPortTemplate.objects.count(), 2)
-
-
-class PowerOutletTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.poweroutlettemplate1 = PowerOutletTemplate.objects.create(
-            device_type=self.devicetype, name='Test PO Template 1'
-        )
-        self.poweroutlettemplate2 = PowerOutletTemplate.objects.create(
-            device_type=self.devicetype, name='Test PO Template 2'
-        )
-        self.poweroutlettemplate3 = PowerOutletTemplate.objects.create(
-            device_type=self.devicetype, name='Test PO Template 3'
-        )
-
-    def test_get_poweroutlettemplate(self):
-
-        url = reverse('dcim-api:poweroutlettemplate-detail', kwargs={'pk': self.poweroutlettemplate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.poweroutlettemplate1.name)
-
-    def test_list_poweroutlettemplates(self):
-
-        url = reverse('dcim-api:poweroutlettemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_poweroutlettemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test PO Template 4',
-        }
-
-        url = reverse('dcim-api:poweroutlettemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerOutletTemplate.objects.count(), 4)
-        poweroutlettemplate4 = PowerOutletTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(poweroutlettemplate4.device_type_id, data['device_type'])
-        self.assertEqual(poweroutlettemplate4.name, data['name'])
-
-    def test_create_poweroutlettemplate_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test PO Template 4',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test PO Template 5',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test PO Template 6',
-            },
-        ]
-
-        url = reverse('dcim-api:poweroutlettemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerOutletTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_poweroutlettemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test PO Template X',
-        }
-
-        url = reverse('dcim-api:poweroutlettemplate-detail', kwargs={'pk': self.poweroutlettemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(PowerOutletTemplate.objects.count(), 3)
-        poweroutlettemplate1 = PowerOutletTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(poweroutlettemplate1.name, data['name'])
-
-    def test_delete_poweroutlettemplate(self):
-
-        url = reverse('dcim-api:poweroutlettemplate-detail', kwargs={'pk': self.poweroutlettemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(PowerOutletTemplate.objects.count(), 2)
-
-
-class InterfaceTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.interfacetemplate1 = InterfaceTemplate.objects.create(
-            device_type=self.devicetype, name='Test Interface Template 1', type='1000base-t'
-        )
-        self.interfacetemplate2 = InterfaceTemplate.objects.create(
-            device_type=self.devicetype, name='Test Interface Template 2', type='1000base-t'
-        )
-        self.interfacetemplate3 = InterfaceTemplate.objects.create(
-            device_type=self.devicetype, name='Test Interface Template 3', type='1000base-t'
-        )
-
-    def test_get_interfacetemplate(self):
-
-        url = reverse('dcim-api:interfacetemplate-detail', kwargs={'pk': self.interfacetemplate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.interfacetemplate1.name)
-
-    def test_list_interfacetemplates(self):
-
-        url = reverse('dcim-api:interfacetemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_interfacetemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test Interface Template 4',
-            'type': '1000base-t',
-        }
-
-        url = reverse('dcim-api:interfacetemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(InterfaceTemplate.objects.count(), 4)
-        interfacetemplate4 = InterfaceTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(interfacetemplate4.device_type_id, data['device_type'])
-        self.assertEqual(interfacetemplate4.name, data['name'])
-
-    def test_create_interfacetemplate_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test Interface Template 4',
-                'type': '1000base-t',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test Interface Template 5',
-                'type': '1000base-t',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test Interface Template 6',
-                'type': '1000base-t',
-            },
-        ]
-
-        url = reverse('dcim-api:interfacetemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(InterfaceTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_interfacetemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test Interface Template X',
-            'type': '1000base-x-gbic',
-        }
-
-        url = reverse('dcim-api:interfacetemplate-detail', kwargs={'pk': self.interfacetemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(InterfaceTemplate.objects.count(), 3)
-        interfacetemplate1 = InterfaceTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(interfacetemplate1.name, data['name'])
-
-    def test_delete_interfacetemplate(self):
-
-        url = reverse('dcim-api:interfacetemplate-detail', kwargs={'pk': self.interfacetemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(InterfaceTemplate.objects.count(), 2)
-
-
-class DeviceBayTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.devicebaytemplate1 = DeviceBayTemplate.objects.create(
-            device_type=self.devicetype, name='Test Device Bay Template 1'
-        )
-        self.devicebaytemplate2 = DeviceBayTemplate.objects.create(
-            device_type=self.devicetype, name='Test Device Bay Template 2'
-        )
-        self.devicebaytemplate3 = DeviceBayTemplate.objects.create(
-            device_type=self.devicetype, name='Test Device Bay Template 3'
-        )
-
-    def test_get_devicebaytemplate(self):
-
-        url = reverse('dcim-api:devicebaytemplate-detail', kwargs={'pk': self.devicebaytemplate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.devicebaytemplate1.name)
-
-    def test_list_devicebaytemplates(self):
-
-        url = reverse('dcim-api:devicebaytemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_devicebaytemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test Device Bay Template 4',
-        }
-
-        url = reverse('dcim-api:devicebaytemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceBayTemplate.objects.count(), 4)
-        devicebaytemplate4 = DeviceBayTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(devicebaytemplate4.device_type_id, data['device_type'])
-        self.assertEqual(devicebaytemplate4.name, data['name'])
-
-    def test_create_devicebaytemplate_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test Device Bay Template 4',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test Device Bay Template 5',
-            },
-            {
-                'device_type': self.devicetype.pk,
-                'name': 'Test Device Bay Template 6',
-            },
-        ]
-
-        url = reverse('dcim-api:devicebaytemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceBayTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_devicebaytemplate(self):
-
-        data = {
-            'device_type': self.devicetype.pk,
-            'name': 'Test Device Bay Template X',
-        }
-
-        url = reverse('dcim-api:devicebaytemplate-detail', kwargs={'pk': self.devicebaytemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(DeviceBayTemplate.objects.count(), 3)
-        devicebaytemplate1 = DeviceBayTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(devicebaytemplate1.name, data['name'])
-
-    def test_delete_devicebaytemplate(self):
-
-        url = reverse('dcim-api:devicebaytemplate-detail', kwargs={'pk': self.devicebaytemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(DeviceBayTemplate.objects.count(), 2)
-
-
-class DeviceRoleTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.devicerole1 = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.devicerole2 = DeviceRole.objects.create(
-            name='Test Device Role 2', slug='test-device-role-2', color='00ff00'
-        )
-        self.devicerole3 = DeviceRole.objects.create(
-            name='Test Device Role 3', slug='test-device-role-3', color='0000ff'
-        )
-
-    def test_get_devicerole(self):
-
-        url = reverse('dcim-api:devicerole-detail', kwargs={'pk': self.devicerole1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.devicerole1.name)
-
-    def test_list_deviceroles(self):
-
-        url = reverse('dcim-api:devicerole-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_deviceroles_brief(self):
-
-        url = reverse('dcim-api:devicerole-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['device_count', 'id', 'name', 'slug', 'url', 'virtualmachine_count']
-        )
-
-    def test_create_devicerole(self):
-
-        data = {
-            'name': 'Test Device Role 4',
-            'slug': 'test-device-role-4',
-            'color': 'ffff00',
-        }
-
-        url = reverse('dcim-api:devicerole-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceRole.objects.count(), 4)
-        devicerole4 = DeviceRole.objects.get(pk=response.data['id'])
-        self.assertEqual(devicerole4.name, data['name'])
-        self.assertEqual(devicerole4.slug, data['slug'])
-        self.assertEqual(devicerole4.color, data['color'])
-
-    def test_create_devicerole_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Device Role 4',
-                'slug': 'test-device-role-4',
-                'color': 'ffff00',
-            },
-            {
-                'name': 'Test Device Role 5',
-                'slug': 'test-device-role-5',
-                'color': 'ffff00',
-            },
-            {
-                'name': 'Test Device Role 6',
-                'slug': 'test-device-role-6',
-                'color': 'ffff00',
-            },
-        ]
-
-        url = reverse('dcim-api:devicerole-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceRole.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_devicerole(self):
-
-        data = {
-            'name': 'Test Device Role X',
-            'slug': 'test-device-role-x',
-            'color': '00ffff',
-        }
-
-        url = reverse('dcim-api:devicerole-detail', kwargs={'pk': self.devicerole1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(DeviceRole.objects.count(), 3)
-        devicerole1 = DeviceRole.objects.get(pk=response.data['id'])
-        self.assertEqual(devicerole1.name, data['name'])
-        self.assertEqual(devicerole1.slug, data['slug'])
-        self.assertEqual(devicerole1.color, data['color'])
-
-    def test_delete_devicerole(self):
-
-        url = reverse('dcim-api:devicerole-detail', kwargs={'pk': self.devicerole1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(DeviceRole.objects.count(), 2)
-
-
-class PlatformTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.platform1 = Platform.objects.create(name='Test Platform 1', slug='test-platform-1')
-        self.platform2 = Platform.objects.create(name='Test Platform 2', slug='test-platform-2')
-        self.platform3 = Platform.objects.create(name='Test Platform 3', slug='test-platform-3')
-
-    def test_get_platform(self):
-
-        url = reverse('dcim-api:platform-detail', kwargs={'pk': self.platform1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.platform1.name)
-
-    def test_list_platforms(self):
-
-        url = reverse('dcim-api:platform-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_platforms_brief(self):
-
-        url = reverse('dcim-api:platform-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['device_count', 'id', 'name', 'slug', 'url', 'virtualmachine_count']
-        )
-
-    def test_create_platform(self):
-
-        data = {
-            'name': 'Test Platform 4',
-            'slug': 'test-platform-4',
-        }
-
-        url = reverse('dcim-api:platform-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Platform.objects.count(), 4)
-        platform4 = Platform.objects.get(pk=response.data['id'])
-        self.assertEqual(platform4.name, data['name'])
-        self.assertEqual(platform4.slug, data['slug'])
-
-    def test_create_platform_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Platform 4',
-                'slug': 'test-platform-4',
-            },
-            {
-                'name': 'Test Platform 5',
-                'slug': 'test-platform-5',
-            },
-            {
-                'name': 'Test Platform 6',
-                'slug': 'test-platform-6',
-            },
-        ]
-
-        url = reverse('dcim-api:platform-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Platform.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_platform(self):
-
-        data = {
-            'name': 'Test Platform X',
-            'slug': 'test-platform-x',
-        }
-
-        url = reverse('dcim-api:platform-detail', kwargs={'pk': self.platform1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Platform.objects.count(), 3)
-        platform1 = Platform.objects.get(pk=response.data['id'])
-        self.assertEqual(platform1.name, data['name'])
-        self.assertEqual(platform1.slug, data['slug'])
-
-    def test_delete_platform(self):
-
-        url = reverse('dcim-api:platform-detail', kwargs={'pk': self.platform1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Platform.objects.count(), 2)
-
-
-class DeviceTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
-        self.rack1 = Rack.objects.create(name='Test Rack 1', site=self.site1, u_height=48)
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype1 = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        self.devicetype2 = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 2', slug='test-device-type-2'
-        )
-        self.devicerole1 = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.devicerole2 = DeviceRole.objects.create(
-            name='Test Device Role 2', slug='test-device-role-2', color='00ff00'
-        )
-        cluster_type = ClusterType.objects.create(name='Test Cluster Type 1', slug='test-cluster-type-1')
-        self.cluster1 = Cluster.objects.create(name='Test Cluster 1', type=cluster_type)
-        self.device1 = Device.objects.create(
-            device_type=self.devicetype1,
-            device_role=self.devicerole1,
-            name='Test Device 1',
-            site=self.site1,
-            cluster=self.cluster1
-        )
-        self.device2 = Device.objects.create(
-            device_type=self.devicetype1,
-            device_role=self.devicerole1,
-            name='Test Device 2',
-            site=self.site1,
-            cluster=self.cluster1
-        )
-        self.device3 = Device.objects.create(
-            device_type=self.devicetype1,
-            device_role=self.devicerole1,
-            name='Test Device 3',
-            site=self.site1,
-            cluster=self.cluster1
-        )
-        self.device_with_context_data = Device.objects.create(
-            device_type=self.devicetype1,
-            device_role=self.devicerole1,
-            name='Device with context data',
-            site=self.site1,
-            local_context_data={
-                'A': 1,
-                'B': 2
-            }
-        )
-
-    def test_get_device(self):
-
-        url = reverse('dcim-api:device-detail', kwargs={'pk': self.device1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.device1.name)
-        self.assertEqual(response.data['device_role']['id'], self.devicerole1.pk)
-        self.assertEqual(response.data['cluster']['id'], self.cluster1.pk)
-
-    def test_get_device_graphs(self):
-
-        device_ct = ContentType.objects.get_for_model(Device)
-        self.graph1 = Graph.objects.create(
-            type=device_ct,
-            name='Test Graph 1',
-            source='http://example.com/graphs.py?device={{ obj.name }}&foo=1'
-        )
-        self.graph2 = Graph.objects.create(
-            type=device_ct,
-            name='Test Graph 2',
-            source='http://example.com/graphs.py?device={{ obj.name }}&foo=2'
-        )
-        self.graph3 = Graph.objects.create(
-            type=device_ct,
-            name='Test Graph 3',
-            source='http://example.com/graphs.py?device={{ obj.name }}&foo=3'
-        )
-
-        url = reverse('dcim-api:device-graphs', kwargs={'pk': self.device1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(len(response.data), 3)
-        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?device=Test Device 1&foo=1')
-
-    def test_list_devices(self):
-
-        url = reverse('dcim-api:device-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 4)
-
-    def test_list_devices_brief(self):
-
-        url = reverse('dcim-api:device-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['display_name', 'id', 'name', 'url']
-        )
-
-    def test_create_device(self):
-
-        data = {
-            'device_type': self.devicetype1.pk,
-            'device_role': self.devicerole1.pk,
-            'name': 'Test Device 4',
-            'site': self.site1.pk,
-            'rack': self.rack1.pk,
-            'face': DeviceFaceChoices.FACE_FRONT,
-            'position': 1,
-            'cluster': self.cluster1.pk,
-        }
-
-        url = reverse('dcim-api:device-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Device.objects.count(), 5)
-        device4 = Device.objects.get(pk=response.data['id'])
-        self.assertEqual(device4.device_type_id, data['device_type'])
-        self.assertEqual(device4.device_role_id, data['device_role'])
-        self.assertEqual(device4.name, data['name'])
-        self.assertEqual(device4.site.pk, data['site'])
-        self.assertEqual(device4.cluster.pk, data['cluster'])
-
-    def test_create_device_bulk(self):
-
-        data = [
-            {
-                'device_type': self.devicetype1.pk,
-                'device_role': self.devicerole1.pk,
-                'name': 'Test Device 4',
-                'site': self.site1.pk,
-            },
-            {
-                'device_type': self.devicetype1.pk,
-                'device_role': self.devicerole1.pk,
-                'name': 'Test Device 5',
-                'site': self.site1.pk,
-            },
-            {
-                'device_type': self.devicetype1.pk,
-                'device_role': self.devicerole1.pk,
-                'name': 'Test Device 6',
-                'site': self.site1.pk,
-            },
-        ]
-
-        url = reverse('dcim-api:device-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Device.objects.count(), 7)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_device(self):
-
-        interface = Interface.objects.create(name='Test Interface 1', device=self.device1)
-        ip4_address = IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), interface=interface)
-        ip6_address = IPAddress.objects.create(address=IPNetwork('2001:db8::1/64'), interface=interface)
-
-        data = {
-            'device_type': self.devicetype2.pk,
-            'device_role': self.devicerole2.pk,
-            'name': 'Test Device X',
-            'site': self.site2.pk,
-            'primary_ip4': ip4_address.pk,
-            'primary_ip6': ip6_address.pk,
-        }
-
-        url = reverse('dcim-api:device-detail', kwargs={'pk': self.device1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Device.objects.count(), 4)
-        device1 = Device.objects.get(pk=response.data['id'])
-        self.assertEqual(device1.device_type_id, data['device_type'])
-        self.assertEqual(device1.device_role_id, data['device_role'])
-        self.assertEqual(device1.name, data['name'])
-        self.assertEqual(device1.site.pk, data['site'])
-        self.assertEqual(device1.primary_ip4.pk, data['primary_ip4'])
-        self.assertEqual(device1.primary_ip6.pk, data['primary_ip6'])
-
-    def test_delete_device(self):
-
-        url = reverse('dcim-api:device-detail', kwargs={'pk': self.device1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Device.objects.count(), 3)
-
-    def test_config_context_included_by_default_in_list_view(self):
-
-        url = reverse('dcim-api:device-list') + '?slug=device-with-context-data'
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['results'][0].get('config_context', {}).get('A'), 1)
-
-    def test_config_context_excluded(self):
-
-        url = reverse('dcim-api:device-list') + '?exclude=config_context'
-        response = self.client.get(url, **self.header)
-
-        self.assertFalse('config_context' in response.data['results'][0])
-
-    def test_unique_name_per_site_constraint(self):
-
-        data = {
-            'device_type': self.devicetype1.pk,
-            'device_role': self.devicerole1.pk,
-            'name': 'Test Device 1',
-            'site': self.site1.pk,
-        }
-
-        url = reverse('dcim-api:device-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
-
-
-class ConsolePortTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
-        )
-        self.consoleport1 = ConsolePort.objects.create(device=self.device, name='Test Console Port 1')
-        self.consoleport2 = ConsolePort.objects.create(device=self.device, name='Test Console Port 2')
-        self.consoleport3 = ConsolePort.objects.create(device=self.device, name='Test Console Port 3')
-
-    def test_get_consoleport(self):
-
-        url = reverse('dcim-api:consoleport-detail', kwargs={'pk': self.consoleport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.consoleport1.name)
-
-    def test_list_consoleports(self):
-
-        url = reverse('dcim-api:consoleport-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_consoleports_brief(self):
-
-        url = reverse('dcim-api:consoleport-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'connection_status', 'device', 'id', 'name', 'url']
-        )
-
-    def test_create_consoleport(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Console Port 4',
-        }
-
-        url = reverse('dcim-api:consoleport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsolePort.objects.count(), 4)
-        consoleport4 = ConsolePort.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleport4.device_id, data['device'])
-        self.assertEqual(consoleport4.name, data['name'])
-
-    def test_create_consoleport_bulk(self):
-
-        data = [
-            {
-                'device': self.device.pk,
-                'name': 'Test Console Port 4',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Console Port 5',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Console Port 6',
-            },
-        ]
-
-        url = reverse('dcim-api:consoleport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsolePort.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_consoleport(self):
-
-        consoleserverport = ConsoleServerPort.objects.create(device=self.device, name='Test CS Port 1')
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Console Port X',
-        }
-
-        url = reverse('dcim-api:consoleport-detail', kwargs={'pk': self.consoleport1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ConsolePort.objects.count(), 3)
-        consoleport1 = ConsolePort.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleport1.name, data['name'])
-
-    def test_delete_consoleport(self):
-
-        url = reverse('dcim-api:consoleport-detail', kwargs={'pk': self.consoleport1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ConsolePort.objects.count(), 2)
-
-    def test_trace_consoleport(self):
-
-        peer_device = Device.objects.create(
-            site=Site.objects.first(),
-            device_type=DeviceType.objects.first(),
-            device_role=DeviceRole.objects.first(),
-            name='Peer Device'
-        )
-        console_server_port = ConsoleServerPort.objects.create(
-            device=peer_device,
-            name='Console Server Port 1'
-        )
-        cable = Cable(termination_a=self.consoleport1, termination_b=console_server_port, label='Cable 1')
-        cable.save()
-
-        url = reverse('dcim-api:consoleport-trace', kwargs={'pk': self.consoleport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(len(response.data), 1)
-        segment1 = response.data[0]
-        self.assertEqual(segment1[0]['name'], self.consoleport1.name)
-        self.assertEqual(segment1[1]['label'], cable.label)
-        self.assertEqual(segment1[2]['name'], console_server_port.name)
-
-
-class ConsoleServerPortTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
-        )
-        self.consoleserverport1 = ConsoleServerPort.objects.create(device=self.device, name='Test CS Port 1')
-        self.consoleserverport2 = ConsoleServerPort.objects.create(device=self.device, name='Test CS Port 2')
-        self.consoleserverport3 = ConsoleServerPort.objects.create(device=self.device, name='Test CS Port 3')
-
-    def test_get_consoleserverport(self):
-
-        url = reverse('dcim-api:consoleserverport-detail', kwargs={'pk': self.consoleserverport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.consoleserverport1.name)
-
-    def test_list_consoleserverports(self):
-
-        url = reverse('dcim-api:consoleserverport-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_consoleserverports_brief(self):
-
-        url = reverse('dcim-api:consoleserverport-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'connection_status', 'device', 'id', 'name', 'url']
-        )
-
-    def test_create_consoleserverport(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test CS Port 4',
-        }
-
-        url = reverse('dcim-api:consoleserverport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsoleServerPort.objects.count(), 4)
-        consoleserverport4 = ConsoleServerPort.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleserverport4.device_id, data['device'])
-        self.assertEqual(consoleserverport4.name, data['name'])
-
-    def test_create_consoleserverport_bulk(self):
-
-        data = [
-            {
-                'device': self.device.pk,
-                'name': 'Test CS Port 4',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test CS Port 5',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test CS Port 6',
-            },
-        ]
-
-        url = reverse('dcim-api:consoleserverport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConsoleServerPort.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_consoleserverport(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test CS Port X',
-        }
-
-        url = reverse('dcim-api:consoleserverport-detail', kwargs={'pk': self.consoleserverport1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ConsoleServerPort.objects.count(), 3)
-        consoleserverport1 = ConsoleServerPort.objects.get(pk=response.data['id'])
-        self.assertEqual(consoleserverport1.name, data['name'])
-
-    def test_delete_consoleserverport(self):
-
-        url = reverse('dcim-api:consoleserverport-detail', kwargs={'pk': self.consoleserverport1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ConsoleServerPort.objects.count(), 2)
-
-    def test_trace_consoleserverport(self):
-
-        peer_device = Device.objects.create(
-            site=Site.objects.first(),
-            device_type=DeviceType.objects.first(),
-            device_role=DeviceRole.objects.first(),
-            name='Peer Device'
-        )
-        console_port = ConsolePort.objects.create(
-            device=peer_device,
-            name='Console Port 1'
-        )
-        cable = Cable(termination_a=self.consoleserverport1, termination_b=console_port, label='Cable 1')
-        cable.save()
-
-        url = reverse('dcim-api:consoleserverport-trace', kwargs={'pk': self.consoleserverport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(len(response.data), 1)
-        segment1 = response.data[0]
-        self.assertEqual(segment1[0]['name'], self.consoleserverport1.name)
-        self.assertEqual(segment1[1]['label'], cable.label)
-        self.assertEqual(segment1[2]['name'], console_port.name)
-
-
-class PowerPortTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
-        )
-        self.powerport1 = PowerPort.objects.create(device=self.device, name='Test Power Port 1')
-        self.powerport2 = PowerPort.objects.create(device=self.device, name='Test Power Port 2')
-        self.powerport3 = PowerPort.objects.create(device=self.device, name='Test Power Port 3')
-
-    def test_get_powerport(self):
-
-        url = reverse('dcim-api:powerport-detail', kwargs={'pk': self.powerport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.powerport1.name)
-
-    def test_list_powerports(self):
-
-        url = reverse('dcim-api:powerport-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_powerports_brief(self):
-
-        url = reverse('dcim-api:powerport-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'connection_status', 'device', 'id', 'name', 'url']
-        )
-
-    def test_create_powerport(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Power Port 4',
-        }
-
-        url = reverse('dcim-api:powerport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerPort.objects.count(), 4)
-        powerport4 = PowerPort.objects.get(pk=response.data['id'])
-        self.assertEqual(powerport4.device_id, data['device'])
-        self.assertEqual(powerport4.name, data['name'])
-
-    def test_create_powerport_bulk(self):
-
-        data = [
-            {
-                'device': self.device.pk,
-                'name': 'Test Power Port 4',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Power Port 5',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Power Port 6',
-            },
-        ]
-
-        url = reverse('dcim-api:powerport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerPort.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_powerport(self):
-
-        poweroutlet = PowerOutlet.objects.create(device=self.device, name='Test Power Outlet 1')
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Power Port X',
-        }
-
-        url = reverse('dcim-api:powerport-detail', kwargs={'pk': self.powerport1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(PowerPort.objects.count(), 3)
-        powerport1 = PowerPort.objects.get(pk=response.data['id'])
-        self.assertEqual(powerport1.name, data['name'])
-
-    def test_delete_powerport(self):
-
-        url = reverse('dcim-api:powerport-detail', kwargs={'pk': self.powerport1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(PowerPort.objects.count(), 2)
-
-    def test_trace_powerport(self):
-
-        peer_device = Device.objects.create(
-            site=Site.objects.first(),
-            device_type=DeviceType.objects.first(),
-            device_role=DeviceRole.objects.first(),
-            name='Peer Device'
-        )
-        power_outlet = PowerOutlet.objects.create(
-            device=peer_device,
-            name='Power Outlet 1'
-        )
-        cable = Cable(termination_a=self.powerport1, termination_b=power_outlet, label='Cable 1')
-        cable.save()
-
-        url = reverse('dcim-api:powerport-trace', kwargs={'pk': self.powerport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(len(response.data), 1)
-        segment1 = response.data[0]
-        self.assertEqual(segment1[0]['name'], self.powerport1.name)
-        self.assertEqual(segment1[1]['label'], cable.label)
-        self.assertEqual(segment1[2]['name'], power_outlet.name)
-
-
-class PowerOutletTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
-        )
-        self.poweroutlet1 = PowerOutlet.objects.create(device=self.device, name='Test Power Outlet 1')
-        self.poweroutlet2 = PowerOutlet.objects.create(device=self.device, name='Test Power Outlet 2')
-        self.poweroutlet3 = PowerOutlet.objects.create(device=self.device, name='Test Power Outlet 3')
-
-    def test_get_poweroutlet(self):
-
-        url = reverse('dcim-api:poweroutlet-detail', kwargs={'pk': self.poweroutlet1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.poweroutlet1.name)
-
-    def test_list_poweroutlets(self):
-
-        url = reverse('dcim-api:poweroutlet-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_poweroutlets_brief(self):
-
-        url = reverse('dcim-api:poweroutlet-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'connection_status', 'device', 'id', 'name', 'url']
-        )
-
-    def test_create_poweroutlet(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Power Outlet 4',
-        }
-
-        url = reverse('dcim-api:poweroutlet-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerOutlet.objects.count(), 4)
-        poweroutlet4 = PowerOutlet.objects.get(pk=response.data['id'])
-        self.assertEqual(poweroutlet4.device_id, data['device'])
-        self.assertEqual(poweroutlet4.name, data['name'])
-
-    def test_create_poweroutlet_bulk(self):
-
-        data = [
-            {
-                'device': self.device.pk,
-                'name': 'Test Power Outlet 4',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Power Outlet 5',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Power Outlet 6',
-            },
-        ]
-
-        url = reverse('dcim-api:poweroutlet-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerOutlet.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_poweroutlet(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Power Outlet X',
-        }
-
-        url = reverse('dcim-api:poweroutlet-detail', kwargs={'pk': self.poweroutlet1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(PowerOutlet.objects.count(), 3)
-        poweroutlet1 = PowerOutlet.objects.get(pk=response.data['id'])
-        self.assertEqual(poweroutlet1.name, data['name'])
-
-    def test_delete_poweroutlet(self):
-
-        url = reverse('dcim-api:poweroutlet-detail', kwargs={'pk': self.poweroutlet1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(PowerOutlet.objects.count(), 2)
-
-    def test_trace_poweroutlet(self):
-
-        peer_device = Device.objects.create(
-            site=Site.objects.first(),
-            device_type=DeviceType.objects.first(),
-            device_role=DeviceRole.objects.first(),
-            name='Peer Device'
-        )
-        power_port = PowerPort.objects.create(
-            device=peer_device,
-            name='Power Port 1'
-        )
-        cable = Cable(termination_a=self.poweroutlet1, termination_b=power_port, label='Cable 1')
-        cable.save()
-
-        url = reverse('dcim-api:poweroutlet-trace', kwargs={'pk': self.poweroutlet1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(len(response.data), 1)
-        segment1 = response.data[0]
-        self.assertEqual(segment1[0]['name'], self.poweroutlet1.name)
-        self.assertEqual(segment1[1]['label'], cable.label)
-        self.assertEqual(segment1[2]['name'], power_port.name)
-
-
-class InterfaceTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
-        )
-        self.interface1 = Interface.objects.create(device=self.device, name='Test Interface 1', type='1000base-t')
-        self.interface2 = Interface.objects.create(device=self.device, name='Test Interface 2', type='1000base-t')
-        self.interface3 = Interface.objects.create(device=self.device, name='Test Interface 3', type='1000base-t')
-
-        self.vlan1 = VLAN.objects.create(name="Test VLAN 1", vid=1)
-        self.vlan2 = VLAN.objects.create(name="Test VLAN 2", vid=2)
-        self.vlan3 = VLAN.objects.create(name="Test VLAN 3", vid=3)
-
-    def test_get_interface(self):
-
-        url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(response.data['name'], self.interface1.name)
-
-    def test_get_interface_graphs(self):
-
-        interface_ct = ContentType.objects.get_for_model(Interface)
-        self.graph1 = Graph.objects.create(
-            type=interface_ct,
-            name='Test Graph 1',
-            source='http://example.com/graphs.py?interface={{ obj.name }}&foo=1'
-        )
-        self.graph2 = Graph.objects.create(
-            type=interface_ct,
-            name='Test Graph 2',
-            source='http://example.com/graphs.py?interface={{ obj.name }}&foo=2'
-        )
-        self.graph3 = Graph.objects.create(
-            type=interface_ct,
-            name='Test Graph 3',
-            source='http://example.com/graphs.py?interface={{ obj.name }}&foo=3'
-        )
-
-        url = reverse('dcim-api:interface-graphs', kwargs={'pk': self.interface1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(len(response.data), 3)
-        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?interface=Test Interface 1&foo=1')
-
-    def test_list_interfaces(self):
-
-        url = reverse('dcim-api:interface-list')
-        response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['count'], 3)
+class ConsoleServerPortTest(APIViewTestCases.APIViewTestCase):
+    model = ConsoleServerPort
+    brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
 
-    def test_list_interfaces_brief(self):
-
-        url = reverse('dcim-api:interface-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'connection_status', 'device', 'id', 'name', 'url']
+        console_server_ports = (
+            ConsoleServerPort(device=device, name='Console Server Port 1'),
+            ConsoleServerPort(device=device, name='Console Server Port 2'),
+            ConsoleServerPort(device=device, name='Console Server Port 3'),
         )
+        ConsoleServerPort.objects.bulk_create(console_server_ports)
 
-    def test_create_interface(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Interface 4',
-            'type': '1000base-t',
-        }
-
-        url = reverse('dcim-api:interface-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Interface.objects.count(), 4)
-        interface4 = Interface.objects.get(pk=response.data['id'])
-        self.assertEqual(interface4.device_id, data['device'])
-        self.assertEqual(interface4.name, data['name'])
-
-    def test_create_interface_with_802_1q(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Interface 4',
-            'type': '1000base-t',
-            'mode': InterfaceModeChoices.MODE_TAGGED,
-            'untagged_vlan': self.vlan3.id,
-            'tagged_vlans': [self.vlan1.id, self.vlan2.id],
-        }
-
-        url = reverse('dcim-api:interface-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Interface.objects.count(), 4)
-        self.assertEqual(response.data['device']['id'], data['device'])
-        self.assertEqual(response.data['name'], data['name'])
-        self.assertEqual(response.data['untagged_vlan']['id'], data['untagged_vlan'])
-        self.assertEqual([v['id'] for v in response.data['tagged_vlans']], data['tagged_vlans'])
-
-    def test_create_interface_bulk(self):
-
-        data = [
-            {
-                'device': self.device.pk,
-                'name': 'Test Interface 4',
-                'type': '1000base-t',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Interface 5',
-                'type': '1000base-t',
-            },
-            {
-                'device': self.device.pk,
-                'name': 'Test Interface 6',
-                'type': '1000base-t',
-            },
-        ]
-
-        url = reverse('dcim-api:interface-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Interface.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_create_interface_802_1q_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
-                'device': self.device.pk,
-                'name': 'Test Interface 4',
-                'type': '1000base-t',
-                'mode': InterfaceModeChoices.MODE_TAGGED,
-                'untagged_vlan': self.vlan2.id,
-                'tagged_vlans': [self.vlan1.id],
+                'device': device.pk,
+                'name': 'Console Server Port 4',
             },
             {
-                'device': self.device.pk,
-                'name': 'Test Interface 5',
-                'type': '1000base-t',
-                'mode': InterfaceModeChoices.MODE_TAGGED,
-                'untagged_vlan': self.vlan2.id,
-                'tagged_vlans': [self.vlan1.id],
+                'device': device.pk,
+                'name': 'Console Server Port 5',
             },
             {
-                'device': self.device.pk,
-                'name': 'Test Interface 6',
-                'type': '1000base-t',
-                'mode': InterfaceModeChoices.MODE_TAGGED,
-                'untagged_vlan': self.vlan2.id,
-                'tagged_vlans': [self.vlan1.id],
+                'device': device.pk,
+                'name': 'Console Server Port 6',
             },
         ]
 
-        url = reverse('dcim-api:interface-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Interface.objects.count(), 6)
-        for i in range(0, 3):
-            self.assertEqual(response.data[i]['name'], data[i]['name'])
-            self.assertEqual([v['id'] for v in response.data[i]['tagged_vlans']], data[i]['tagged_vlans'])
-            self.assertEqual(response.data[i]['untagged_vlan']['id'], data[i]['untagged_vlan'])
-
-    def test_update_interface(self):
-
-        lag_interface = Interface.objects.create(
-            device=self.device, name='Test LAG Interface', type=InterfaceTypeChoices.TYPE_LAG
+    def test_trace_consoleserverport(self):
+        """
+        Test tracing a ConsoleServerPort cable.
+        """
+        consoleserverport = ConsoleServerPort.objects.first()
+        peer_device = Device.objects.create(
+            site=Site.objects.first(),
+            device_type=DeviceType.objects.first(),
+            device_role=DeviceRole.objects.first(),
+            name='Peer Device'
         )
+        consoleport = ConsolePort.objects.create(
+            device=peer_device,
+            name='Console Port 1'
+        )
+        cable = Cable(termination_a=consoleserverport, termination_b=consoleport, label='Cable 1')
+        cable.save()
 
-        data = {
-            'device': self.device.pk,
-            'name': 'Test Interface X',
-            'type': '1000base-x-gbic',
-            'lag': lag_interface.pk,
-        }
-
-        url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
+        url = reverse('dcim-api:consoleserverport-trace', kwargs={'pk': consoleserverport.pk})
+        response = self.client.get(url, **self.header)
 
         self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Interface.objects.count(), 4)
-        interface1 = Interface.objects.get(pk=response.data['id'])
-        self.assertEqual(interface1.name, data['name'])
-        self.assertEqual(interface1.lag_id, data['lag'])
-
-    def test_delete_interface(self):
-
-        url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Interface.objects.count(), 2)
-
-
-class FrontPortTest(APITestCase):
+        self.assertEqual(len(response.data), 1)
+        segment1 = response.data[0]
+        self.assertEqual(segment1[0]['name'], consoleserverport.name)
+        self.assertEqual(segment1[1]['label'], cable.label)
+        self.assertEqual(segment1[2]['name'], consoleport.name)
 
-    def setUp(self):
 
-        super().setUp()
+class PowerPortTest(APIViewTestCases.APIViewTestCase):
+    model = PowerPort
+    brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
 
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
+    @classmethod
+    def setUpTestData(cls):
         manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
-        )
-        rear_ports = RearPort.objects.bulk_create((
-            RearPort(device=self.device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
-            RearPort(device=self.device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
-            RearPort(device=self.device, name='Rear Port 3', type=PortTypeChoices.TYPE_8P8C),
-            RearPort(device=self.device, name='Rear Port 4', type=PortTypeChoices.TYPE_8P8C),
-            RearPort(device=self.device, name='Rear Port 5', type=PortTypeChoices.TYPE_8P8C),
-            RearPort(device=self.device, name='Rear Port 6', type=PortTypeChoices.TYPE_8P8C),
-        ))
-        self.frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0])
-        self.frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1])
-        self.frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[2])
-
-    def test_get_frontport(self):
-
-        url = reverse('dcim-api:frontport-detail', kwargs={'pk': self.frontport1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.frontport1.name)
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-    def test_list_frontports(self):
-
-        url = reverse('dcim-api:frontport-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_frontports_brief(self):
-
-        url = reverse('dcim-api:frontport-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'device', 'id', 'name', 'url']
+        power_ports = (
+            PowerPort(device=device, name='Power Port 1'),
+            PowerPort(device=device, name='Power Port 2'),
+            PowerPort(device=device, name='Power Port 3'),
         )
+        PowerPort.objects.bulk_create(power_ports)
 
-    def test_create_frontport(self):
-
-        rear_port = RearPort.objects.get(name='Rear Port 4')
-        data = {
-            'device': self.device.pk,
-            'name': 'Front Port 4',
-            'type': PortTypeChoices.TYPE_8P8C,
-            'rear_port': rear_port.pk,
-            'rear_port_position': 1,
-        }
-
-        url = reverse('dcim-api:frontport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(FrontPort.objects.count(), 4)
-        frontport4 = FrontPort.objects.get(pk=response.data['id'])
-        self.assertEqual(frontport4.device_id, data['device'])
-        self.assertEqual(frontport4.name, data['name'])
-
-    def test_create_frontport_bulk(self):
-
-        rear_ports = RearPort.objects.filter(frontports__isnull=True)
-        data = [
+        cls.create_data = [
             {
-                'device': self.device.pk,
-                'name': 'Front Port 4',
-                'type': PortTypeChoices.TYPE_8P8C,
-                'rear_port': rear_ports[0].pk,
-                'rear_port_position': 1,
+                'device': device.pk,
+                'name': 'Power Port 4',
             },
             {
-                'device': self.device.pk,
-                'name': 'Front Port 5',
-                'type': PortTypeChoices.TYPE_8P8C,
-                'rear_port': rear_ports[1].pk,
-                'rear_port_position': 1,
+                'device': device.pk,
+                'name': 'Power Port 5',
             },
             {
-                'device': self.device.pk,
-                'name': 'Front Port 6',
-                'type': PortTypeChoices.TYPE_8P8C,
-                'rear_port': rear_ports[2].pk,
-                'rear_port_position': 1,
+                'device': device.pk,
+                'name': 'Power Port 6',
             },
         ]
 
-        url = reverse('dcim-api:frontport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(FrontPort.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_frontport(self):
-
-        rear_port = RearPort.objects.get(name='Rear Port 4')
-        data = {
-            'device': self.device.pk,
-            'name': 'Front Port X',
-            'type': PortTypeChoices.TYPE_110_PUNCH,
-            'rear_port': rear_port.pk,
-            'rear_port_position': 1,
-        }
-
-        url = reverse('dcim-api:frontport-detail', kwargs={'pk': self.frontport1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(FrontPort.objects.count(), 3)
-        frontport1 = FrontPort.objects.get(pk=response.data['id'])
-        self.assertEqual(frontport1.name, data['name'])
-        self.assertEqual(frontport1.type, data['type'])
-        self.assertEqual(frontport1.rear_port, rear_port)
-
-    def test_delete_frontport(self):
-
-        url = reverse('dcim-api:frontport-detail', kwargs={'pk': self.frontport1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(FrontPort.objects.count(), 2)
-
-
-class RearPortTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
+    def test_trace_powerport(self):
+        """
+        Test tracing a PowerPort cable.
+        """
+        powerport = PowerPort.objects.first()
+        peer_device = Device.objects.create(
+            site=Site.objects.first(),
+            device_type=DeviceType.objects.first(),
+            device_role=DeviceRole.objects.first(),
+            name='Peer Device'
         )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
+        poweroutlet = PowerOutlet.objects.create(
+            device=peer_device,
+            name='Power Outlet 1'
         )
-        self.rearport1 = RearPort.objects.create(device=self.device, type=PortTypeChoices.TYPE_8P8C, name='Rear Port 1')
-        self.rearport3 = RearPort.objects.create(device=self.device, type=PortTypeChoices.TYPE_8P8C, name='Rear Port 2')
-        self.rearport1 = RearPort.objects.create(device=self.device, type=PortTypeChoices.TYPE_8P8C, name='Rear Port 3')
-
-    def test_get_rearport(self):
+        cable = Cable(termination_a=powerport, termination_b=poweroutlet, label='Cable 1')
+        cable.save()
 
-        url = reverse('dcim-api:rearport-detail', kwargs={'pk': self.rearport1.pk})
+        url = reverse('dcim-api:powerport-trace', kwargs={'pk': powerport.pk})
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['name'], self.rearport1.name)
-
-    def test_list_rearports(self):
-
-        url = reverse('dcim-api:rearport-list')
-        response = self.client.get(url, **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(len(response.data), 1)
+        segment1 = response.data[0]
+        self.assertEqual(segment1[0]['name'], powerport.name)
+        self.assertEqual(segment1[1]['label'], cable.label)
+        self.assertEqual(segment1[2]['name'], poweroutlet.name)
 
-        self.assertEqual(response.data['count'], 3)
 
-    def test_list_rearports_brief(self):
+class PowerOutletTest(APIViewTestCases.APIViewTestCase):
+    model = PowerOutlet
+    brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
 
-        url = reverse('dcim-api:rearport-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cable', 'device', 'id', 'name', 'url']
+        power_outlets = (
+            PowerOutlet(device=device, name='Power Outlet 1'),
+            PowerOutlet(device=device, name='Power Outlet 2'),
+            PowerOutlet(device=device, name='Power Outlet 3'),
         )
+        PowerOutlet.objects.bulk_create(power_outlets)
 
-    def test_create_rearport(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Front Port 4',
-            'type': PortTypeChoices.TYPE_8P8C,
-        }
-
-        url = reverse('dcim-api:rearport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RearPort.objects.count(), 4)
-        rearport4 = RearPort.objects.get(pk=response.data['id'])
-        self.assertEqual(rearport4.device_id, data['device'])
-        self.assertEqual(rearport4.name, data['name'])
-
-    def test_create_rearport_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
-                'device': self.device.pk,
-                'name': 'Rear Port 4',
-                'type': PortTypeChoices.TYPE_8P8C,
+                'device': device.pk,
+                'name': 'Power Outlet 4',
             },
             {
-                'device': self.device.pk,
-                'name': 'Rear Port 5',
-                'type': PortTypeChoices.TYPE_8P8C,
+                'device': device.pk,
+                'name': 'Power Outlet 5',
             },
             {
-                'device': self.device.pk,
-                'name': 'Rear Port 6',
-                'type': PortTypeChoices.TYPE_8P8C,
+                'device': device.pk,
+                'name': 'Power Outlet 6',
             },
         ]
 
-        url = reverse('dcim-api:rearport-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RearPort.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_rearport(self):
-
-        data = {
-            'device': self.device.pk,
-            'name': 'Front Port X',
-            'type': PortTypeChoices.TYPE_110_PUNCH
-        }
-
-        url = reverse('dcim-api:rearport-detail', kwargs={'pk': self.rearport1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(RearPort.objects.count(), 3)
-        rearport1 = RearPort.objects.get(pk=response.data['id'])
-        self.assertEqual(rearport1.name, data['name'])
-        self.assertEqual(rearport1.type, data['type'])
-
-    def test_delete_rearport(self):
-
-        url = reverse('dcim-api:rearport-detail', kwargs={'pk': self.rearport1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(RearPort.objects.count(), 2)
-
-
-class DeviceBayTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        self.devicetype1 = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Parent Device Type', slug='parent-device-type',
-            subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
-        )
-        self.devicetype2 = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Child Device Type', slug='child-device-type',
-            subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.parent_device = Device.objects.create(
-            device_type=self.devicetype1, device_role=devicerole, name='Parent Device 1', site=site
+    def test_trace_poweroutlet(self):
+        """
+        Test tracing a PowerOutlet cable.
+        """
+        poweroutlet = PowerOutlet.objects.first()
+        peer_device = Device.objects.create(
+            site=Site.objects.first(),
+            device_type=DeviceType.objects.first(),
+            device_role=DeviceRole.objects.first(),
+            name='Peer Device'
         )
-        self.child_device = Device.objects.create(
-            device_type=self.devicetype2, device_role=devicerole, name='Child Device 1', site=site
+        powerport = PowerPort.objects.create(
+            device=peer_device,
+            name='Power Port 1'
         )
-        self.devicebay1 = DeviceBay.objects.create(device=self.parent_device, name='Test Device Bay 1')
-        self.devicebay2 = DeviceBay.objects.create(device=self.parent_device, name='Test Device Bay 2')
-        self.devicebay3 = DeviceBay.objects.create(device=self.parent_device, name='Test Device Bay 3')
-
-    def test_get_devicebay(self):
-
-        url = reverse('dcim-api:devicebay-detail', kwargs={'pk': self.devicebay1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.devicebay1.name)
-
-    def test_list_devicebays(self):
+        cable = Cable(termination_a=poweroutlet, termination_b=powerport, label='Cable 1')
+        cable.save()
 
-        url = reverse('dcim-api:devicebay-list')
+        url = reverse('dcim-api:poweroutlet-trace', kwargs={'pk': poweroutlet.pk})
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_devicebays_brief(self):
-
-        url = reverse('dcim-api:devicebay-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['device', 'id', 'name', 'url']
-        )
-
-    def test_create_devicebay(self):
-
-        data = {
-            'device': self.parent_device.pk,
-            'name': 'Test Device Bay 4',
-            'installed_device': self.child_device.pk,
-        }
-
-        url = reverse('dcim-api:devicebay-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceBay.objects.count(), 4)
-        devicebay4 = DeviceBay.objects.get(pk=response.data['id'])
-        self.assertEqual(devicebay4.device_id, data['device'])
-        self.assertEqual(devicebay4.name, data['name'])
-        self.assertEqual(devicebay4.installed_device_id, data['installed_device'])
-
-    def test_create_devicebay_bulk(self):
-
-        data = [
-            {
-                'device': self.parent_device.pk,
-                'name': 'Test Device Bay 4',
-            },
-            {
-                'device': self.parent_device.pk,
-                'name': 'Test Device Bay 5',
-            },
-            {
-                'device': self.parent_device.pk,
-                'name': 'Test Device Bay 6',
-            },
-        ]
-
-        url = reverse('dcim-api:devicebay-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(DeviceBay.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_devicebay(self):
-
-        data = {
-            'device': self.parent_device.pk,
-            'name': 'Test Device Bay X',
-            'installed_device': self.child_device.pk,
-        }
-
-        url = reverse('dcim-api:devicebay-detail', kwargs={'pk': self.devicebay1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
         self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(DeviceBay.objects.count(), 3)
-        devicebay1 = DeviceBay.objects.get(pk=response.data['id'])
-        self.assertEqual(devicebay1.name, data['name'])
-        self.assertEqual(devicebay1.installed_device_id, data['installed_device'])
-
-    def test_delete_devicebay(self):
-
-        url = reverse('dcim-api:devicebay-detail', kwargs={'pk': self.devicebay1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(DeviceBay.objects.count(), 2)
-
+        self.assertEqual(len(response.data), 1)
+        segment1 = response.data[0]
+        self.assertEqual(segment1[0]['name'], poweroutlet.name)
+        self.assertEqual(segment1[1]['label'], cable.label)
+        self.assertEqual(segment1[2]['name'], powerport.name)
 
-class InventoryItemTest(APITestCase):
 
-    def setUp(self):
+class InterfaceTest(APIViewTestCases.APIViewTestCase):
+    model = Interface
+    brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
 
-        super().setUp()
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
+        interfaces = (
+            Interface(device=device, name='Interface 1', type='1000base-t'),
+            Interface(device=device, name='Interface 2', type='1000base-t'),
+            Interface(device=device, name='Interface 3', type='1000base-t'),
         )
-        self.inventoryitem1 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 1')
-        self.inventoryitem2 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 2')
-        self.inventoryitem3 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 3')
+        Interface.objects.bulk_create(interfaces)
 
-    def test_get_inventoryitem(self):
-
-        url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
-        response = self.client.get(url, **self.header)
+        vlans = (
+            VLAN(name='VLAN 1', vid=1),
+            VLAN(name='VLAN 2', vid=2),
+            VLAN(name='VLAN 3', vid=3),
+        )
+        VLAN.objects.bulk_create(vlans)
 
-        self.assertEqual(response.data['name'], self.inventoryitem1.name)
+        cls.create_data = [
+            {
+                'device': device.pk,
+                'name': 'Interface 4',
+                'type': '1000base-t',
+                'mode': InterfaceModeChoices.MODE_TAGGED,
+                'tagged_vlans': [vlans[0].pk, vlans[1].pk],
+                'untagged_vlan': vlans[2].pk,
+            },
+            {
+                'device': device.pk,
+                'name': 'Interface 5',
+                'type': '1000base-t',
+                'mode': InterfaceModeChoices.MODE_TAGGED,
+                'tagged_vlans': [vlans[0].pk, vlans[1].pk],
+                'untagged_vlan': vlans[2].pk,
+            },
+            {
+                'device': device.pk,
+                'name': 'Interface 6',
+                'type': '1000base-t',
+                'mode': InterfaceModeChoices.MODE_TAGGED,
+                'tagged_vlans': [vlans[0].pk, vlans[1].pk],
+                'untagged_vlan': vlans[2].pk,
+            },
+        ]
 
-    def test_list_inventoryitems(self):
+    def test_get_interface_graphs(self):
+        """
+        Test retrieval of Graphs assigned to Devices.
+        """
+        ct = ContentType.objects.get_for_model(Interface)
+        graphs = (
+            Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?interface={{ obj.name }}&foo=1'),
+            Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?interface={{ obj.name }}&foo=2'),
+            Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?interface={{ obj.name }}&foo=3'),
+        )
+        Graph.objects.bulk_create(graphs)
 
-        url = reverse('dcim-api:inventoryitem-list')
+        url = reverse('dcim-api:interface-graphs', kwargs={'pk': Interface.objects.first().pk})
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['count'], 3)
+        self.assertEqual(len(response.data), 3)
+        self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?interface=Interface 1&foo=1')
 
-    def test_create_inventoryitem(self):
 
-        data = {
-            'device': self.device.pk,
-            'parent': self.inventoryitem1.pk,
-            'name': 'Test Inventory Item 4',
-            'manufacturer': self.manufacturer.pk,
-        }
+class FrontPortTest(APIViewTestCases.APIViewTestCase):
+    model = FrontPort
+    brief_fields = ['cable', 'device', 'id', 'name', 'url']
 
-        url = reverse('dcim-api:inventoryitem-list')
-        response = self.client.post(url, data, format='json', **self.header)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(InventoryItem.objects.count(), 4)
-        inventoryitem4 = InventoryItem.objects.get(pk=response.data['id'])
-        self.assertEqual(inventoryitem4.device_id, data['device'])
-        self.assertEqual(inventoryitem4.parent_id, data['parent'])
-        self.assertEqual(inventoryitem4.name, data['name'])
-        self.assertEqual(inventoryitem4.manufacturer_id, data['manufacturer'])
+        rear_ports = (
+            RearPort(device=device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 3', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 4', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 5', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 6', type=PortTypeChoices.TYPE_8P8C),
+        )
+        RearPort.objects.bulk_create(rear_ports)
 
-    def test_create_inventoryitem_bulk(self):
+        front_ports = (
+            FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
+            FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
+            FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[2]),
+        )
+        FrontPort.objects.bulk_create(front_ports)
 
-        data = [
+        cls.create_data = [
             {
-                'device': self.device.pk,
-                'parent': self.inventoryitem1.pk,
-                'name': 'Test Inventory Item 4',
-                'manufacturer': self.manufacturer.pk,
+                'device': device.pk,
+                'name': 'Front Port 4',
+                'type': PortTypeChoices.TYPE_8P8C,
+                'rear_port': rear_ports[3].pk,
+                'rear_port_position': 1,
             },
             {
-                'device': self.device.pk,
-                'parent': self.inventoryitem1.pk,
-                'name': 'Test Inventory Item 5',
-                'manufacturer': self.manufacturer.pk,
+                'device': device.pk,
+                'name': 'Front Port 5',
+                'type': PortTypeChoices.TYPE_8P8C,
+                'rear_port': rear_ports[4].pk,
+                'rear_port_position': 1,
             },
             {
-                'device': self.device.pk,
-                'parent': self.inventoryitem1.pk,
-                'name': 'Test Inventory Item 6',
-                'manufacturer': self.manufacturer.pk,
+                'device': device.pk,
+                'name': 'Front Port 6',
+                'type': PortTypeChoices.TYPE_8P8C,
+                'rear_port': rear_ports[5].pk,
+                'rear_port_position': 1,
             },
         ]
 
-        url = reverse('dcim-api:inventoryitem-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(InventoryItem.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_inventoryitem(self):
-
-        data = {
-            'device': self.device.pk,
-            'parent': self.inventoryitem1.pk,
-            'name': 'Test Inventory Item X',
-            'manufacturer': self.manufacturer.pk,
-        }
-
-        url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(InventoryItem.objects.count(), 3)
-        inventoryitem1 = InventoryItem.objects.get(pk=response.data['id'])
-        self.assertEqual(inventoryitem1.device_id, data['device'])
-        self.assertEqual(inventoryitem1.parent_id, data['parent'])
-        self.assertEqual(inventoryitem1.name, data['name'])
-        self.assertEqual(inventoryitem1.manufacturer_id, data['manufacturer'])
 
-    def test_delete_inventoryitem(self):
+class RearPortTest(APIViewTestCases.APIViewTestCase):
+    model = RearPort
+    brief_fields = ['cable', 'device', 'id', 'name', 'url']
 
-        url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
-        response = self.client.delete(url, **self.header)
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(InventoryItem.objects.count(), 2)
+        rear_ports = (
+            RearPort(device=device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
+            RearPort(device=device, name='Rear Port 3', type=PortTypeChoices.TYPE_8P8C),
+        )
+        RearPort.objects.bulk_create(rear_ports)
 
+        cls.create_data = [
+            {
+                'device': device.pk,
+                'name': 'Rear Port 4',
+                'type': PortTypeChoices.TYPE_8P8C,
+            },
+            {
+                'device': device.pk,
+                'name': 'Rear Port 5',
+                'type': PortTypeChoices.TYPE_8P8C,
+            },
+            {
+                'device': device.pk,
+                'name': 'Rear Port 6',
+                'type': PortTypeChoices.TYPE_8P8C,
+            },
+        ]
 
-class CableTest(APITestCase):
 
-    def setUp(self):
+class DeviceBayTest(APIViewTestCases.APIViewTestCase):
+    model = DeviceBay
+    brief_fields = ['device', 'id', 'name', 'url']
 
-        super().setUp()
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
 
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(
-            manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
-        )
-        devicerole = DeviceRole.objects.create(
-            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
-        )
-        self.device1 = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
+        device_types = (
+            DeviceType(
+                manufacturer=manufacturer,
+                model='Device Type 1',
+                slug='device-type-1',
+                subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
+            ),
+            DeviceType(
+                manufacturer=manufacturer,
+                model='Device Type 2',
+                slug='device-type-2',
+                subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
+            ),
         )
-        self.device2 = Device.objects.create(
-            device_type=devicetype, device_role=devicerole, name='Test Device 2', site=site
-        )
-        for device in [self.device1, self.device2]:
-            for i in range(0, 10):
-                Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth{}'.format(i)).save()
+        DeviceType.objects.bulk_create(device_types)
 
-        self.cable1 = Cable(
-            termination_a=self.device1.interfaces.get(name='eth0'),
-            termination_b=self.device2.interfaces.get(name='eth0'),
-            label='Test Cable 1'
-        )
-        self.cable1.save()
-        self.cable2 = Cable(
-            termination_a=self.device1.interfaces.get(name='eth1'),
-            termination_b=self.device2.interfaces.get(name='eth1'),
-            label='Test Cable 2'
+        devices = (
+            Device(device_type=device_types[0], device_role=devicerole, name='Device 1', site=site),
+            Device(device_type=device_types[1], device_role=devicerole, name='Device 2', site=site),
+            Device(device_type=device_types[1], device_role=devicerole, name='Device 3', site=site),
+            Device(device_type=device_types[1], device_role=devicerole, name='Device 4', site=site),
         )
-        self.cable2.save()
-        self.cable3 = Cable(
-            termination_a=self.device1.interfaces.get(name='eth2'),
-            termination_b=self.device2.interfaces.get(name='eth2'),
-            label='Test Cable 3'
+        Device.objects.bulk_create(devices)
+
+        device_bays = (
+            DeviceBay(device=devices[0], name='Device Bay 1'),
+            DeviceBay(device=devices[0], name='Device Bay 2'),
+            DeviceBay(device=devices[0], name='Device Bay 3'),
         )
-        self.cable3.save()
+        DeviceBay.objects.bulk_create(device_bays)
 
-    def test_get_cable(self):
+        cls.create_data = [
+            {
+                'device': devices[0].pk,
+                'name': 'Device Bay 4',
+                'installed_device': devices[1].pk,
+            },
+            {
+                'device': devices[0].pk,
+                'name': 'Device Bay 5',
+                'installed_device': devices[2].pk,
+            },
+            {
+                'device': devices[0].pk,
+                'name': 'Device Bay 6',
+                'installed_device': devices[3].pk,
+            },
+        ]
 
-        url = reverse('dcim-api:cable-detail', kwargs={'pk': self.cable1.pk})
-        response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['id'], self.cable1.pk)
+class InventoryItemTest(APIViewTestCases.APIViewTestCase):
+    model = InventoryItem
+    brief_fields = ['device', 'id', 'name', 'url']
 
-    def test_list_cables(self):
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
+        device = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Device 1', site=site)
 
-        url = reverse('dcim-api:cable-list')
-        response = self.client.get(url, **self.header)
+        inventory_items = (
+            InventoryItem(device=device, name='Inventory Item 1', manufacturer=manufacturer),
+            InventoryItem(device=device, name='Inventory Item 2', manufacturer=manufacturer),
+            InventoryItem(device=device, name='Inventory Item 3', manufacturer=manufacturer),
+        )
+        InventoryItem.objects.bulk_create(inventory_items)
 
-        self.assertEqual(response.data['count'], 3)
+        cls.create_data = [
+            {
+                'device': device.pk,
+                'name': 'Inventory Item 4',
+                'manufacturer': manufacturer.pk,
+            },
+            {
+                'device': device.pk,
+                'name': 'Inventory Item 5',
+                'manufacturer': manufacturer.pk,
+            },
+            {
+                'device': device.pk,
+                'name': 'Inventory Item 6',
+                'manufacturer': manufacturer.pk,
+            },
+        ]
 
-    def test_create_cable(self):
 
-        interface_a = self.device1.interfaces.get(name='eth3')
-        interface_b = self.device2.interfaces.get(name='eth3')
-        data = {
-            'termination_a_type': 'dcim.interface',
-            'termination_a_id': interface_a.pk,
-            'termination_b_type': 'dcim.interface',
-            'termination_b_id': interface_b.pk,
-            'status': CableStatusChoices.STATUS_PLANNED,
-            'label': 'Test Cable 4',
-        }
+class CableTest(APIViewTestCases.APIViewTestCase):
+    model = Cable
+    brief_fields = ['id', 'label', 'url']
 
-        url = reverse('dcim-api:cable-list')
-        response = self.client.post(url, data, format='json', **self.header)
+    # TODO: Allow updating cable terminations
+    test_update_object = None
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Cable.objects.count(), 4)
-        cable4 = Cable.objects.get(pk=response.data['id'])
-        self.assertEqual(cable4.termination_a, interface_a)
-        self.assertEqual(cable4.termination_b, interface_b)
-        self.assertEqual(cable4.status, data['status'])
-        self.assertEqual(cable4.label, data['label'])
+    @classmethod
+    def setUpTestData(cls):
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1', color='ff0000')
+
+        devices = (
+            Device(device_type=devicetype, device_role=devicerole, name='Device 1', site=site),
+            Device(device_type=devicetype, device_role=devicerole, name='Device 2', site=site),
+        )
+        Device.objects.bulk_create(devices)
+
+        interfaces = []
+        for device in devices:
+            for i in range(0, 10):
+                interfaces.append(Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name=f'eth{i}'))
+        Interface.objects.bulk_create(interfaces)
 
-    def test_create_cable_bulk(self):
+        cables = (
+            Cable(termination_a=interfaces[0], termination_b=interfaces[10], label='Cable 1'),
+            Cable(termination_a=interfaces[1], termination_b=interfaces[11], label='Cable 2'),
+            Cable(termination_a=interfaces[2], termination_b=interfaces[12], label='Cable 3'),
+        )
+        for cable in cables:
+            cable.save()
 
-        data = [
+        cls.create_data = [
             {
                 'termination_a_type': 'dcim.interface',
-                'termination_a_id': self.device1.interfaces.get(name='eth3').pk,
+                'termination_a_id': interfaces[4].pk,
                 'termination_b_type': 'dcim.interface',
-                'termination_b_id': self.device2.interfaces.get(name='eth3').pk,
-                'label': 'Test Cable 4',
+                'termination_b_id': interfaces[14].pk,
+                'label': 'Cable 4',
             },
             {
                 'termination_a_type': 'dcim.interface',
-                'termination_a_id': self.device1.interfaces.get(name='eth4').pk,
+                'termination_a_id': interfaces[5].pk,
                 'termination_b_type': 'dcim.interface',
-                'termination_b_id': self.device2.interfaces.get(name='eth4').pk,
-                'label': 'Test Cable 5',
+                'termination_b_id': interfaces[15].pk,
+                'label': 'Cable 5',
             },
             {
                 'termination_a_type': 'dcim.interface',
-                'termination_a_id': self.device1.interfaces.get(name='eth5').pk,
+                'termination_a_id': interfaces[6].pk,
                 'termination_b_type': 'dcim.interface',
-                'termination_b_id': self.device2.interfaces.get(name='eth5').pk,
-                'label': 'Test Cable 6',
+                'termination_b_id': interfaces[16].pk,
+                'label': 'Cable 6',
             },
         ]
 
-        url = reverse('dcim-api:cable-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Cable.objects.count(), 6)
-        self.assertEqual(response.data[0]['label'], data[0]['label'])
-        self.assertEqual(response.data[1]['label'], data[1]['label'])
-        self.assertEqual(response.data[2]['label'], data[2]['label'])
-
-    def test_update_cable(self):
-
-        data = {
-            'label': 'Test Cable X',
-            'status': CableStatusChoices.STATUS_CONNECTED,
-        }
-
-        url = reverse('dcim-api:cable-detail', kwargs={'pk': self.cable1.pk})
-        response = self.client.patch(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Cable.objects.count(), 3)
-        cable1 = Cable.objects.get(pk=response.data['id'])
-        self.assertEqual(cable1.status, data['status'])
-        self.assertEqual(cable1.label, data['label'])
-
-    def test_delete_cable(self):
-
-        url = reverse('dcim-api:cable-detail', kwargs={'pk': self.cable1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Cable.objects.count(), 2)
-
 
 class ConnectionTest(APITestCase):
 
@@ -3839,426 +1805,159 @@ def test_get_connected_device(self):
         self.assertEqual(response.data['name'], self.device1.name)
 
 
-class VirtualChassisTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
+class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
+    model = VirtualChassis
+    brief_fields = ['id', 'master', 'member_count', 'url']
 
+    @classmethod
+    def setUpTestData(cls):
         site = Site.objects.create(name='Test Site', slug='test-site')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer', slug='test-manufacturer')
-        device_type = DeviceType.objects.create(
-            manufacturer=manufacturer, model='Test Device Type', slug='test-device-type'
-        )
-        device_role = DeviceRole.objects.create(
-            name='Test Device Role', slug='test-device-role', color='ff0000'
-        )
-
-        # Create 9 member Devices with 12 interfaces each
-        self.device1 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch1', site=site
-        )
-        self.device2 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch2', site=site
-        )
-        self.device3 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch3', site=site
-        )
-        self.device4 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch4', site=site
-        )
-        self.device5 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch5', site=site
-        )
-        self.device6 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch6', site=site
-        )
-        self.device7 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch7', site=site
-        )
-        self.device8 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch8', site=site
-        )
-        self.device9 = Device.objects.create(
-            device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
-        )
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device1, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device2, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device3, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device4, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device5, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device6, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device7, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device8, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
-        for i in range(0, 13):
-            Interface.objects.create(device=self.device9, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
+        manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type', slug='device-type')
+        devicerole = DeviceRole.objects.create(name='Device Role', slug='device-role', color='ff0000')
+
+        devices = (
+            Device(name='Device 1', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 2', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 3', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 4', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 5', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 6', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 7', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 8', device_type=devicetype, device_role=devicerole, site=site),
+            Device(name='Device 9', device_type=devicetype, device_role=devicerole, site=site),
+        )
+        Device.objects.bulk_create(devices)
+
+        # Create 12 interfaces per device
+        interfaces = []
+        for i, device in enumerate(devices):
+            for j in range(0, 13):
+                interfaces.append(
+                    # Interface name starts with parent device's position in VC; e.g. 1/1, 1/2, 1/3...
+                    Interface(device=device, name=f'{i%3+1}/{j}', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
+                )
+        Interface.objects.bulk_create(interfaces)
 
         # Create two VirtualChassis with three members each
-        self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1')
-        Device.objects.filter(pk=self.device2.pk).update(virtual_chassis=self.vc1, vc_position=2)
-        Device.objects.filter(pk=self.device3.pk).update(virtual_chassis=self.vc1, vc_position=3)
-        self.vc2 = VirtualChassis.objects.create(master=self.device4, domain='test-domain-2')
-        Device.objects.filter(pk=self.device5.pk).update(virtual_chassis=self.vc2, vc_position=2)
-        Device.objects.filter(pk=self.device6.pk).update(virtual_chassis=self.vc2, vc_position=3)
-
-    def test_get_virtualchassis(self):
-
-        url = reverse('dcim-api:virtualchassis-detail', kwargs={'pk': self.vc1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['domain'], self.vc1.domain)
-
-    def test_list_virtualchassis(self):
-
-        url = reverse('dcim-api:virtualchassis-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 2)
-
-    def test_list_virtualchassis_brief(self):
-
-        url = reverse('dcim-api:virtualchassis-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'master', 'member_count', 'url']
+        virtual_chassis = (
+            VirtualChassis(master=devices[0], domain='domain-1'),
+            VirtualChassis(master=devices[3], domain='domain-2'),
         )
+        VirtualChassis.objects.bulk_create(virtual_chassis)
+        Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis[0], vc_position=2)
+        Device.objects.filter(pk=devices[2].pk).update(virtual_chassis=virtual_chassis[0], vc_position=3)
+        Device.objects.filter(pk=devices[4].pk).update(virtual_chassis=virtual_chassis[1], vc_position=2)
+        Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[1], vc_position=3)
 
-    def test_create_virtualchassis(self):
-
-        data = {
-            'master': self.device7.pk,
-            'domain': 'test-domain-3',
+        cls.update_data = {
+            'master': devices[1].pk,
+            'domain': 'domain-x',
         }
 
-        url = reverse('dcim-api:virtualchassis-list')
-        response = self.client.post(url, data, format='json', **self.header)
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VirtualChassis.objects.count(), 3)
-        vc3 = VirtualChassis.objects.get(pk=response.data['id'])
-        self.assertEqual(vc3.master.pk, data['master'])
-        self.assertEqual(vc3.domain, data['domain'])
-
-        # Verify that the master device was automatically assigned to the VC
-        self.assertTrue(Device.objects.filter(pk=vc3.master.pk, virtual_chassis=vc3.pk).exists())
-
-    def test_create_virtualchassis_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
-                'master': self.device7.pk,
-                'domain': 'test-domain-3',
+                'master': devices[6].pk,
+                'domain': 'domain-3',
             },
             {
-                'master': self.device8.pk,
-                'domain': 'test-domain-4',
+                'master': devices[7].pk,
+                'domain': 'domain-4',
             },
             {
-                'master': self.device9.pk,
-                'domain': 'test-domain-5',
+                'master': devices[8].pk,
+                'domain': 'domain-5',
             },
         ]
 
-        url = reverse('dcim-api:virtualchassis-list')
-        response = self.client.post(url, data, format='json', **self.header)
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VirtualChassis.objects.count(), 5)
-        for i in range(0, 3):
-            self.assertEqual(response.data[i]['master']['id'], data[i]['master'])
-            self.assertEqual(response.data[i]['domain'], data[i]['domain'])
-
-    def test_update_virtualchassis(self):
-
-        data = {
-            'master': self.device2.pk,
-            'domain': 'test-domain-x',
-        }
-
-        url = reverse('dcim-api:virtualchassis-detail', kwargs={'pk': self.vc1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(VirtualChassis.objects.count(), 2)
-        vc1 = VirtualChassis.objects.get(pk=response.data['id'])
-        self.assertEqual(vc1.master.pk, data['master'])
-        self.assertEqual(vc1.domain, data['domain'])
-
-    def test_delete_virtualchassis(self):
-
-        url = reverse('dcim-api:virtualchassis-detail', kwargs={'pk': self.vc1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(VirtualChassis.objects.count(), 1)
-
-        # Verify that all VC members have had their VC-related fields nullified
-        for d in [self.device1, self.device2, self.device3]:
-            self.assertTrue(
-                Device.objects.filter(pk=d.pk, virtual_chassis=None, vc_position=None, vc_priority=None)
-            )
 
+class PowerPanelTest(APIViewTestCases.APIViewTestCase):
+    model = PowerPanel
+    brief_fields = ['id', 'name', 'powerfeed_count', 'url']
 
-class PowerPanelTest(APITestCase):
+    @classmethod
+    def setUpTestData(cls):
+        site = Site.objects.create(name='Site 1', slug='site-1')
 
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1')
-        self.rackgroup2 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 2', slug='test-rack-group-2')
-        self.rackgroup3 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 3', slug='test-rack-group-3')
-        self.powerpanel1 = PowerPanel.objects.create(
-            site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 1'
-        )
-        self.powerpanel2 = PowerPanel.objects.create(
-            site=self.site1, rack_group=self.rackgroup2, name='Test Power Panel 2'
+        rack_groups = (
+            RackGroup.objects.create(name='Rack Group 1', slug='rack-group-1', site=site),
+            RackGroup.objects.create(name='Rack Group 2', slug='rack-group-2', site=site),
+            RackGroup.objects.create(name='Rack Group 3', slug='rack-group-3', site=site),
         )
-        self.powerpanel3 = PowerPanel.objects.create(
-            site=self.site1, rack_group=self.rackgroup3, name='Test Power Panel 3'
-        )
-
-    def test_get_powerpanel(self):
 
-        url = reverse('dcim-api:powerpanel-detail', kwargs={'pk': self.powerpanel1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.powerpanel1.name)
-
-    def test_list_powerpanels(self):
-
-        url = reverse('dcim-api:powerpanel-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_powerpanels_brief(self):
-
-        url = reverse('dcim-api:powerpanel-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'powerfeed_count', 'url']
+        power_panels = (
+            PowerPanel(site=site, rack_group=rack_groups[0], name='Power Panel 1'),
+            PowerPanel(site=site, rack_group=rack_groups[1], name='Power Panel 2'),
+            PowerPanel(site=site, rack_group=rack_groups[2], name='Power Panel 3'),
         )
+        PowerPanel.objects.bulk_create(power_panels)
 
-    def test_create_powerpanel(self):
-
-        data = {
-            'name': 'Test Power Panel 4',
-            'site': self.site1.pk,
-            'rack_group': self.rackgroup1.pk,
-        }
-
-        url = reverse('dcim-api:powerpanel-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerPanel.objects.count(), 4)
-        powerpanel4 = PowerPanel.objects.get(pk=response.data['id'])
-        self.assertEqual(powerpanel4.name, data['name'])
-        self.assertEqual(powerpanel4.site_id, data['site'])
-        self.assertEqual(powerpanel4.rack_group_id, data['rack_group'])
-
-    def test_create_powerpanel_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Power Panel 4',
-                'site': self.site1.pk,
-                'rack_group': self.rackgroup1.pk,
+                'name': 'Power Panel 4',
+                'site': site.pk,
+                'rack_group': rack_groups[0].pk,
             },
             {
-                'name': 'Test Power Panel 5',
-                'site': self.site1.pk,
-                'rack_group': self.rackgroup2.pk,
+                'name': 'Power Panel 5',
+                'site': site.pk,
+                'rack_group': rack_groups[1].pk,
             },
             {
-                'name': 'Test Power Panel 6',
-                'site': self.site1.pk,
-                'rack_group': self.rackgroup3.pk,
+                'name': 'Power Panel 6',
+                'site': site.pk,
+                'rack_group': rack_groups[2].pk,
             },
         ]
 
-        url = reverse('dcim-api:powerpanel-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerPanel.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_powerpanel(self):
-
-        data = {
-            'name': 'Test Power Panel X',
-            'rack_group': self.rackgroup2.pk,
-        }
-
-        url = reverse('dcim-api:powerpanel-detail', kwargs={'pk': self.powerpanel1.pk})
-        response = self.client.patch(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(PowerPanel.objects.count(), 3)
-        powerpanel1 = PowerPanel.objects.get(pk=response.data['id'])
-        self.assertEqual(powerpanel1.name, data['name'])
-        self.assertEqual(powerpanel1.rack_group_id, data['rack_group'])
-
-    def test_delete_powerpanel(self):
-
-        url = reverse('dcim-api:powerpanel-detail', kwargs={'pk': self.powerpanel1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(PowerPanel.objects.count(), 2)
-
 
-class PowerFeedTest(APITestCase):
+class PowerFeedTest(APIViewTestCases.APIViewTestCase):
+    model = PowerFeed
+    brief_fields = ['id', 'name', 'url']
 
-    def setUp(self):
-
-        super().setUp()
+    @classmethod
+    def setUpTestData(cls):
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        rackgroup = RackGroup.objects.create(site=site, name='Rack Group 1', slug='rack-group-1')
+        rackrole = RackRole.objects.create(name='Rack Role 1', slug='rack-role-1', color='ff0000')
 
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1')
-        self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
-        self.rack1 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 1', u_height=42,
-        )
-        self.rack2 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 2', u_height=42,
-        )
-        self.rack3 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 3', u_height=42,
-        )
-        self.rack4 = Rack.objects.create(
-            site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 4', u_height=42,
-        )
-        self.powerpanel1 = PowerPanel.objects.create(
-            site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 1'
+        racks = (
+            Rack(site=site, group=rackgroup, role=rackrole, name='Rack 1'),
+            Rack(site=site, group=rackgroup, role=rackrole, name='Rack 2'),
+            Rack(site=site, group=rackgroup, role=rackrole, name='Rack 3'),
+            Rack(site=site, group=rackgroup, role=rackrole, name='Rack 4'),
         )
-        self.powerpanel2 = PowerPanel.objects.create(
-            site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 2'
-        )
-        self.powerfeed1 = PowerFeed.objects.create(
-            power_panel=self.powerpanel1, rack=self.rack1, name='Test Power Feed 1A', type=PowerFeedTypeChoices.TYPE_PRIMARY
-        )
-        self.powerfeed2 = PowerFeed.objects.create(
-            power_panel=self.powerpanel2, rack=self.rack1, name='Test Power Feed 1B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
-        )
-        self.powerfeed3 = PowerFeed.objects.create(
-            power_panel=self.powerpanel1, rack=self.rack2, name='Test Power Feed 2A', type=PowerFeedTypeChoices.TYPE_PRIMARY
-        )
-        self.powerfeed4 = PowerFeed.objects.create(
-            power_panel=self.powerpanel2, rack=self.rack2, name='Test Power Feed 2B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
-        )
-        self.powerfeed5 = PowerFeed.objects.create(
-            power_panel=self.powerpanel1, rack=self.rack3, name='Test Power Feed 3A', type=PowerFeedTypeChoices.TYPE_PRIMARY
-        )
-        self.powerfeed6 = PowerFeed.objects.create(
-            power_panel=self.powerpanel2, rack=self.rack3, name='Test Power Feed 3B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
-        )
-
-    def test_get_powerfeed(self):
+        Rack.objects.bulk_create(racks)
 
-        url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.powerfeed1.name)
-
-    def test_list_powerfeeds(self):
-
-        url = reverse('dcim-api:powerfeed-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 6)
-
-    def test_list_powerfeeds_brief(self):
-
-        url = reverse('dcim-api:powerfeed-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'url']
+        power_panels = (
+            PowerPanel(site=site, rack_group=rackgroup, name='Power Panel 1'),
+            PowerPanel(site=site, rack_group=rackgroup, name='Power Panel 2'),
         )
+        PowerPanel.objects.bulk_create(power_panels)
 
-    def test_create_powerfeed(self):
-
-        data = {
-            'name': 'Test Power Feed 4A',
-            'power_panel': self.powerpanel1.pk,
-            'rack': self.rack4.pk,
-            'type': PowerFeedTypeChoices.TYPE_PRIMARY,
-        }
-
-        url = reverse('dcim-api:powerfeed-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerFeed.objects.count(), 7)
-        powerfeed4 = PowerFeed.objects.get(pk=response.data['id'])
-        self.assertEqual(powerfeed4.name, data['name'])
-        self.assertEqual(powerfeed4.power_panel_id, data['power_panel'])
-        self.assertEqual(powerfeed4.rack_id, data['rack'])
-
-    def test_create_powerfeed_bulk(self):
+        PRIMARY = PowerFeedTypeChoices.TYPE_PRIMARY
+        REDUNDANT = PowerFeedTypeChoices.TYPE_REDUNDANT
+        power_feeds = (
+            PowerFeed(power_panel=power_panels[0], rack=racks[0], name='Power Feed 1A', type=PRIMARY),
+            PowerFeed(power_panel=power_panels[1], rack=racks[0], name='Power Feed 1B', type=REDUNDANT),
+            PowerFeed(power_panel=power_panels[0], rack=racks[1], name='Power Feed 2A', type=PRIMARY),
+            PowerFeed(power_panel=power_panels[1], rack=racks[1], name='Power Feed 2B', type=REDUNDANT),
+            PowerFeed(power_panel=power_panels[0], rack=racks[2], name='Power Feed 3A', type=PRIMARY),
+            PowerFeed(power_panel=power_panels[1], rack=racks[2], name='Power Feed 3B', type=REDUNDANT),
+        )
+        PowerFeed.objects.bulk_create(power_feeds)
 
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Power Feed 4A',
-                'power_panel': self.powerpanel1.pk,
-                'rack': self.rack4.pk,
-                'type': PowerFeedTypeChoices.TYPE_PRIMARY,
+                'name': 'Power Feed 4A',
+                'power_panel': power_panels[0].pk,
+                'rack': racks[3].pk,
+                'type': PRIMARY,
             },
             {
-                'name': 'Test Power Feed 4B',
-                'power_panel': self.powerpanel1.pk,
-                'rack': self.rack4.pk,
-                'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
+                'name': 'Power Feed 4B',
+                'power_panel': power_panels[1].pk,
+                'rack': racks[3].pk,
+                'type': REDUNDANT,
             },
         ]
-
-        url = reverse('dcim-api:powerfeed-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(PowerFeed.objects.count(), 8)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-
-    def test_update_powerfeed(self):
-
-        data = {
-            'name': 'Test Power Feed X',
-            'rack': self.rack4.pk,
-            'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
-        }
-
-        url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk})
-        response = self.client.patch(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(PowerFeed.objects.count(), 6)
-        powerfeed1 = PowerFeed.objects.get(pk=response.data['id'])
-        self.assertEqual(powerfeed1.name, data['name'])
-        self.assertEqual(powerfeed1.rack_id, data['rack'])
-        self.assertEqual(powerfeed1.type, data['type'])
-
-    def test_delete_powerfeed(self):
-
-        url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(PowerFeed.objects.count(), 5)
diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py
--- a/netbox/extras/tests/test_api.py
+++ b/netbox/extras/tests/test_api.py
@@ -5,13 +5,11 @@
 from django.utils import timezone
 from rest_framework import status
 
-from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site
+from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site
 from extras.api.views import ScriptViewSet
 from extras.models import ConfigContext, Graph, ExportTemplate, Tag
 from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
-from extras.utils import FeatureQuery
-from tenancy.models import Tenant, TenantGroup
-from utilities.testing import APITestCase
+from utilities.testing import APITestCase, APIViewTestCases
 
 
 class AppTest(APITestCase):
@@ -24,489 +22,150 @@ def test_root(self):
         self.assertEqual(response.status_code, 200)
 
 
-class GraphTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        site_ct = ContentType.objects.get_for_model(Site)
-        self.graph1 = Graph.objects.create(
-            type=site_ct,
-            name='Test Graph 1',
-            source='http://example.com/graphs.py?site={{ obj.name }}&foo=1'
-        )
-        self.graph2 = Graph.objects.create(
-            type=site_ct,
-            name='Test Graph 2',
-            source='http://example.com/graphs.py?site={{ obj.name }}&foo=2'
-        )
-        self.graph3 = Graph.objects.create(
-            type=site_ct,
-            name='Test Graph 3',
-            source='http://example.com/graphs.py?site={{ obj.name }}&foo=3'
-        )
-
-    def test_get_graph(self):
-
-        url = reverse('extras-api:graph-detail', kwargs={'pk': self.graph1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.graph1.name)
-
-    def test_list_graphs(self):
-
-        url = reverse('extras-api:graph-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_graph(self):
-
-        data = {
+class GraphTest(APIViewTestCases.APIViewTestCase):
+    model = Graph
+    brief_fields = ['id', 'name', 'url']
+    create_data = [
+        {
             'type': 'dcim.site',
-            'name': 'Test Graph 4',
+            'name': 'Graph 4',
             'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=4',
-        }
-
-        url = reverse('extras-api:graph-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Graph.objects.count(), 4)
-        graph4 = Graph.objects.get(pk=response.data['id'])
-        self.assertEqual(graph4.type, ContentType.objects.get_for_model(Site))
-        self.assertEqual(graph4.name, data['name'])
-        self.assertEqual(graph4.source, data['source'])
-
-    def test_create_graph_bulk(self):
-
-        data = [
-            {
-                'type': 'dcim.site',
-                'name': 'Test Graph 4',
-                'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=4',
-            },
-            {
-                'type': 'dcim.site',
-                'name': 'Test Graph 5',
-                'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=5',
-            },
-            {
-                'type': 'dcim.site',
-                'name': 'Test Graph 6',
-                'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=6',
-            },
-        ]
-
-        url = reverse('extras-api:graph-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Graph.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_graph(self):
-
-        data = {
+        },
+        {
             'type': 'dcim.site',
-            'name': 'Test Graph X',
-            'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=99',
-        }
-
-        url = reverse('extras-api:graph-detail', kwargs={'pk': self.graph1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Graph.objects.count(), 3)
-        graph1 = Graph.objects.get(pk=response.data['id'])
-        self.assertEqual(graph1.type, ContentType.objects.get_for_model(Site))
-        self.assertEqual(graph1.name, data['name'])
-        self.assertEqual(graph1.source, data['source'])
-
-    def test_delete_graph(self):
-
-        url = reverse('extras-api:graph-detail', kwargs={'pk': self.graph1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Graph.objects.count(), 2)
-
-
-class ExportTemplateTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        content_type = ContentType.objects.get_for_model(Device)
-        self.exporttemplate1 = ExportTemplate.objects.create(
-            content_type=content_type, name='Test Export Template 1',
-            template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
-        )
-        self.exporttemplate2 = ExportTemplate.objects.create(
-            content_type=content_type, name='Test Export Template 2',
-            template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
-        )
-        self.exporttemplate3 = ExportTemplate.objects.create(
-            content_type=content_type, name='Test Export Template 3',
-            template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
+            'name': 'Graph 5',
+            'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=5',
+        },
+        {
+            'type': 'dcim.site',
+            'name': 'Graph 6',
+            'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+        ct = ContentType.objects.get_for_model(Site)
+
+        graphs = (
+            Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?site={{ obj.name }}&foo=1'),
+            Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?site={{ obj.name }}&foo=2'),
+            Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?site={{ obj.name }}&foo=3'),
         )
+        Graph.objects.bulk_create(graphs)
 
-    def test_get_exporttemplate(self):
-
-        url = reverse('extras-api:exporttemplate-detail', kwargs={'pk': self.exporttemplate1.pk})
-        response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['name'], self.exporttemplate1.name)
-
-    def test_list_exporttemplates(self):
-
-        url = reverse('extras-api:exporttemplate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_exporttemplate(self):
-
-        data = {
+class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
+    model = ExportTemplate
+    brief_fields = ['id', 'name', 'url']
+    create_data = [
+        {
             'content_type': 'dcim.device',
             'name': 'Test Export Template 4',
             'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
-        }
-
-        url = reverse('extras-api:exporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ExportTemplate.objects.count(), 4)
-        exporttemplate4 = ExportTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(exporttemplate4.content_type, ContentType.objects.get_for_model(Device))
-        self.assertEqual(exporttemplate4.name, data['name'])
-        self.assertEqual(exporttemplate4.template_code, data['template_code'])
-
-    def test_create_exporttemplate_bulk(self):
-
-        data = [
-            {
-                'content_type': 'dcim.device',
-                'name': 'Test Export Template 4',
-                'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
-            },
-            {
-                'content_type': 'dcim.device',
-                'name': 'Test Export Template 5',
-                'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
-            },
-            {
-                'content_type': 'dcim.device',
-                'name': 'Test Export Template 6',
-                'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
-            },
-        ]
-
-        url = reverse('extras-api:exporttemplate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ExportTemplate.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_exporttemplate(self):
-
-        data = {
+        },
+        {
             'content_type': 'dcim.device',
-            'name': 'Test Export Template X',
+            'name': 'Test Export Template 5',
             'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
-        }
-
-        url = reverse('extras-api:exporttemplate-detail', kwargs={'pk': self.exporttemplate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ExportTemplate.objects.count(), 3)
-        exporttemplate1 = ExportTemplate.objects.get(pk=response.data['id'])
-        self.assertEqual(exporttemplate1.name, data['name'])
-        self.assertEqual(exporttemplate1.template_code, data['template_code'])
-
-    def test_delete_exporttemplate(self):
-
-        url = reverse('extras-api:exporttemplate-detail', kwargs={'pk': self.exporttemplate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ExportTemplate.objects.count(), 2)
-
-
-class TagTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.tag1 = Tag.objects.create(name='Test Tag 1', slug='test-tag-1')
-        self.tag2 = Tag.objects.create(name='Test Tag 2', slug='test-tag-2')
-        self.tag3 = Tag.objects.create(name='Test Tag 3', slug='test-tag-3')
-
-    def test_get_tag(self):
-
-        url = reverse('extras-api:tag-detail', kwargs={'pk': self.tag1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.tag1.name)
-
-    def test_list_tags(self):
-
-        url = reverse('extras-api:tag-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_tag(self):
-
-        data = {
-            'name': 'Test Tag 4',
-            'slug': 'test-tag-4',
-        }
-
-        url = reverse('extras-api:tag-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Tag.objects.count(), 4)
-        tag4 = Tag.objects.get(pk=response.data['id'])
-        self.assertEqual(tag4.name, data['name'])
-        self.assertEqual(tag4.slug, data['slug'])
-
-    def test_create_tag_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Tag 4',
-                'slug': 'test-tag-4',
-            },
-            {
-                'name': 'Test Tag 5',
-                'slug': 'test-tag-5',
-            },
-            {
-                'name': 'Test Tag 6',
-                'slug': 'test-tag-6',
-            },
-        ]
-
-        url = reverse('extras-api:tag-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Tag.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_tag(self):
-
-        data = {
-            'name': 'Test Tag X',
-            'slug': 'test-tag-x',
-        }
-
-        url = reverse('extras-api:tag-detail', kwargs={'pk': self.tag1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Tag.objects.count(), 3)
-        tag1 = Tag.objects.get(pk=response.data['id'])
-        self.assertEqual(tag1.name, data['name'])
-        self.assertEqual(tag1.slug, data['slug'])
-
-    def test_delete_tag(self):
-
-        url = reverse('extras-api:tag-detail', kwargs={'pk': self.tag1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Tag.objects.count(), 2)
-
-
-class ConfigContextTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.configcontext1 = ConfigContext.objects.create(
-            name='Test Config Context 1',
-            weight=100,
-            data={'foo': 123}
+        },
+        {
+            'content_type': 'dcim.device',
+            'name': 'Test Export Template 6',
+            'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+        ct = ContentType.objects.get_for_model(Device)
+
+        export_templates = (
+            ExportTemplate(
+                content_type=ct,
+                name='Export Template 1',
+                template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
+            ),
+            ExportTemplate(
+                content_type=ct,
+                name='Export Template 2',
+                template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
+            ),
+            ExportTemplate(
+                content_type=ct,
+                name='Export Template 3',
+                template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
+            ),
         )
-        self.configcontext2 = ConfigContext.objects.create(
-            name='Test Config Context 2',
-            weight=200,
-            data={'bar': 456}
+        ExportTemplate.objects.bulk_create(export_templates)
+
+
+class TagTest(APIViewTestCases.APIViewTestCase):
+    model = Tag
+    brief_fields = ['color', 'id', 'name', 'slug', 'tagged_items', 'url']
+    create_data = [
+        {
+            'name': 'Tag 4',
+            'slug': 'tag-4',
+        },
+        {
+            'name': 'Tag 5',
+            'slug': 'tag-5',
+        },
+        {
+            'name': 'Tag 6',
+            'slug': 'tag-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        tags = (
+            Tag(name='Tag 1', slug='tag-1'),
+            Tag(name='Tag 2', slug='tag-2'),
+            Tag(name='Tag 3', slug='tag-3'),
         )
-        self.configcontext3 = ConfigContext.objects.create(
-            name='Test Config Context 3',
-            weight=300,
-            data={'baz': 789}
+        Tag.objects.bulk_create(tags)
+
+
+class ConfigContextTest(APIViewTestCases.APIViewTestCase):
+    model = ConfigContext
+    brief_fields = ['id', 'name', 'url']
+    create_data = [
+        {
+            'name': 'Config Context 4',
+            'data': {'more_foo': True},
+        },
+        {
+            'name': 'Config Context 5',
+            'data': {'more_bar': False},
+        },
+        {
+            'name': 'Config Context 6',
+            'data': {'more_baz': None},
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        config_contexts = (
+            ConfigContext(name='Config Context 1', weight=100, data={'foo': 123}),
+            ConfigContext(name='Config Context 2', weight=200, data={'bar': 456}),
+            ConfigContext(name='Config Context 3', weight=300, data={'baz': 789}),
         )
-
-    def test_get_configcontext(self):
-
-        url = reverse('extras-api:configcontext-detail', kwargs={'pk': self.configcontext1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.configcontext1.name)
-        self.assertEqual(response.data['data'], self.configcontext1.data)
-
-    def test_list_configcontexts(self):
-
-        url = reverse('extras-api:configcontext-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_configcontext(self):
-
-        region1 = Region.objects.create(name='Test Region 1', slug='test-region-1')
-        region2 = Region.objects.create(name='Test Region 2', slug='test-region-2')
-        site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
-        role1 = DeviceRole.objects.create(name='Test Role 1', slug='test-role-1')
-        role2 = DeviceRole.objects.create(name='Test Role 2', slug='test-role-2')
-        platform1 = Platform.objects.create(name='Test Platform 1', slug='test-platform-1')
-        platform2 = Platform.objects.create(name='Test Platform 2', slug='test-platform-2')
-        tenantgroup1 = TenantGroup(name='Test Tenant Group 1', slug='test-tenant-group-1')
-        tenantgroup1.save()
-        tenantgroup2 = TenantGroup(name='Test Tenant Group 2', slug='test-tenant-group-2')
-        tenantgroup2.save()
-        tenant1 = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
-        tenant2 = Tenant.objects.create(name='Test Tenant 2', slug='test-tenant-2')
-        tag1 = Tag.objects.create(name='Test Tag 1', slug='test-tag-1')
-        tag2 = Tag.objects.create(name='Test Tag 2', slug='test-tag-2')
-
-        data = {
-            'name': 'Test Config Context 4',
-            'weight': 1000,
-            'regions': [region1.pk, region2.pk],
-            'sites': [site1.pk, site2.pk],
-            'roles': [role1.pk, role2.pk],
-            'platforms': [platform1.pk, platform2.pk],
-            'tenant_groups': [tenantgroup1.pk, tenantgroup2.pk],
-            'tenants': [tenant1.pk, tenant2.pk],
-            'tags': [tag1.slug, tag2.slug],
-            'data': {'foo': 'XXX'}
-        }
-
-        url = reverse('extras-api:configcontext-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConfigContext.objects.count(), 4)
-        configcontext4 = ConfigContext.objects.get(pk=response.data['id'])
-        self.assertEqual(configcontext4.name, data['name'])
-        self.assertEqual(region1.pk, data['regions'][0])
-        self.assertEqual(region2.pk, data['regions'][1])
-        self.assertEqual(site1.pk, data['sites'][0])
-        self.assertEqual(site2.pk, data['sites'][1])
-        self.assertEqual(role1.pk, data['roles'][0])
-        self.assertEqual(role2.pk, data['roles'][1])
-        self.assertEqual(platform1.pk, data['platforms'][0])
-        self.assertEqual(platform2.pk, data['platforms'][1])
-        self.assertEqual(tenantgroup1.pk, data['tenant_groups'][0])
-        self.assertEqual(tenantgroup2.pk, data['tenant_groups'][1])
-        self.assertEqual(tenant1.pk, data['tenants'][0])
-        self.assertEqual(tenant2.pk, data['tenants'][1])
-        self.assertEqual(tag1.slug, data['tags'][0])
-        self.assertEqual(tag2.slug, data['tags'][1])
-        self.assertEqual(configcontext4.data, data['data'])
-
-    def test_create_configcontext_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Config Context 4',
-                'data': {'more_foo': True},
-            },
-            {
-                'name': 'Test Config Context 5',
-                'data': {'more_bar': False},
-            },
-            {
-                'name': 'Test Config Context 6',
-                'data': {'more_baz': None},
-            },
-        ]
-
-        url = reverse('extras-api:configcontext-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ConfigContext.objects.count(), 6)
-        for i in range(0, 3):
-            self.assertEqual(response.data[i]['name'], data[i]['name'])
-            self.assertEqual(response.data[i]['data'], data[i]['data'])
-
-    def test_update_configcontext(self):
-
-        region1 = Region.objects.create(name='Test Region 1', slug='test-region-1')
-        region2 = Region.objects.create(name='Test Region 2', slug='test-region-2')
-
-        data = {
-            'name': 'Test Config Context X',
-            'weight': 999,
-            'regions': [region1.pk, region2.pk],
-            'data': {'foo': 'XXX'}
-        }
-
-        url = reverse('extras-api:configcontext-detail', kwargs={'pk': self.configcontext1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ConfigContext.objects.count(), 3)
-        configcontext1 = ConfigContext.objects.get(pk=response.data['id'])
-        self.assertEqual(configcontext1.name, data['name'])
-        self.assertEqual(configcontext1.weight, data['weight'])
-        self.assertEqual(sorted([r.pk for r in configcontext1.regions.all()]), sorted(data['regions']))
-        self.assertEqual(configcontext1.data, data['data'])
-
-    def test_delete_configcontext(self):
-
-        url = reverse('extras-api:configcontext-detail', kwargs={'pk': self.configcontext1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ConfigContext.objects.count(), 2)
+        ConfigContext.objects.bulk_create(config_contexts)
 
     def test_render_configcontext_for_object(self):
-
-        # Create a Device for which we'll render a config context
-        manufacturer = Manufacturer.objects.create(
-            name='Test Manufacturer',
-            slug='test-manufacturer'
-        )
-        device_type = DeviceType.objects.create(
-            manufacturer=manufacturer,
-            model='Test Device Type'
-        )
-        device_role = DeviceRole.objects.create(
-            name='Test Role',
-            slug='test-role'
-        )
-        site = Site.objects.create(
-            name='Test Site',
-            slug='test-site'
-        )
-        device = Device.objects.create(
-            name='Test Device',
-            device_type=device_type,
-            device_role=device_role,
-            site=site
-        )
+        """
+        Test rendering config context data for a device.
+        """
+        manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
+        devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
+        site = Site.objects.create(name='Site-1', slug='site-1')
+        device = Device.objects.create(name='Device 1', device_type=devicetype, device_role=devicerole, site=site)
 
         # Test default config contexts (created at test setup)
         rendered_context = device.get_config_context()
@@ -516,7 +175,7 @@ def test_render_configcontext_for_object(self):
 
         # Add another context specific to the site
         configcontext4 = ConfigContext(
-            name='Test Config Context 4',
+            name='Config Context 4',
             data={'site_data': 'ABC'}
         )
         configcontext4.save()
@@ -526,7 +185,7 @@ def test_render_configcontext_for_object(self):
 
         # Override one of the default contexts
         configcontext5 = ConfigContext(
-            name='Test Config Context 5',
+            name='Config Context 5',
             weight=2000,
             data={'foo': 999}
         )
@@ -536,12 +195,9 @@ def test_render_configcontext_for_object(self):
         self.assertEqual(rendered_context['foo'], 999)
 
         # Add a context which does NOT match our device and ensure it does not apply
-        site2 = Site.objects.create(
-            name='Test Site 2',
-            slug='test-site-2'
-        )
+        site2 = Site.objects.create(name='Site 2', slug='site-2')
         configcontext6 = ConfigContext(
-            name='Test Config Context 6',
+            name='Config Context 6',
             weight=2000,
             data={'bar': 999}
         )
diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py
--- a/netbox/ipam/tests/test_api.py
+++ b/netbox/ipam/tests/test_api.py
@@ -7,7 +7,7 @@
 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 APITestCase, disable_warnings
+from utilities.testing import APITestCase, APIViewTestCases, disable_warnings
 
 
 class AppTest(APITestCase):
@@ -20,536 +20,158 @@ def test_root(self):
         self.assertEqual(response.status_code, 200)
 
 
-class VRFTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.vrf1 = VRF.objects.create(name='Test VRF 1', rd='65000:1')
-        self.vrf2 = VRF.objects.create(name='Test VRF 2', rd='65000:2')
-        self.vrf3 = VRF.objects.create(name='Test VRF 3')  # No RD
-
-    def test_get_vrf(self):
-
-        url = reverse('ipam-api:vrf-detail', kwargs={'pk': self.vrf1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.vrf1.name)
-
-    def test_list_vrfs(self):
-
-        url = reverse('ipam-api:vrf-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_vrfs_brief(self):
-
-        url = reverse('ipam-api:vrf-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'prefix_count', 'rd', 'url']
+class VRFTest(APIViewTestCases.APIViewTestCase):
+    model = VRF
+    brief_fields = ['id', 'name', 'prefix_count', 'rd', 'url']
+    create_data = [
+        {
+            'name': 'VRF 4',
+            'rd': '65000:4',
+        },
+        {
+            'name': 'VRF 5',
+            'rd': '65000:5',
+        },
+        {
+            'name': 'VRF 6',
+            'rd': '65000:6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        vrfs = (
+            VRF(name='VRF 1', rd='65000:1'),
+            VRF(name='VRF 2', rd='65000:2'),
+            VRF(name='VRF 3'),  # No RD
         )
-
-    def test_create_vrf(self):
-
-        data_list = [
-            # VRF with RD
-            {
-                'name': 'Test VRF 4',
-                'rd': '65000:4',
-            },
-            # VRF without RD
-            {
-                'name': 'Test VRF 5',
-            }
-        ]
-
-        url = reverse('ipam-api:vrf-list')
-
-        for data in data_list:
-            response = self.client.post(url, data, format='json', **self.header)
-            self.assertHttpStatus(response, status.HTTP_201_CREATED)
-            vrf = VRF.objects.get(pk=response.data['id'])
-            self.assertEqual(vrf.name, data['name'])
-            self.assertEqual(vrf.rd, data['rd'] if 'rd' in data else None)
-
-    def test_create_vrf_bulk(self):
-
-        data = [
-            {
-                'name': 'Test VRF 4',
-                'rd': '65000:4',
-            },
-            {
-                'name': 'Test VRF 5',
-                'rd': '65000:5',
-            },
-            {
-                'name': 'Test VRF 6',
-                'rd': '65000:6',
-            },
-        ]
-
-        url = reverse('ipam-api:vrf-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VRF.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_vrf(self):
-
-        data = {
-            'name': 'Test VRF X',
-            'rd': '65000:99',
-        }
-
-        url = reverse('ipam-api:vrf-detail', kwargs={'pk': self.vrf1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(VRF.objects.count(), 3)
-        vrf1 = VRF.objects.get(pk=response.data['id'])
-        self.assertEqual(vrf1.name, data['name'])
-        self.assertEqual(vrf1.rd, data['rd'])
-
-    def test_delete_vrf(self):
-
-        url = reverse('ipam-api:vrf-detail', kwargs={'pk': self.vrf1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(VRF.objects.count(), 2)
-
-
-class RIRTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.rir1 = RIR.objects.create(name='Test RIR 1', slug='test-rir-1')
-        self.rir2 = RIR.objects.create(name='Test RIR 2', slug='test-rir-2')
-        self.rir3 = RIR.objects.create(name='Test RIR 3', slug='test-rir-3')
-
-    def test_get_rir(self):
-
-        url = reverse('ipam-api:rir-detail', kwargs={'pk': self.rir1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.rir1.name)
-
-    def test_list_rirs(self):
-
-        url = reverse('ipam-api:rir-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_rirs_brief(self):
-
-        url = reverse('ipam-api:rir-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['aggregate_count', 'id', 'name', 'slug', 'url']
+        VRF.objects.bulk_create(vrfs)
+
+
+class RIRTest(APIViewTestCases.APIViewTestCase):
+    model = RIR
+    brief_fields = ['aggregate_count', 'id', 'name', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'RIR 4',
+            'slug': 'rir-4',
+        },
+        {
+            'name': 'RIR 5',
+            'slug': 'rir-5',
+        },
+        {
+            'name': 'RIR 6',
+            'slug': 'rir-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        rirs = (
+            RIR(name='RIR 1', slug='rir-1'),
+            RIR(name='RIR 2', slug='rir-2'),
+            RIR(name='RIR 3', slug='rir-3'),
         )
-
-    def test_create_rir(self):
-
-        data = {
-            'name': 'Test RIR 4',
-            'slug': 'test-rir-4',
-        }
-
-        url = reverse('ipam-api:rir-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RIR.objects.count(), 4)
-        rir4 = RIR.objects.get(pk=response.data['id'])
-        self.assertEqual(rir4.name, data['name'])
-        self.assertEqual(rir4.slug, data['slug'])
-
-    def test_create_rir_bulk(self):
-
-        data = [
-            {
-                'name': 'Test RIR 4',
-                'slug': 'test-rir-4',
-            },
-            {
-                'name': 'Test RIR 5',
-                'slug': 'test-rir-5',
-            },
-            {
-                'name': 'Test RIR 6',
-                'slug': 'test-rir-6',
-            },
-        ]
-
-        url = reverse('ipam-api:rir-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(RIR.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_rir(self):
-
-        data = {
-            'name': 'Test RIR X',
-            'slug': 'test-rir-x',
-        }
-
-        url = reverse('ipam-api:rir-detail', kwargs={'pk': self.rir1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(RIR.objects.count(), 3)
-        rir1 = RIR.objects.get(pk=response.data['id'])
-        self.assertEqual(rir1.name, data['name'])
-        self.assertEqual(rir1.slug, data['slug'])
-
-    def test_delete_rir(self):
-
-        url = reverse('ipam-api:rir-detail', kwargs={'pk': self.rir1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(RIR.objects.count(), 2)
+        RIR.objects.bulk_create(rirs)
 
 
-class AggregateTest(APITestCase):
+class AggregateTest(APIViewTestCases.APIViewTestCase):
+    model = Aggregate
+    brief_fields = ['family', 'id', 'prefix', 'url']
 
-    def setUp(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        super().setUp()
-
-        self.rir1 = RIR.objects.create(name='Test RIR 1', slug='test-rir-1')
-        self.rir2 = RIR.objects.create(name='Test RIR 2', slug='test-rir-2')
-        self.aggregate1 = Aggregate.objects.create(prefix=IPNetwork('10.0.0.0/8'), rir=self.rir1)
-        self.aggregate2 = Aggregate.objects.create(prefix=IPNetwork('172.16.0.0/12'), rir=self.rir1)
-        self.aggregate3 = Aggregate.objects.create(prefix=IPNetwork('192.168.0.0/16'), rir=self.rir1)
-
-    def test_get_aggregate(self):
-
-        url = reverse('ipam-api:aggregate-detail', kwargs={'pk': self.aggregate1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['family']['value'], 4)
-        self.assertEqual(response.data['prefix'], str(self.aggregate1.prefix))
-
-    def test_list_aggregates(self):
-
-        url = reverse('ipam-api:aggregate-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_aggregates_brief(self):
-
-        url = reverse('ipam-api:aggregate-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['family', 'id', 'prefix', 'url']
+        rirs = (
+            RIR(name='RIR 1', slug='rir-1'),
+            RIR(name='RIR 2', slug='rir-2'),
         )
+        RIR.objects.bulk_create(rirs)
 
-    def test_create_aggregate(self):
-
-        data = {
-            'prefix': '192.0.2.0/24',
-            'rir': self.rir1.pk,
-        }
-
-        url = reverse('ipam-api:aggregate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Aggregate.objects.count(), 4)
-        aggregate4 = Aggregate.objects.get(pk=response.data['id'])
-        self.assertEqual(str(aggregate4.prefix), data['prefix'])
-        self.assertEqual(aggregate4.rir_id, data['rir'])
-
-    def test_create_aggregate_bulk(self):
+        aggregates = (
+            Aggregate(prefix=IPNetwork('10.0.0.0/8'), rir=rirs[0]),
+            Aggregate(prefix=IPNetwork('172.16.0.0/12'), rir=rirs[0]),
+            Aggregate(prefix=IPNetwork('192.168.0.0/16'), rir=rirs[0]),
+        )
+        Aggregate.objects.bulk_create(aggregates)
 
-        data = [
+        cls.create_data = [
             {
                 'prefix': '100.0.0.0/8',
-                'rir': self.rir1.pk,
+                'rir': rirs[1].pk,
             },
             {
                 'prefix': '101.0.0.0/8',
-                'rir': self.rir1.pk,
+                'rir': rirs[1].pk,
             },
             {
                 'prefix': '102.0.0.0/8',
-                'rir': self.rir1.pk,
-            },
-        ]
-
-        url = reverse('ipam-api:aggregate-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Aggregate.objects.count(), 6)
-        self.assertEqual(response.data[0]['prefix'], data[0]['prefix'])
-        self.assertEqual(response.data[1]['prefix'], data[1]['prefix'])
-        self.assertEqual(response.data[2]['prefix'], data[2]['prefix'])
-
-    def test_update_aggregate(self):
-
-        data = {
-            'prefix': '11.0.0.0/8',
-            'rir': self.rir2.pk,
-        }
-
-        url = reverse('ipam-api:aggregate-detail', kwargs={'pk': self.aggregate1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Aggregate.objects.count(), 3)
-        aggregate1 = Aggregate.objects.get(pk=response.data['id'])
-        self.assertEqual(str(aggregate1.prefix), data['prefix'])
-        self.assertEqual(aggregate1.rir_id, data['rir'])
-
-    def test_delete_aggregate(self):
-
-        url = reverse('ipam-api:aggregate-detail', kwargs={'pk': self.aggregate1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Aggregate.objects.count(), 2)
-
-
-class RoleTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.role1 = Role.objects.create(name='Test Role 1', slug='test-role-1')
-        self.role2 = Role.objects.create(name='Test Role 2', slug='test-role-2')
-        self.role3 = Role.objects.create(name='Test Role 3', slug='test-role-3')
-
-    def test_get_role(self):
-
-        url = reverse('ipam-api:role-detail', kwargs={'pk': self.role1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.role1.name)
-
-    def test_list_roles(self):
-
-        url = reverse('ipam-api:role-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_roles_brief(self):
-
-        url = reverse('ipam-api:role-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'prefix_count', 'slug', 'url', 'vlan_count']
-        )
-
-    def test_create_role(self):
-
-        data = {
-            'name': 'Test Role 4',
-            'slug': 'test-role-4',
-        }
-
-        url = reverse('ipam-api:role-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Role.objects.count(), 4)
-        role4 = Role.objects.get(pk=response.data['id'])
-        self.assertEqual(role4.name, data['name'])
-        self.assertEqual(role4.slug, data['slug'])
-
-    def test_create_role_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Role 4',
-                'slug': 'test-role-4',
-            },
-            {
-                'name': 'Test Role 5',
-                'slug': 'test-role-5',
-            },
-            {
-                'name': 'Test Role 6',
-                'slug': 'test-role-6',
+                'rir': rirs[1].pk,
             },
         ]
 
-        url = reverse('ipam-api:role-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Role.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_role(self):
-
-        data = {
-            'name': 'Test Role X',
-            'slug': 'test-role-x',
-        }
-
-        url = reverse('ipam-api:role-detail', kwargs={'pk': self.role1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Role.objects.count(), 3)
-        role1 = Role.objects.get(pk=response.data['id'])
-        self.assertEqual(role1.name, data['name'])
-        self.assertEqual(role1.slug, data['slug'])
-
-    def test_delete_role(self):
-
-        url = reverse('ipam-api:role-detail', kwargs={'pk': self.role1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Role.objects.count(), 2)
-
-
-class PrefixTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.vrf1 = VRF.objects.create(name='Test VRF 1', rd='65000:1')
-        self.vlan1 = VLAN.objects.create(vid=1, name='Test VLAN 1')
-        self.role1 = Role.objects.create(name='Test Role 1', slug='test-role-1')
-        self.prefix1 = Prefix.objects.create(prefix=IPNetwork('192.168.1.0/24'))
-        self.prefix2 = Prefix.objects.create(prefix=IPNetwork('192.168.2.0/24'))
-        self.prefix3 = Prefix.objects.create(prefix=IPNetwork('192.168.3.0/24'))
-
-    def test_get_prefix(self):
-
-        url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefix1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['family']['value'], 4)
-        self.assertEqual(response.data['prefix'], str(self.prefix1.prefix))
-
-    def test_list_prefixes(self):
-
-        url = reverse('ipam-api:prefix-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_prefixes_brief(self):
 
-        url = reverse('ipam-api:prefix-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['family', 'id', 'prefix', 'url']
+class RoleTest(APIViewTestCases.APIViewTestCase):
+    model = Role
+    brief_fields = ['id', 'name', 'prefix_count', 'slug', 'url', 'vlan_count']
+    create_data = [
+        {
+            'name': 'Role 4',
+            'slug': 'role-4',
+        },
+        {
+            'name': 'Role 5',
+            'slug': 'role-5',
+        },
+        {
+            'name': 'Role 6',
+            'slug': 'role-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        roles = (
+            Role(name='Role 1', slug='role-1'),
+            Role(name='Role 2', slug='role-2'),
+            Role(name='Role 3', slug='role-3'),
         )
+        Role.objects.bulk_create(roles)
 
-    def test_create_prefix(self):
 
-        data = {
+class PrefixTest(APIViewTestCases.APIViewTestCase):
+    model = Prefix
+    brief_fields = ['family', 'id', 'prefix', 'url']
+    create_data = [
+        {
             'prefix': '192.168.4.0/24',
-            'site': self.site1.pk,
-            'vrf': self.vrf1.pk,
-            'vlan': self.vlan1.pk,
-            'role': self.role1.pk,
-        }
-
-        url = reverse('ipam-api:prefix-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Prefix.objects.count(), 4)
-        prefix4 = Prefix.objects.get(pk=response.data['id'])
-        self.assertEqual(str(prefix4.prefix), data['prefix'])
-        self.assertEqual(prefix4.site_id, data['site'])
-        self.assertEqual(prefix4.vrf_id, data['vrf'])
-        self.assertEqual(prefix4.vlan_id, data['vlan'])
-        self.assertEqual(prefix4.role_id, data['role'])
-
-    def test_create_prefix_bulk(self):
-
-        data = [
-            {
-                'prefix': '10.0.1.0/24',
-            },
-            {
-                'prefix': '10.0.2.0/24',
-            },
-            {
-                'prefix': '10.0.3.0/24',
-            },
-        ]
-
-        url = reverse('ipam-api:prefix-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Prefix.objects.count(), 6)
-        self.assertEqual(response.data[0]['prefix'], data[0]['prefix'])
-        self.assertEqual(response.data[1]['prefix'], data[1]['prefix'])
-        self.assertEqual(response.data[2]['prefix'], data[2]['prefix'])
-
-    def test_update_prefix(self):
-
-        data = {
-            'prefix': '192.168.99.0/24',
-            'site': self.site1.pk,
-            'vrf': self.vrf1.pk,
-            'vlan': self.vlan1.pk,
-            'role': self.role1.pk,
-        }
-
-        url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefix1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Prefix.objects.count(), 3)
-        prefix1 = Prefix.objects.get(pk=response.data['id'])
-        self.assertEqual(str(prefix1.prefix), data['prefix'])
-        self.assertEqual(prefix1.site_id, data['site'])
-        self.assertEqual(prefix1.vrf_id, data['vrf'])
-        self.assertEqual(prefix1.vlan_id, data['vlan'])
-        self.assertEqual(prefix1.role_id, data['role'])
-
-    def test_delete_prefix(self):
-
-        url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefix1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Prefix.objects.count(), 2)
+        },
+        {
+            'prefix': '192.168.5.0/24',
+        },
+        {
+            'prefix': '192.168.6.0/24',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        prefixes = (
+            Prefix(prefix=IPNetwork('192.168.1.0/24')),
+            Prefix(prefix=IPNetwork('192.168.2.0/24')),
+            Prefix(prefix=IPNetwork('192.168.3.0/24')),
+        )
+        Prefix.objects.bulk_create(prefixes)
 
     def test_list_available_prefixes(self):
-
+        """
+        Test retrieval of all available prefixes within a parent prefix.
+        """
         prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
         Prefix.objects.create(prefix=IPNetwork('192.0.2.64/26'))
         Prefix.objects.create(prefix=IPNetwork('192.0.2.192/27'))
@@ -562,7 +184,9 @@ def test_list_available_prefixes(self):
             self.assertEqual(p['prefix'], available_prefixes[i])
 
     def test_create_single_available_prefix(self):
-
+        """
+        Test retrieval of the first available prefix within a parent prefix.
+        """
         vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
         prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), vrf=vrf, is_pool=True)
         url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
@@ -596,7 +220,9 @@ def test_create_single_available_prefix(self):
         self.assertIn('prefix_length', response.data[0])
 
     def test_create_multiple_available_prefixes(self):
-
+        """
+        Test the creation of available prefixes within a parent prefix.
+        """
         prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), is_pool=True)
         url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
 
@@ -622,7 +248,9 @@ def test_create_multiple_available_prefixes(self):
         self.assertEqual(len(response.data), 4)
 
     def test_list_available_ips(self):
-
+        """
+        Test retrieval of all available IP addresses within a parent prefix.
+        """
         prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
         url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
 
@@ -637,7 +265,9 @@ def test_list_available_ips(self):
         self.assertEqual(len(response.data), 6)  # 8 - 2 because prefix.is_pool = False
 
     def test_create_single_available_ip(self):
-
+        """
+        Test retrieval of the first available IP address within a parent prefix.
+        """
         vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
         prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), vrf=vrf, is_pool=True)
         url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
@@ -658,7 +288,9 @@ def test_create_single_available_ip(self):
         self.assertIn('detail', response.data)
 
     def test_create_multiple_available_ips(self):
-
+        """
+        Test the creation of available IP addresses within a parent prefix.
+        """
         prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
         url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
 
@@ -679,317 +311,107 @@ def test_create_multiple_available_ips(self):
         self.assertEqual(len(response.data), 8)
 
 
-class IPAddressTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.vrf1 = VRF.objects.create(name='Test VRF 1', rd='65000:1')
-        self.ipaddress1 = IPAddress.objects.create(address=IPNetwork('192.168.0.1/24'))
-        self.ipaddress2 = IPAddress.objects.create(address=IPNetwork('192.168.0.2/24'))
-        self.ipaddress3 = IPAddress.objects.create(address=IPNetwork('192.168.0.3/24'))
-
-    def test_get_ipaddress(self):
-
-        url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': self.ipaddress1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['family']['value'], 4)
-        self.assertEqual(response.data['address'], str(self.ipaddress1.address))
-
-    def test_list_ipaddresss(self):
-
-        url = reverse('ipam-api:ipaddress-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_ipaddresses_brief(self):
-
-        url = reverse('ipam-api:ipaddress-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['address', 'family', 'id', 'url']
-        )
-
-    def test_create_ipaddress(self):
-
-        data = {
+class IPAddressTest(APIViewTestCases.APIViewTestCase):
+    model = IPAddress
+    brief_fields = ['address', 'family', 'id', 'url']
+    create_data = [
+        {
             'address': '192.168.0.4/24',
-            'vrf': self.vrf1.pk,
-        }
-
-        url = reverse('ipam-api:ipaddress-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(IPAddress.objects.count(), 4)
-        ipaddress4 = IPAddress.objects.get(pk=response.data['id'])
-        self.assertEqual(str(ipaddress4.address), data['address'])
-        self.assertEqual(ipaddress4.vrf_id, data['vrf'])
-
-    def test_create_ipaddress_bulk(self):
-
-        data = [
-            {
-                'address': '192.168.0.4/24',
-            },
-            {
-                'address': '192.168.0.5/24',
-            },
-            {
-                'address': '192.168.0.6/24',
-            },
-        ]
-
-        url = reverse('ipam-api:ipaddress-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(IPAddress.objects.count(), 6)
-        self.assertEqual(response.data[0]['address'], data[0]['address'])
-        self.assertEqual(response.data[1]['address'], data[1]['address'])
-        self.assertEqual(response.data[2]['address'], data[2]['address'])
-
-    def test_update_ipaddress(self):
-
-        data = {
-            'address': '192.168.0.99/24',
-            'vrf': self.vrf1.pk,
-        }
-
-        url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': self.ipaddress1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(IPAddress.objects.count(), 3)
-        ipaddress1 = IPAddress.objects.get(pk=response.data['id'])
-        self.assertEqual(str(ipaddress1.address), data['address'])
-        self.assertEqual(ipaddress1.vrf_id, data['vrf'])
-
-    def test_delete_ipaddress(self):
-
-        url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': self.ipaddress1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(IPAddress.objects.count(), 2)
-
-
-class VLANGroupTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        self.vlangroup1 = VLANGroup.objects.create(name='Test VLAN Group 1', slug='test-vlan-group-1')
-        self.vlangroup2 = VLANGroup.objects.create(name='Test VLAN Group 2', slug='test-vlan-group-2')
-        self.vlangroup3 = VLANGroup.objects.create(name='Test VLAN Group 3', slug='test-vlan-group-3')
-
-    def test_get_vlangroup(self):
-
-        url = reverse('ipam-api:vlangroup-detail', kwargs={'pk': self.vlangroup1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.vlangroup1.name)
-
-    def test_list_vlangroups(self):
-
-        url = reverse('ipam-api:vlangroup-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_vlangroups_brief(self):
-
-        url = reverse('ipam-api:vlangroup-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'slug', 'url', 'vlan_count']
+        },
+        {
+            'address': '192.168.0.5/24',
+        },
+        {
+            'address': '192.168.0.6/24',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        ip_addresses = (
+            IPAddress(address=IPNetwork('192.168.0.1/24')),
+            IPAddress(address=IPNetwork('192.168.0.2/24')),
+            IPAddress(address=IPNetwork('192.168.0.3/24')),
         )
-
-    def test_create_vlangroup(self):
-
-        data = {
-            'name': 'Test VLAN Group 4',
-            'slug': 'test-vlan-group-4',
-            'site': self.site1.pk,
-        }
-
-        url = reverse('ipam-api:vlangroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VLANGroup.objects.count(), 4)
-        vlangroup4 = VLANGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(vlangroup4.name, data['name'])
-        self.assertEqual(vlangroup4.slug, data['slug'])
-
-    def test_create_vlangroup_bulk(self):
-
-        data = [
-            {
-                'name': 'Test VLAN Group 4',
-                'slug': 'test-vlan-group-4',
-            },
-            {
-                'name': 'Test VLAN Group 5',
-                'slug': 'test-vlan-group-5',
-            },
-            {
-                'name': 'Test VLAN Group 6',
-                'slug': 'test-vlan-group-6',
-            },
-        ]
-
-        url = reverse('ipam-api:vlangroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VLANGroup.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_vlangroup(self):
-
-        data = {
-            'name': 'Test VLAN Group X',
-            'slug': 'test-vlan-group-x',
-        }
-
-        url = reverse('ipam-api:vlangroup-detail', kwargs={'pk': self.vlangroup1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(VLANGroup.objects.count(), 3)
-        vlangroup1 = VLANGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(vlangroup1.name, data['name'])
-        self.assertEqual(vlangroup1.slug, data['slug'])
-
-    def test_delete_vlangroup(self):
-
-        url = reverse('ipam-api:vlangroup-detail', kwargs={'pk': self.vlangroup1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(VLANGroup.objects.count(), 2)
-
-
-class VLANTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.group1 = VLANGroup.objects.create(name='Test VLAN Group 1', slug='test-vlan-group-1')
-        self.vlan1 = VLAN.objects.create(vid=1, name='Test VLAN 1')
-        self.vlan2 = VLAN.objects.create(vid=2, name='Test VLAN 2')
-        self.vlan3 = VLAN.objects.create(vid=3, name='Test VLAN 3')
-        self.prefix1 = Prefix.objects.create(prefix=IPNetwork('192.168.1.0/24'))
-
-    def test_get_vlan(self):
-
-        url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.vlan1.name)
-
-    def test_list_vlans(self):
-
-        url = reverse('ipam-api:vlan-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_vlans_brief(self):
-
-        url = reverse('ipam-api:vlan-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['display_name', 'id', 'name', 'url', 'vid']
+        IPAddress.objects.bulk_create(ip_addresses)
+
+
+class VLANGroupTest(APIViewTestCases.APIViewTestCase):
+    model = VLANGroup
+    brief_fields = ['id', 'name', 'slug', 'url', 'vlan_count']
+    create_data = [
+        {
+            'name': 'VLAN Group 4',
+            'slug': 'vlan-group-4',
+        },
+        {
+            'name': 'VLAN Group 5',
+            'slug': 'vlan-group-5',
+        },
+        {
+            'name': 'VLAN Group 6',
+            'slug': 'vlan-group-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        vlan_groups = (
+            VLANGroup(name='VLAN Group 1', slug='vlan-group-1'),
+            VLANGroup(name='VLAN Group 2', slug='vlan-group-2'),
+            VLANGroup(name='VLAN Group 3', slug='vlan-group-3'),
         )
+        VLANGroup.objects.bulk_create(vlan_groups)
 
-    def test_create_vlan(self):
 
-        data = {
-            'vid': 4,
-            'name': 'Test VLAN 4',
-            'group': self.group1.pk,
-        }
+class VLANTest(APIViewTestCases.APIViewTestCase):
+    model = VLAN
+    brief_fields = ['display_name', 'id', 'name', 'url', 'vid']
 
-        url = reverse('ipam-api:vlan-list')
-        response = self.client.post(url, data, format='json', **self.header)
+    @classmethod
+    def setUpTestData(cls):
 
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VLAN.objects.count(), 4)
-        vlan4 = VLAN.objects.get(pk=response.data['id'])
-        self.assertEqual(vlan4.vid, data['vid'])
-        self.assertEqual(vlan4.name, data['name'])
+        vlan_groups = (
+            VLANGroup(name='VLAN Group 1', slug='vlan-group-1'),
+            VLANGroup(name='VLAN Group 2', slug='vlan-group-2'),
+        )
+        VLANGroup.objects.bulk_create(vlan_groups)
 
-    def test_create_vlan_bulk(self):
+        vlans = (
+            VLAN(name='VLAN 1', vid=1, group=vlan_groups[0]),
+            VLAN(name='VLAN 2', vid=2, group=vlan_groups[0]),
+            VLAN(name='VLAN 3', vid=3, group=vlan_groups[0]),
+        )
+        VLAN.objects.bulk_create(vlans)
 
-        data = [
+        cls.create_data = [
             {
                 'vid': 4,
-                'name': 'Test VLAN 4',
+                'name': 'VLAN 4',
+                'group': vlan_groups[1].pk,
             },
             {
                 'vid': 5,
-                'name': 'Test VLAN 5',
+                'name': 'VLAN 5',
+                'group': vlan_groups[1].pk,
             },
             {
                 'vid': 6,
-                'name': 'Test VLAN 6',
+                'name': 'VLAN 6',
+                'group': vlan_groups[1].pk,
             },
         ]
 
-        url = reverse('ipam-api:vlan-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VLAN.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_vlan(self):
-
-        data = {
-            'vid': 99,
-            'name': 'Test VLAN X',
-        }
-
-        url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(VLAN.objects.count(), 3)
-        vlan1 = VLAN.objects.get(pk=response.data['id'])
-        self.assertEqual(vlan1.vid, data['vid'])
-        self.assertEqual(vlan1.name, data['name'])
-
-    def test_delete_vlan(self):
-
-        url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(VLAN.objects.count(), 2)
-
     def test_delete_vlan_with_prefix(self):
-        self.prefix1.vlan = self.vlan1
-        self.prefix1.save()
+        """
+        Attempt and fail to delete a VLAN with a Prefix assigned to it.
+        """
+        vlan = VLAN.objects.first()
+        Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), vlan=vlan)
 
-        url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
+        url = reverse('ipam-api:vlan-detail', kwargs={'pk': vlan.pk})
         with disable_warnings('django.request'):
             response = self.client.delete(url, **self.header)
 
@@ -1000,125 +422,47 @@ def test_delete_vlan_with_prefix(self):
         self.assertTrue(content['detail'].startswith('Unable to delete object.'))
 
 
-class ServiceTest(APITestCase):
+class ServiceTest(APIViewTestCases.APIViewTestCase):
+    model = Service
+    brief_fields = ['id', 'name', 'port', 'protocol', 'url']
 
-    def setUp(self):
+    @classmethod
+    def setUpTestData(cls):
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
+        devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
 
-        super().setUp()
-
-        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
-        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
-        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device Type 1')
-        devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1')
-        self.device1 = Device.objects.create(
-            name='Test Device 1', site=site, device_type=devicetype, device_role=devicerole
-        )
-        self.device2 = Device.objects.create(
-            name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole
-        )
-        self.service1 = Service.objects.create(
-            device=self.device1, name='Test Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=1
+        devices = (
+            Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole),
+            Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole),
         )
-        self.service1 = Service.objects.create(
-            device=self.device1, name='Test Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=2
-        )
-        self.service1 = Service.objects.create(
-            device=self.device1, name='Test Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=3
-        )
-
-    def test_get_service(self):
+        Device.objects.bulk_create(devices)
 
-        url = reverse('ipam-api:service-detail', kwargs={'pk': self.service1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.service1.name)
-
-    def test_list_services(self):
-
-        url = reverse('ipam-api:service-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_create_service(self):
-
-        data = {
-            'device': self.device1.pk,
-            'name': 'Test Service 4',
-            'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
-            'port': 4,
-            'tags': ['Foo', 'Bar'],
-        }
-
-        url = reverse('ipam-api:service-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Service.objects.count(), 4)
-        service4 = Service.objects.get(pk=response.data['id'])
-        self.assertEqual(service4.device_id, data['device'])
-        self.assertEqual(service4.name, data['name'])
-        self.assertEqual(service4.protocol, data['protocol'])
-        self.assertEqual(service4.port, data['port'])
-        tags = [tag.name for tag in service4.tags.all()]
-        self.assertEqual(sorted(tags), sorted(data['tags']))
-
-    def test_create_service_bulk(self):
+        services = (
+            Service(device=devices[0], name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=1),
+            Service(device=devices[0], name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=2),
+            Service(device=devices[0], name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=3),
+        )
+        Service.objects.bulk_create(services)
 
-        data = [
+        cls.create_data = [
             {
-                'device': self.device1.pk,
-                'name': 'Test Service 4',
+                'device': devices[1].pk,
+                'name': 'Service 4',
                 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
                 'port': 4,
             },
             {
-                'device': self.device1.pk,
-                'name': 'Test Service 5',
+                'device': devices[1].pk,
+                'name': 'Service 5',
                 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
                 'port': 5,
             },
             {
-                'device': self.device1.pk,
-                'name': 'Test Service 6',
+                'device': devices[1].pk,
+                'name': 'Service 6',
                 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
                 'port': 6,
             },
         ]
-
-        url = reverse('ipam-api:service-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Service.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_service(self):
-
-        data = {
-            'device': self.device2.pk,
-            'name': 'Test Service X',
-            'protocol': ServiceProtocolChoices.PROTOCOL_UDP,
-            'port': 99,
-        }
-
-        url = reverse('ipam-api:service-detail', kwargs={'pk': self.service1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Service.objects.count(), 3)
-        service1 = Service.objects.get(pk=response.data['id'])
-        self.assertEqual(service1.device_id, data['device'])
-        self.assertEqual(service1.name, data['name'])
-        self.assertEqual(service1.protocol, data['protocol'])
-        self.assertEqual(service1.port, data['port'])
-
-    def test_delete_service(self):
-
-        url = reverse('ipam-api:service-detail', kwargs={'pk': self.service1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Service.objects.count(), 2)
diff --git a/netbox/secrets/tests/test_api.py b/netbox/secrets/tests/test_api.py
--- a/netbox/secrets/tests/test_api.py
+++ b/netbox/secrets/tests/test_api.py
@@ -6,7 +6,7 @@
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from users.models import Token
-from utilities.testing import APITestCase, create_test_user
+from utilities.testing import APITestCase, APIViewTestCases, create_test_user
 from .constants import PRIVATE_KEY, PUBLIC_KEY
 
 
@@ -20,107 +20,36 @@ def test_root(self):
         self.assertEqual(response.status_code, 200)
 
 
-class SecretRoleTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.secretrole1 = SecretRole.objects.create(name='Test Secret Role 1', slug='test-secret-role-1')
-        self.secretrole2 = SecretRole.objects.create(name='Test Secret Role 2', slug='test-secret-role-2')
-        self.secretrole3 = SecretRole.objects.create(name='Test Secret Role 3', slug='test-secret-role-3')
-
-    def test_get_secretrole(self):
-
-        url = reverse('secrets-api:secretrole-detail', kwargs={'pk': self.secretrole1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.secretrole1.name)
-
-    def test_list_secretroles(self):
-
-        url = reverse('secrets-api:secretrole-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_secretroles_brief(self):
-
-        url = reverse('secrets-api:secretrole-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'secret_count', 'slug', 'url']
+class SecretRoleTest(APIViewTestCases.APIViewTestCase):
+    model = SecretRole
+    brief_fields = ['id', 'name', 'secret_count', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Secret Role 4',
+            'slug': 'secret-role-4',
+        },
+        {
+            'name': 'Secret Role 5',
+            'slug': 'secret-role-5',
+        },
+        {
+            'name': 'Secret Role 6',
+            'slug': 'secret-role-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        secret_roles = (
+            SecretRole(name='Secret Role 1', slug='secret-role-1'),
+            SecretRole(name='Secret Role 2', slug='secret-role-2'),
+            SecretRole(name='Secret Role 3', slug='secret-role-3'),
         )
-
-    def test_create_secretrole(self):
-
-        data = {
-            'name': 'Test Secret Role 4',
-            'slug': 'test-secret-role-4',
-        }
-
-        url = reverse('secrets-api:secretrole-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(SecretRole.objects.count(), 4)
-        secretrole4 = SecretRole.objects.get(pk=response.data['id'])
-        self.assertEqual(secretrole4.name, data['name'])
-        self.assertEqual(secretrole4.slug, data['slug'])
-
-    def test_create_secretrole_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Secret Role 4',
-                'slug': 'test-secret-role-4',
-            },
-            {
-                'name': 'Test Secret Role 5',
-                'slug': 'test-secret-role-5',
-            },
-            {
-                'name': 'Test Secret Role 6',
-                'slug': 'test-secret-role-6',
-            },
-        ]
-
-        url = reverse('secrets-api:secretrole-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(SecretRole.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_secretrole(self):
-
-        data = {
-            'name': 'Test SecretRole X',
-            'slug': 'test-secretrole-x',
-        }
-
-        url = reverse('secrets-api:secretrole-detail', kwargs={'pk': self.secretrole1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(SecretRole.objects.count(), 3)
-        secretrole1 = SecretRole.objects.get(pk=response.data['id'])
-        self.assertEqual(secretrole1.name, data['name'])
-        self.assertEqual(secretrole1.slug, data['slug'])
-
-    def test_delete_secretrole(self):
-
-        url = reverse('secrets-api:secretrole-detail', kwargs={'pk': self.secretrole1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(SecretRole.objects.count(), 2)
+        SecretRole.objects.bulk_create(secret_roles)
 
 
+# TODO: Standardize SecretTest
 class SecretTest(APITestCase):
 
     def setUp(self):
diff --git a/netbox/tenancy/tests/test_api.py b/netbox/tenancy/tests/test_api.py
--- a/netbox/tenancy/tests/test_api.py
+++ b/netbox/tenancy/tests/test_api.py
@@ -1,8 +1,7 @@
 from django.urls import reverse
-from rest_framework import status
 
 from tenancy.models import Tenant, TenantGroup
-from utilities.testing import APITestCase
+from utilities.testing import APITestCase, APIViewTestCases
 
 
 class AppTest(APITestCase):
@@ -15,235 +14,74 @@ def test_root(self):
         self.assertEqual(response.status_code, 200)
 
 
-class TenantGroupTest(APITestCase):
+class TenantGroupTest(APIViewTestCases.APIViewTestCase):
+    model = TenantGroup
+    brief_fields = ['id', 'name', 'slug', 'tenant_count', 'url']
 
-    def setUp(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        super().setUp()
-
-        self.parent_tenant_groups = (
-            TenantGroup(name='Parent Tenant Group 1', slug='parent-tenant-group-1'),
-            TenantGroup(name='Parent Tenant Group 2', slug='parent-tenant-group-2'),
-        )
-        for tenantgroup in self.parent_tenant_groups:
-            tenantgroup.save()
-
-        self.tenant_groups = (
-            TenantGroup(name='Tenant Group 1', slug='tenant-group-1', parent=self.parent_tenant_groups[0]),
-            TenantGroup(name='Tenant Group 2', slug='tenant-group-2', parent=self.parent_tenant_groups[0]),
-            TenantGroup(name='Tenant Group 3', slug='tenant-group-3', parent=self.parent_tenant_groups[0]),
-        )
-        for tenantgroup in self.tenant_groups:
-            tenantgroup.save()
-
-    def test_get_tenantgroup(self):
-
-        url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenant_groups[0].pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.tenant_groups[0].name)
-
-    def test_list_tenantgroups(self):
-
-        url = reverse('tenancy-api:tenantgroup-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 5)
-
-    def test_list_tenantgroups_brief(self):
-
-        url = reverse('tenancy-api:tenantgroup-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'slug', 'tenant_count', 'url']
+        parent_tenant_groups = (
+            TenantGroup.objects.create(name='Parent Tenant Group 1', slug='parent-tenant-group-1'),
+            TenantGroup.objects.create(name='Parent Tenant Group 2', slug='parent-tenant-group-2'),
         )
 
-    def test_create_tenantgroup(self):
+        TenantGroup.objects.create(name='Tenant Group 1', slug='tenant-group-1', parent=parent_tenant_groups[0])
+        TenantGroup.objects.create(name='Tenant Group 2', slug='tenant-group-2', parent=parent_tenant_groups[0])
+        TenantGroup.objects.create(name='Tenant Group 3', slug='tenant-group-3', parent=parent_tenant_groups[0])
 
-        data = {
-            'name': 'Tenant Group 4',
-            'slug': 'tenant-group-4',
-            'parent': self.parent_tenant_groups[0].pk,
-        }
-
-        url = reverse('tenancy-api:tenantgroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(TenantGroup.objects.count(), 6)
-        tenantgroup4 = TenantGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(tenantgroup4.name, data['name'])
-        self.assertEqual(tenantgroup4.slug, data['slug'])
-        self.assertEqual(tenantgroup4.parent_id, data['parent'])
-
-    def test_create_tenantgroup_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
                 'name': 'Tenant Group 4',
                 'slug': 'tenant-group-4',
-                'parent': self.parent_tenant_groups[0].pk,
+                'parent': parent_tenant_groups[1].pk,
             },
             {
                 'name': 'Tenant Group 5',
                 'slug': 'tenant-group-5',
-                'parent': self.parent_tenant_groups[0].pk,
+                'parent': parent_tenant_groups[1].pk,
             },
             {
                 'name': 'Tenant Group 6',
                 'slug': 'tenant-group-6',
-                'parent': self.parent_tenant_groups[0].pk,
+                'parent': parent_tenant_groups[1].pk,
             },
         ]
 
-        url = reverse('tenancy-api:tenantgroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(TenantGroup.objects.count(), 8)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_tenantgroup(self):
-
-        data = {
-            'name': 'Tenant Group X',
-            'slug': 'tenant-group-x',
-            'parent': self.parent_tenant_groups[1].pk,
-        }
-
-        url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenant_groups[0].pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(TenantGroup.objects.count(), 5)
-        tenantgroup1 = TenantGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(tenantgroup1.name, data['name'])
-        self.assertEqual(tenantgroup1.slug, data['slug'])
-        self.assertEqual(tenantgroup1.parent_id, data['parent'])
-
-    def test_delete_tenantgroup(self):
-
-        url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenant_groups[0].pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(TenantGroup.objects.count(), 4)
-
 
-class TenantTest(APITestCase):
+class TenantTest(APIViewTestCases.APIViewTestCase):
+    model = Tenant
+    brief_fields = ['id', 'name', 'slug', 'url']
 
-    def setUp(self):
+    @classmethod
+    def setUpTestData(cls):
 
-        super().setUp()
-
-        self.tenant_groups = (
-            TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
-            TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
+        tenant_groups = (
+            TenantGroup.objects.create(name='Tenant Group 1', slug='tenant-group-1'),
+            TenantGroup.objects.create(name='Tenant Group 2', slug='tenant-group-2'),
         )
-        for tenantgroup in self.tenant_groups:
-            tenantgroup.save()
 
-        self.tenants = (
-            Tenant(name='Test Tenant 1', slug='test-tenant-1', group=self.tenant_groups[0]),
-            Tenant(name='Test Tenant 2', slug='test-tenant-2', group=self.tenant_groups[0]),
-            Tenant(name='Test Tenant 3', slug='test-tenant-3', group=self.tenant_groups[0]),
+        tenants = (
+            Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
+            Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[0]),
+            Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[0]),
         )
-        Tenant.objects.bulk_create(self.tenants)
-
-    def test_get_tenant(self):
-
-        url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenants[0].pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.tenants[0].name)
-
-    def test_list_tenants(self):
-
-        url = reverse('tenancy-api:tenant-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_tenants_brief(self):
-
-        url = reverse('tenancy-api:tenant-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'slug', 'url']
-        )
-
-    def test_create_tenant(self):
-
-        data = {
-            'name': 'Test Tenant 4',
-            'slug': 'test-tenant-4',
-            'group': self.tenant_groups[0].pk,
-        }
+        Tenant.objects.bulk_create(tenants)
 
-        url = reverse('tenancy-api:tenant-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Tenant.objects.count(), 4)
-        tenant4 = Tenant.objects.get(pk=response.data['id'])
-        self.assertEqual(tenant4.name, data['name'])
-        self.assertEqual(tenant4.slug, data['slug'])
-        self.assertEqual(tenant4.group_id, data['group'])
-
-    def test_create_tenant_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Tenant 4',
-                'slug': 'test-tenant-4',
+                'name': 'Tenant 4',
+                'slug': 'tenant-4',
+                'group': tenant_groups[1].pk,
             },
             {
-                'name': 'Test Tenant 5',
-                'slug': 'test-tenant-5',
+                'name': 'Tenant 5',
+                'slug': 'tenant-5',
+                'group': tenant_groups[1].pk,
             },
             {
-                'name': 'Test Tenant 6',
-                'slug': 'test-tenant-6',
+                'name': 'Tenant 6',
+                'slug': 'tenant-6',
+                'group': tenant_groups[1].pk,
             },
         ]
-
-        url = reverse('tenancy-api:tenant-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Tenant.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_tenant(self):
-
-        data = {
-            'name': 'Test Tenant X',
-            'slug': 'test-tenant-x',
-            'group': self.tenant_groups[1].pk,
-        }
-
-        url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenants[0].pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Tenant.objects.count(), 3)
-        tenant1 = Tenant.objects.get(pk=response.data['id'])
-        self.assertEqual(tenant1.name, data['name'])
-        self.assertEqual(tenant1.slug, data['slug'])
-        self.assertEqual(tenant1.group_id, data['group'])
-
-    def test_delete_tenant(self):
-
-        url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenants[0].pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Tenant.objects.count(), 2)
diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py
--- a/netbox/utilities/testing/testcases.py
+++ b/netbox/utilities/testing/testcases.py
@@ -1,8 +1,11 @@
 from django.contrib.auth.models import Permission, User
+from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist
 from django.forms.models import model_to_dict
 from django.test import Client, TestCase as _TestCase, override_settings
 from django.urls import reverse, NoReverseMatch
+from netaddr import IPNetwork
+from rest_framework import status
 from rest_framework.test import APIClient
 
 from users.models import Token
@@ -57,6 +60,49 @@ def assertHttpStatus(self, response, expected_status):
             expected_status, response.status_code, getattr(response, 'data', 'No data')
         ))
 
+    def assertInstanceEqual(self, instance, data, api=False):
+        """
+        Compare a model instance to a dictionary, checking that its attribute values match those specified
+        in the dictionary.
+
+        :instance: Python object instance
+        :data: Dictionary of test data used to define the instance
+        :api: Set to True is the data is a JSON representation of the instance
+        """
+        model_dict = model_to_dict(instance, fields=data.keys())
+
+        for key, value in list(model_dict.items()):
+
+            # TODO: Differentiate between tags assigned to the instance and a M2M field for tags (ex: ConfigContext)
+            if key == 'tags':
+                model_dict[key] = ','.join(sorted([tag.name for tag in value]))
+
+            # Convert ManyToManyField to list of instance PKs
+            elif model_dict[key] and type(value) in (list, tuple) and hasattr(value[0], 'pk'):
+                model_dict[key] = [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
+                if type(value) is IPNetwork:
+                    model_dict[key] = str(value)
+
+        # Omit any dictionary keys which are not instance attributes
+        relevant_data = {
+            k: v for k, v in data.items() if hasattr(instance, k)
+        }
+
+        self.assertDictEqual(model_dict, relevant_data)
+
+
+#
+# UI Tests
+#
 
 class ModelViewTestCase(TestCase):
     """
@@ -104,42 +150,6 @@ def _get_url(self, action, instance=None):
         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
-        in the dictionary.
-        """
-        model_dict = model_to_dict(instance, fields=data.keys())
-
-        for key in list(model_dict.keys()):
-
-            # TODO: Differentiate between tags assigned to the instance and a M2M field for tags (ex: ConfigContext)
-            if key == 'tags':
-                model_dict[key] = ','.join(sorted([tag.name for tag in model_dict['tags']]))
-
-            # Convert ManyToManyField to list of instance PKs
-            elif model_dict[key] and type(model_dict[key]) in (list, tuple) and hasattr(model_dict[key][0], 'pk'):
-                model_dict[key] = [obj.pk for obj in model_dict[key]]
-
-        # Omit any dictionary keys which are not instance attributes
-        relevant_data = {
-            k: v for k, v in data.items() if hasattr(instance, k)
-        }
-
-        self.assertDictEqual(model_dict, relevant_data)
-
-
-class APITestCase(TestCase):
-    client_class = APIClient
-
-    def setUp(self):
-        """
-        Create a superuser and token for API calls.
-        """
-        self.user = User.objects.create(username='testuser', is_superuser=True)
-        self.token = Token.objects.create(user=self.user)
-        self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)}
-
 
 class ViewTestCases:
     """
@@ -165,7 +175,7 @@ def test_get_object(self):
             self.assertHttpStatus(response, 200)
 
         @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
-        def test_list_objects_anonymous(self):
+        def test_get_object_anonymous(self):
             # Make the request as an unauthenticated user
             self.client.logout()
             response = self.client.get(self.model.objects.first().get_absolute_url())
@@ -488,3 +498,129 @@ class DeviceComponentViewTestCase(
         TestCase suitable for testing device component models (ConsolePorts, Interfaces, etc.)
         """
         maxDiff = None
+
+
+#
+# REST API Tests
+#
+
+class APITestCase(TestCase):
+    client_class = APIClient
+    model = None
+
+    def setUp(self):
+        """
+        Create a superuser and token for API calls.
+        """
+        self.user = User.objects.create(username='testuser', is_superuser=True)
+        self.token = Token.objects.create(user=self.user)
+        self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)}
+
+    def _get_detail_url(self, instance):
+        viewname = f'{instance._meta.app_label}-api:{instance._meta.model_name}-detail'
+        return reverse(viewname, kwargs={'pk': instance.pk})
+
+    def _get_list_url(self):
+        viewname = f'{self.model._meta.app_label}-api:{self.model._meta.model_name}-list'
+        return reverse(viewname)
+
+
+class APIViewTestCases:
+
+    class GetObjectViewTestCase(APITestCase):
+
+        def test_get_object(self):
+            """
+            GET a single object identified by its numeric ID.
+            """
+            instance = self.model.objects.first()
+            url = self._get_detail_url(instance)
+            response = self.client.get(url, **self.header)
+
+            self.assertEqual(response.data['id'], instance.pk)
+
+    class ListObjectsViewTestCase(APITestCase):
+        brief_fields = []
+
+        def test_list_objects(self):
+            """
+            GET a list of objects.
+            """
+            url = self._get_list_url()
+            response = self.client.get(url, **self.header)
+
+            self.assertEqual(len(response.data['results']), self.model.objects.count())
+
+        def test_list_objects_brief(self):
+            """
+            GET a list of objects using the "brief" parameter.
+            """
+            url = f'{self._get_list_url()}?brief=1'
+            response = self.client.get(url, **self.header)
+
+            self.assertEqual(len(response.data['results']), self.model.objects.count())
+            self.assertEqual(sorted(response.data['results'][0]), self.brief_fields)
+
+    class CreateObjectViewTestCase(APITestCase):
+        create_data = []
+
+        def test_create_object(self):
+            """
+            POST a single object.
+            """
+            initial_count = self.model.objects.count()
+            url = self._get_list_url()
+            response = self.client.post(url, self.create_data[0], format='json', **self.header)
+
+            self.assertHttpStatus(response, status.HTTP_201_CREATED)
+            self.assertEqual(self.model.objects.count(), initial_count + 1)
+            self.assertInstanceEqual(self.model.objects.get(pk=response.data['id']), self.create_data[0], api=True)
+
+        def test_bulk_create_object(self):
+            """
+            POST a set of objects in a single request.
+            """
+            initial_count = self.model.objects.count()
+            url = self._get_list_url()
+            response = self.client.post(url, self.create_data, format='json', **self.header)
+
+            self.assertHttpStatus(response, status.HTTP_201_CREATED)
+            self.assertEqual(self.model.objects.count(), initial_count + len(self.create_data))
+
+    class UpdateObjectViewTestCase(APITestCase):
+        update_data = {}
+
+        def test_update_object(self):
+            """
+            PATCH a single object identified by its numeric ID.
+            """
+            instance = self.model.objects.first()
+            url = self._get_detail_url(instance)
+            update_data = self.update_data or getattr(self, 'create_data')[0]
+            response = self.client.patch(url, update_data, format='json', **self.header)
+
+            self.assertHttpStatus(response, status.HTTP_200_OK)
+            instance.refresh_from_db()
+            self.assertInstanceEqual(instance, self.update_data, api=True)
+
+    class DeleteObjectViewTestCase(APITestCase):
+
+        def test_delete_object(self):
+            """
+            DELETE a single object identified by its numeric ID.
+            """
+            instance = self.model.objects.first()
+            url = self._get_detail_url(instance)
+            response = self.client.delete(url, **self.header)
+
+            self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
+            self.assertFalse(self.model.objects.filter(pk=instance.pk).exists())
+
+    class APIViewTestCase(
+        GetObjectViewTestCase,
+        ListObjectsViewTestCase,
+        CreateObjectViewTestCase,
+        UpdateObjectViewTestCase,
+        DeleteObjectViewTestCase
+    ):
+        pass
diff --git a/netbox/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py
--- a/netbox/virtualization/tests/test_api.py
+++ b/netbox/virtualization/tests/test_api.py
@@ -1,11 +1,10 @@
 from django.urls import reverse
-from netaddr import IPNetwork
 from rest_framework import status
 
 from dcim.choices import InterfaceModeChoices
 from dcim.models import Interface
-from ipam.models import IPAddress, VLAN
-from utilities.testing import APITestCase, disable_warnings
+from ipam.models import VLAN
+from utilities.testing import APITestCase, APIViewTestCases
 from virtualization.choices import *
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
@@ -20,487 +19,181 @@ def test_root(self):
         self.assertEqual(response.status_code, 200)
 
 
-class ClusterTypeTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.clustertype1 = ClusterType.objects.create(name='Test Cluster Type 1', slug='test-cluster-type-1')
-        self.clustertype2 = ClusterType.objects.create(name='Test Cluster Type 2', slug='test-cluster-type-2')
-        self.clustertype3 = ClusterType.objects.create(name='Test Cluster Type 3', slug='test-cluster-type-3')
-
-    def test_get_clustertype(self):
-
-        url = reverse('virtualization-api:clustertype-detail', kwargs={'pk': self.clustertype1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.clustertype1.name)
-
-    def test_list_clustertypes(self):
-
-        url = reverse('virtualization-api:clustertype-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_clustertypes_brief(self):
-
-        url = reverse('virtualization-api:clustertype-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cluster_count', 'id', 'name', 'slug', 'url']
+class ClusterTypeTest(APIViewTestCases.APIViewTestCase):
+    model = ClusterType
+    brief_fields = ['cluster_count', 'id', 'name', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Cluster Type 4',
+            'slug': 'cluster-type-4',
+        },
+        {
+            'name': 'Cluster Type 5',
+            'slug': 'cluster-type-5',
+        },
+        {
+            'name': 'Cluster Type 6',
+            'slug': 'cluster-type-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        cluster_types = (
+            ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
+            ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
+            ClusterType(name='Cluster Type 3', slug='cluster-type-3'),
         )
-
-    def test_create_clustertype(self):
-
-        data = {
-            'name': 'Test Cluster Type 4',
-            'slug': 'test-cluster-type-4',
-        }
-
-        url = reverse('virtualization-api:clustertype-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ClusterType.objects.count(), 4)
-        clustertype4 = ClusterType.objects.get(pk=response.data['id'])
-        self.assertEqual(clustertype4.name, data['name'])
-        self.assertEqual(clustertype4.slug, data['slug'])
-
-    def test_create_clustertype_bulk(self):
-
-        data = [
-            {
-                'name': 'Test Cluster Type 4',
-                'slug': 'test-cluster-type-4',
-            },
-            {
-                'name': 'Test Cluster Type 5',
-                'slug': 'test-cluster-type-5',
-            },
-            {
-                'name': 'Test Cluster Type 6',
-                'slug': 'test-cluster-type-6',
-            },
-        ]
-
-        url = reverse('virtualization-api:clustertype-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ClusterType.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_clustertype(self):
-
-        data = {
-            'name': 'Test Cluster Type X',
-            'slug': 'test-cluster-type-x',
-        }
-
-        url = reverse('virtualization-api:clustertype-detail', kwargs={'pk': self.clustertype1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ClusterType.objects.count(), 3)
-        clustertype1 = ClusterType.objects.get(pk=response.data['id'])
-        self.assertEqual(clustertype1.name, data['name'])
-        self.assertEqual(clustertype1.slug, data['slug'])
-
-    def test_delete_clustertype(self):
-
-        url = reverse('virtualization-api:clustertype-detail', kwargs={'pk': self.clustertype1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ClusterType.objects.count(), 2)
-
-
-class ClusterGroupTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        self.clustergroup1 = ClusterGroup.objects.create(name='Test Cluster Group 1', slug='test-cluster-group-1')
-        self.clustergroup2 = ClusterGroup.objects.create(name='Test Cluster Group 2', slug='test-cluster-group-2')
-        self.clustergroup3 = ClusterGroup.objects.create(name='Test Cluster Group 3', slug='test-cluster-group-3')
-
-    def test_get_clustergroup(self):
-
-        url = reverse('virtualization-api:clustergroup-detail', kwargs={'pk': self.clustergroup1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.clustergroup1.name)
-
-    def test_list_clustergroups(self):
-
-        url = reverse('virtualization-api:clustergroup-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_clustergroups_brief(self):
-
-        url = reverse('virtualization-api:clustergroup-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['cluster_count', 'id', 'name', 'slug', 'url']
+        ClusterType.objects.bulk_create(cluster_types)
+
+
+class ClusterGroupTest(APIViewTestCases.APIViewTestCase):
+    model = ClusterGroup
+    brief_fields = ['cluster_count', 'id', 'name', 'slug', 'url']
+    create_data = [
+        {
+            'name': 'Cluster Group 4',
+            'slug': 'cluster-type-4',
+        },
+        {
+            'name': 'Cluster Group 5',
+            'slug': 'cluster-type-5',
+        },
+        {
+            'name': 'Cluster Group 6',
+            'slug': 'cluster-type-6',
+        },
+    ]
+
+    @classmethod
+    def setUpTestData(cls):
+
+        cluster_Groups = (
+            ClusterGroup(name='Cluster Group 1', slug='cluster-type-1'),
+            ClusterGroup(name='Cluster Group 2', slug='cluster-type-2'),
+            ClusterGroup(name='Cluster Group 3', slug='cluster-type-3'),
         )
+        ClusterGroup.objects.bulk_create(cluster_Groups)
 
-    def test_create_clustergroup(self):
-
-        data = {
-            'name': 'Test Cluster Group 4',
-            'slug': 'test-cluster-group-4',
-        }
-
-        url = reverse('virtualization-api:clustergroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ClusterGroup.objects.count(), 4)
-        clustergroup4 = ClusterGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(clustergroup4.name, data['name'])
-        self.assertEqual(clustergroup4.slug, data['slug'])
-
-    def test_create_clustergroup_bulk(self):
 
-        data = [
-            {
-                'name': 'Test Cluster Group 4',
-                'slug': 'test-cluster-group-4',
-            },
-            {
-                'name': 'Test Cluster Group 5',
-                'slug': 'test-cluster-group-5',
-            },
-            {
-                'name': 'Test Cluster Group 6',
-                'slug': 'test-cluster-group-6',
-            },
-        ]
-
-        url = reverse('virtualization-api:clustergroup-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(ClusterGroup.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_clustergroup(self):
-
-        data = {
-            'name': 'Test Cluster Group X',
-            'slug': 'test-cluster-group-x',
-        }
-
-        url = reverse('virtualization-api:clustergroup-detail', kwargs={'pk': self.clustergroup1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(ClusterGroup.objects.count(), 3)
-        clustergroup1 = ClusterGroup.objects.get(pk=response.data['id'])
-        self.assertEqual(clustergroup1.name, data['name'])
-        self.assertEqual(clustergroup1.slug, data['slug'])
-
-    def test_delete_clustergroup(self):
-
-        url = reverse('virtualization-api:clustergroup-detail', kwargs={'pk': self.clustergroup1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(ClusterGroup.objects.count(), 2)
+class ClusterTest(APIViewTestCases.APIViewTestCase):
+    model = Cluster
+    brief_fields = ['id', 'name', 'url', 'virtualmachine_count']
 
+    @classmethod
+    def setUpTestData(cls):
 
-class ClusterTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        cluster_type = ClusterType.objects.create(name='Test Cluster Type 1', slug='test-cluster-type-1')
-        cluster_group = ClusterGroup.objects.create(name='Test Cluster Group 1', slug='test-cluster-group-1')
-
-        self.cluster1 = Cluster.objects.create(name='Test Cluster 1', type=cluster_type, group=cluster_group)
-        self.cluster2 = Cluster.objects.create(name='Test Cluster 2', type=cluster_type, group=cluster_group)
-        self.cluster3 = Cluster.objects.create(name='Test Cluster 3', type=cluster_type, group=cluster_group)
-
-    def test_get_cluster(self):
-
-        url = reverse('virtualization-api:cluster-detail', kwargs={'pk': self.cluster1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.cluster1.name)
-
-    def test_list_clusters(self):
-
-        url = reverse('virtualization-api:cluster-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 3)
-
-    def test_list_clusters_brief(self):
-
-        url = reverse('virtualization-api:cluster-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'url', 'virtualmachine_count']
+        cluster_types = (
+            ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
+            ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
         )
+        ClusterType.objects.bulk_create(cluster_types)
 
-    def test_create_cluster(self):
-
-        data = {
-            'name': 'Test Cluster 4',
-            'type': ClusterType.objects.first().pk,
-            'group': ClusterGroup.objects.first().pk,
-        }
-
-        url = reverse('virtualization-api:cluster-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Cluster.objects.count(), 4)
-        cluster4 = Cluster.objects.get(pk=response.data['id'])
-        self.assertEqual(cluster4.name, data['name'])
-        self.assertEqual(cluster4.type.pk, data['type'])
-        self.assertEqual(cluster4.group.pk, data['group'])
+        cluster_groups = (
+            ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
+            ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
+        )
+        ClusterGroup.objects.bulk_create(cluster_groups)
 
-    def test_create_cluster_bulk(self):
+        clusters = (
+            Cluster(name='Cluster 1', type=cluster_types[0], group=cluster_groups[0]),
+            Cluster(name='Cluster 2', type=cluster_types[0], group=cluster_groups[0]),
+            Cluster(name='Cluster 3', type=cluster_types[0], group=cluster_groups[0]),
+        )
+        Cluster.objects.bulk_create(clusters)
 
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Cluster 4',
-                'type': ClusterType.objects.first().pk,
-                'group': ClusterGroup.objects.first().pk,
+                'name': 'Cluster 4',
+                'type': cluster_types[1].pk,
+                'group': cluster_groups[1].pk,
             },
             {
-                'name': 'Test Cluster 5',
-                'type': ClusterType.objects.first().pk,
-                'group': ClusterGroup.objects.first().pk,
+                'name': 'Cluster 5',
+                'type': cluster_types[1].pk,
+                'group': cluster_groups[1].pk,
             },
             {
-                'name': 'Test Cluster 6',
-                'type': ClusterType.objects.first().pk,
-                'group': ClusterGroup.objects.first().pk,
+                'name': 'Cluster 6',
+                'type': cluster_types[1].pk,
+                'group': cluster_groups[1].pk,
             },
         ]
 
-        url = reverse('virtualization-api:cluster-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(Cluster.objects.count(), 6)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
 
-    def test_update_cluster(self):
-
-        cluster_type2 = ClusterType.objects.create(name='Test Cluster Type 2', slug='test-cluster-type-2')
-        cluster_group2 = ClusterGroup.objects.create(name='Test Cluster Group 2', slug='test-cluster-group-2')
-        data = {
-            'name': 'Test Cluster X',
-            'type': cluster_type2.pk,
-            'group': cluster_group2.pk,
-        }
-
-        url = reverse('virtualization-api:cluster-detail', kwargs={'pk': self.cluster1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(Cluster.objects.count(), 3)
-        cluster1 = Cluster.objects.get(pk=response.data['id'])
-        self.assertEqual(cluster1.name, data['name'])
-        self.assertEqual(cluster1.type.pk, data['type'])
-        self.assertEqual(cluster1.group.pk, data['group'])
+class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
+    model = VirtualMachine
+    brief_fields = ['id', 'name', 'url']
 
-    def test_delete_cluster(self):
+    @classmethod
+    def setUpTestData(cls):
+        clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
+        clustergroup = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1')
 
-        url = reverse('virtualization-api:cluster-detail', kwargs={'pk': self.cluster1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Cluster.objects.count(), 2)
-
-
-class VirtualMachineTest(APITestCase):
-
-    def setUp(self):
-
-        super().setUp()
-
-        cluster_type = ClusterType.objects.create(name='Test Cluster Type 1', slug='test-cluster-type-1')
-        cluster_group = ClusterGroup.objects.create(name='Test Cluster Group 1', slug='test-cluster-group-1')
-        self.cluster1 = Cluster.objects.create(name='Test Cluster 1', type=cluster_type, group=cluster_group)
-
-        self.virtualmachine1 = VirtualMachine.objects.create(name='Test Virtual Machine 1', cluster=self.cluster1)
-        self.virtualmachine2 = VirtualMachine.objects.create(name='Test Virtual Machine 2', cluster=self.cluster1)
-        self.virtualmachine3 = VirtualMachine.objects.create(name='Test Virtual Machine 3', cluster=self.cluster1)
-        self.virtualmachine_with_context_data = VirtualMachine.objects.create(
-            name='VM with context data',
-            cluster=self.cluster1,
-            local_context_data={
-                'A': 1,
-                'B': 2
-            }
+        clusters = (
+            Cluster(name='Cluster 1', type=clustertype, group=clustergroup),
+            Cluster(name='Cluster 2', type=clustertype, group=clustergroup),
         )
+        Cluster.objects.bulk_create(clusters)
 
-    def test_get_virtualmachine(self):
-
-        url = reverse('virtualization-api:virtualmachine-detail', kwargs={'pk': self.virtualmachine1.pk})
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['name'], self.virtualmachine1.name)
-
-    def test_list_virtualmachines(self):
-
-        url = reverse('virtualization-api:virtualmachine-list')
-        response = self.client.get(url, **self.header)
-
-        self.assertEqual(response.data['count'], 4)
-
-    def test_list_virtualmachines_brief(self):
-
-        url = reverse('virtualization-api:virtualmachine-list')
-        response = self.client.get('{}?brief=1'.format(url), **self.header)
-
-        self.assertEqual(
-            sorted(response.data['results'][0]),
-            ['id', 'name', 'url']
+        virtual_machines = (
+            VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], local_context_data={'A': 1}),
+            VirtualMachine(name='Virtual Machine 2', cluster=clusters[0], local_context_data={'B': 2}),
+            VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], local_context_data={'C': 3}),
         )
+        VirtualMachine.objects.bulk_create(virtual_machines)
 
-    def test_create_virtualmachine(self):
-
-        data = {
-            'name': 'Test Virtual Machine 4',
-            'cluster': self.cluster1.pk,
-        }
-
-        url = reverse('virtualization-api:virtualmachine-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VirtualMachine.objects.count(), 5)
-        virtualmachine4 = VirtualMachine.objects.get(pk=response.data['id'])
-        self.assertEqual(virtualmachine4.name, data['name'])
-        self.assertEqual(virtualmachine4.cluster.pk, data['cluster'])
-
-    def test_create_virtualmachine_without_cluster(self):
-
-        data = {
-            'name': 'Test Virtual Machine 4',
-        }
-
-        url = reverse('virtualization-api:virtualmachine-list')
-        with disable_warnings('django.request'):
-            response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
-        self.assertEqual(VirtualMachine.objects.count(), 4)
-
-    def test_create_virtualmachine_bulk(self):
-
-        data = [
+        cls.create_data = [
             {
-                'name': 'Test Virtual Machine 4',
-                'cluster': self.cluster1.pk,
+                'name': 'Virtual Machine 4',
+                'cluster': clusters[1].pk,
             },
             {
-                'name': 'Test Virtual Machine 5',
-                'cluster': self.cluster1.pk,
+                'name': 'Virtual Machine 5',
+                'cluster': clusters[1].pk,
             },
             {
-                'name': 'Test Virtual Machine 6',
-                'cluster': self.cluster1.pk,
+                'name': 'Virtual Machine 6',
+                'cluster': clusters[1].pk,
             },
         ]
 
-        url = reverse('virtualization-api:virtualmachine-list')
-        response = self.client.post(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VirtualMachine.objects.count(), 7)
-        self.assertEqual(response.data[0]['name'], data[0]['name'])
-        self.assertEqual(response.data[1]['name'], data[1]['name'])
-        self.assertEqual(response.data[2]['name'], data[2]['name'])
-
-    def test_update_virtualmachine(self):
-
-        interface = Interface.objects.create(name='Test Interface 1', virtual_machine=self.virtualmachine1)
-        ip4_address = IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), interface=interface)
-        ip6_address = IPAddress.objects.create(address=IPNetwork('2001:db8::1/64'), interface=interface)
-
-        cluster2 = Cluster.objects.create(
-            name='Test Cluster 2',
-            type=ClusterType.objects.first(),
-            group=ClusterGroup.objects.first()
-        )
-        data = {
-            'name': 'Test Virtual Machine X',
-            'cluster': cluster2.pk,
-            'primary_ip4': ip4_address.pk,
-            'primary_ip6': ip6_address.pk,
-        }
-
-        url = reverse('virtualization-api:virtualmachine-detail', kwargs={'pk': self.virtualmachine1.pk})
-        response = self.client.put(url, data, format='json', **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(VirtualMachine.objects.count(), 4)
-        virtualmachine1 = VirtualMachine.objects.get(pk=response.data['id'])
-        self.assertEqual(virtualmachine1.name, data['name'])
-        self.assertEqual(virtualmachine1.cluster.pk, data['cluster'])
-        self.assertEqual(virtualmachine1.primary_ip4.pk, data['primary_ip4'])
-        self.assertEqual(virtualmachine1.primary_ip6.pk, data['primary_ip6'])
-
-    def test_delete_virtualmachine(self):
-
-        url = reverse('virtualization-api:virtualmachine-detail', kwargs={'pk': self.virtualmachine1.pk})
-        response = self.client.delete(url, **self.header)
-
-        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(VirtualMachine.objects.count(), 3)
-
     def test_config_context_included_by_default_in_list_view(self):
-
+        """
+        Check that config context data is included by default in the virtual machines list.
+        """
+        virtualmachine = VirtualMachine.objects.first()
         url = reverse('virtualization-api:virtualmachine-list')
-        url = '{}?id={}'.format(url, self.virtualmachine_with_context_data.pk)
+        url = '{}?id={}'.format(url, virtualmachine.pk)
         response = self.client.get(url, **self.header)
 
         self.assertEqual(response.data['results'][0].get('config_context', {}).get('A'), 1)
 
     def test_config_context_excluded(self):
-
+        """
+        Check that config context data can be excluded by passing ?exclude=config_context.
+        """
         url = reverse('virtualization-api:virtualmachine-list') + '?exclude=config_context'
         response = self.client.get(url, **self.header)
 
         self.assertFalse('config_context' in response.data['results'][0])
 
     def test_unique_name_per_cluster_constraint(self):
-
+        """
+        Check that creating a virtual machine with a duplicate name fails.
+        """
         data = {
-            'name': 'Test Virtual Machine 1',
-            'cluster': self.cluster1.pk,
+            'name': 'Virtual Machine 1',
+            'cluster': Cluster.objects.first().pk,
         }
-
         url = reverse('virtualization-api:virtualmachine-list')
         response = self.client.post(url, data, format='json', **self.header)
 
         self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
 
 
+# TODO: Standardize InterfaceTest (pending #4721)
 class InterfaceTest(APITestCase):
 
     def setUp(self):
diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py
--- a/netbox/virtualization/tests/test_views.py
+++ b/netbox/virtualization/tests/test_views.py
@@ -187,14 +187,14 @@ def setUpTestData(cls):
 
 class InterfaceTestCase(
     ViewTestCases.GetObjectViewTestCase,
-    ViewTestCases.DeviceComponentViewTestCase,
+    ViewTestCases.EditObjectViewTestCase,
+    ViewTestCases.DeleteObjectViewTestCase,
+    ViewTestCases.BulkCreateObjectsViewTestCase,
+    ViewTestCases.BulkEditObjectsViewTestCase,
+    ViewTestCases.BulkDeleteObjectsViewTestCase,
 ):
     model = Interface
 
-    # Disable inapplicable tests
-    test_list_objects = None
-    test_import_objects = None
-
     def _get_base_url(self):
         # Interface belongs to the DCIM app, so we have to override the base URL
         return 'virtualization:interface_{}'