diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index 977bd48ac..be36d45ac 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -3,7 +3,7 @@ from django.db.models import Q from django.utils.translation import gettext as _ from dcim.filtersets import CabledObjectFilterSet -from dcim.models import Location, Region, Site, SiteGroup +from dcim.models import Interface, Location, Region, Site, SiteGroup from ipam.models import ASN from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet @@ -20,6 +20,8 @@ __all__ = ( 'ProviderNetworkFilterSet', 'ProviderAccountFilterSet', 'ProviderFilterSet', + 'VirtualCircuitFilterSet', + 'VirtualCircuitTerminationFilterSet', ) @@ -483,7 +485,7 @@ class VirtualCircuitTerminationFilterSet(NetBoxModelFilterSet): ) provider_account = django_filters.ModelMultipleChoiceFilter( field_name='virtual_circuit__provider_account__account', - queryset=Provider.objects.all(), + queryset=ProviderAccount.objects.all(), to_field_name='account', label=_('Provider account (account)'), ) @@ -492,15 +494,20 @@ class VirtualCircuitTerminationFilterSet(NetBoxModelFilterSet): field_name='virtual_circuit__provider_network', label=_('Provider network (ID)'), ) + interface_id = django_filters.ModelMultipleChoiceFilter( + queryset=Interface.objects.all(), + field_name='interface', + label=_('Interface (ID)'), + ) class Meta: model = VirtualCircuitTermination - fields = ('id', 'interface_id') + fields = ('id', 'interface_id', 'description') def search(self, queryset, name, value): if not value.strip(): return queryset return queryset.filter( - Q(circuit__cid__icontains=value) | + Q(virtual_circuit__cid__icontains=value) | Q(description__icontains=value) ).distinct() diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py index b0340b76c..a5596ec81 100644 --- a/netbox/circuits/forms/bulk_import.py +++ b/netbox/circuits/forms/bulk_import.py @@ -188,13 +188,13 @@ class VirtualCircuitImportForm(NetBoxModelImportForm): provider_network = CSVModelChoiceField( label=_('Provider network'), queryset=ProviderNetwork.objects.all(), - to_field_name='cid', + to_field_name='name', help_text=_('The network to which this virtual circuit belongs') ) provider_account = CSVModelChoiceField( label=_('Provider account'), queryset=ProviderAccount.objects.all(), - to_field_name='name', + to_field_name='account', help_text=_('Assigned provider account (if any)'), required=False ) @@ -214,7 +214,7 @@ class VirtualCircuitImportForm(NetBoxModelImportForm): class Meta: model = VirtualCircuit fields = [ - 'cid', 'provider_network', 'provider_account', 'status', 'tenant','description', 'comments', 'tags', + 'cid', 'provider_network', 'provider_account', 'status', 'tenant', 'description', 'comments', 'tags', ] @@ -238,5 +238,5 @@ class VirtualCircuitTerminationImportForm(NetBoxModelImportForm): class Meta: model = VirtualCircuitTermination fields = [ - 'virtual_circuit', 'description', 'tags' + 'virtual_circuit', 'role', 'interface', 'description', 'tags' ] diff --git a/netbox/circuits/graphql/types.py b/netbox/circuits/graphql/types.py index 1041b35d1..f2703b207 100644 --- a/netbox/circuits/graphql/types.py +++ b/netbox/circuits/graphql/types.py @@ -130,8 +130,14 @@ class CircuitGroupAssignmentType(TagsMixin, BaseObjectType): filters=VirtualCircuitTerminationFilter ) class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType): - virtual_circuit: Annotated["VirtualCircuitType", strawberry.lazy('circuits.graphql.types')] - interface: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] + virtual_circuit: Annotated[ + "VirtualCircuitType", + strawberry.lazy('circuits.graphql.types') + ] = strawberry_django.field(select_related=["virtual_circuit"]) + interface: Annotated[ + "InterfaceType", + strawberry.lazy('dcim.graphql.types') + ] = strawberry_django.field(select_related=["interface"]) @strawberry_django.type( @@ -140,7 +146,7 @@ class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType): filters=VirtualCircuitFilter ) class VirtualCircuitType(NetBoxObjectType): - provider_network: ProviderNetworkType + provider_network: ProviderNetworkType = strawberry_django.field(select_related=["provider_network"]) provider_account: ProviderAccountType | None tenant: TenantType | None diff --git a/netbox/circuits/tables/virtual_circuits.py b/netbox/circuits/tables/virtual_circuits.py index c670dd1dd..b7617f297 100644 --- a/netbox/circuits/tables/virtual_circuits.py +++ b/netbox/circuits/tables/virtual_circuits.py @@ -43,7 +43,7 @@ class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable) ) class Meta(NetBoxTable.Meta): - model = Circuit + model = VirtualCircuit fields = ( 'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated', diff --git a/netbox/circuits/tests/test_filtersets.py b/netbox/circuits/tests/test_filtersets.py index 0dbc7172b..01b5e3105 100644 --- a/netbox/circuits/tests/test_filtersets.py +++ b/netbox/circuits/tests/test_filtersets.py @@ -3,7 +3,8 @@ from django.test import TestCase from circuits.choices import * from circuits.filtersets import * from circuits.models import * -from dcim.models import Cable, Region, Site, SiteGroup +from dcim.choices import InterfaceTypeChoices +from dcim.models import Cable, Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site, SiteGroup from ipam.models import ASN, RIR from netbox.choices import DistanceUnitChoices from tenancy.models import Tenant, TenantGroup @@ -678,3 +679,293 @@ class ProviderAccountTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'provider': [providers[0].slug, providers[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + +class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests): + queryset = VirtualCircuit.objects.all() + filterset = VirtualCircuitFilterSet + + @classmethod + def setUpTestData(cls): + + tenant_groups = ( + TenantGroup(name='Tenant group 1', slug='tenant-group-1'), + TenantGroup(name='Tenant group 2', slug='tenant-group-2'), + TenantGroup(name='Tenant group 3', slug='tenant-group-3'), + ) + for tenantgroup in tenant_groups: + tenantgroup.save() + + tenants = ( + Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), + Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1]), + Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[2]), + ) + Tenant.objects.bulk_create(tenants) + + 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) + + provider_accounts = ( + ProviderAccount(name='Provider Account 1', provider=providers[0], account='A'), + ProviderAccount(name='Provider Account 2', provider=providers[1], account='B'), + ProviderAccount(name='Provider Account 3', provider=providers[2], account='C'), + ) + ProviderAccount.objects.bulk_create(provider_accounts) + + provider_networks = ( + ProviderNetwork(name='Provider Network 1', provider=providers[0]), + ProviderNetwork(name='Provider Network 2', provider=providers[1]), + ProviderNetwork(name='Provider Network 3', provider=providers[2]), + ) + ProviderNetwork.objects.bulk_create(provider_networks) + + virutal_circuits = ( + VirtualCircuit( + provider_network=provider_networks[0], + provider_account=provider_accounts[0], + tenant=tenants[0], + cid='Virtual Circuit 1', + status=CircuitStatusChoices.STATUS_PLANNED, + description='virtualcircuit1', + ), + VirtualCircuit( + provider_network=provider_networks[1], + provider_account=provider_accounts[1], + tenant=tenants[1], + cid='Virtual Circuit 2', + status=CircuitStatusChoices.STATUS_ACTIVE, + description='virtualcircuit2', + ), + VirtualCircuit( + provider_network=provider_networks[2], + provider_account=provider_accounts[2], + tenant=tenants[2], + cid='Virtual Circuit 3', + status=CircuitStatusChoices.STATUS_DEPROVISIONING, + description='virtualcircuit3', + ), + ) + VirtualCircuit.objects.bulk_create(virutal_circuits) + + def test_q(self): + params = {'q': 'virtualcircuit1'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_cid(self): + params = {'cid': ['Virtual Circuit 1', 'Virtual Circuit 2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_provider(self): + providers = Provider.objects.all()[:2] + params = {'provider_id': [providers[0].pk, providers[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'provider': [providers[0].slug, providers[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_provider_account(self): + provider_accounts = ProviderAccount.objects.all()[:2] + params = {'provider_account_id': [provider_accounts[0].pk, provider_accounts[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_provider_network(self): + provider_networks = ProviderNetwork.objects.all()[:2] + params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_status(self): + params = {'status': [CircuitStatusChoices.STATUS_ACTIVE, CircuitStatusChoices.STATUS_PLANNED]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['virtualcircuit1', 'virtualcircuit2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_tenant(self): + tenants = Tenant.objects.all()[:2] + params = {'tenant_id': [tenants[0].pk, tenants[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'tenant': [tenants[0].slug, tenants[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_tenant_group(self): + tenant_groups = TenantGroup.objects.all()[:2] + params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + +class VirtualCircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests): + queryset = VirtualCircuitTermination.objects.all() + filterset = VirtualCircuitTerminationFilterSet + + @classmethod + def setUpTestData(cls): + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + site = Site.objects.create(name='Site 1', slug='site-1') + + devices = ( + Device(site=site, name='Device 1', device_type=device_type, role=device_role), + Device(site=site, name='Device 2', device_type=device_type, role=device_role), + Device(site=site, name='Device 3', device_type=device_type, role=device_role), + ) + Device.objects.bulk_create(devices) + + virtual_interfaces = ( + # Device 1 + Interface( + device=devices[0], + name='eth0.1', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[0], + name='eth0.2', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + # Device 2 + Interface( + device=devices[1], + name='eth0.1', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[1], + name='eth0.2', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + # Device 3 + Interface( + device=devices[2], + name='eth0.1', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[2], + name='eth0.2', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + ) + Interface.objects.bulk_create(virtual_interfaces) + + 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) + provider_networks = ( + ProviderNetwork(provider=providers[0], name='Provider Network 1'), + ProviderNetwork(provider=providers[1], name='Provider Network 2'), + ProviderNetwork(provider=providers[2], name='Provider Network 3'), + ) + ProviderNetwork.objects.bulk_create(provider_networks) + provider_accounts = ( + ProviderAccount(provider=providers[0], account='Provider Account 1'), + ProviderAccount(provider=providers[1], account='Provider Account 2'), + ProviderAccount(provider=providers[2], account='Provider Account 3'), + ) + ProviderAccount.objects.bulk_create(provider_accounts) + + virtual_circuits = ( + VirtualCircuit( + provider_network=provider_networks[0], + provider_account=provider_accounts[0], + cid='Virtual Circuit 1' + ), + VirtualCircuit( + provider_network=provider_networks[1], + provider_account=provider_accounts[1], + cid='Virtual Circuit 2' + ), + VirtualCircuit( + provider_network=provider_networks[2], + provider_account=provider_accounts[2], + cid='Virtual Circuit 3' + ), + ) + VirtualCircuit.objects.bulk_create(virtual_circuits) + + virtual_circuit_terminations = ( + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[0], + role=VirtualCircuitTerminationRoleChoices.ROLE_HUB, + interface=virtual_interfaces[0], + description='termination1' + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[0], + role=VirtualCircuitTerminationRoleChoices.ROLE_SPOKE, + interface=virtual_interfaces[3], + description='termination2' + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[1], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[1], + description='termination3' + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[1], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[4], + description='termination4' + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[2], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[2], + description='termination5' + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[2], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[5], + description='termination6' + ), + ) + VirtualCircuitTermination.objects.bulk_create(virtual_circuit_terminations) + + def test_q(self): + params = {'q': 'termination1'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_description(self): + params = {'description': ['termination1', 'termination2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_virtual_circuit_id(self): + virtual_circuits = VirtualCircuit.objects.filter()[:2] + params = {'virtual_circuit_id': [virtual_circuits[0].pk, virtual_circuits[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_provider(self): + providers = Provider.objects.all()[:2] + params = {'provider_id': [providers[0].pk, providers[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'provider': [providers[0].slug, providers[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_provider_network(self): + provider_networks = ProviderNetwork.objects.all()[:2] + params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_provider_account(self): + provider_accounts = ProviderAccount.objects.all()[:2] + params = {'provider_account_id': [provider_accounts[0].pk, provider_accounts[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'provider_account': [provider_accounts[0].account, provider_accounts[1].account]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_interface(self): + interfaces = Interface.objects.all()[:2] + params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index f6c626443..a68a349c1 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -7,7 +7,8 @@ from django.urls import reverse from circuits.choices import * from circuits.models import * from core.models import ObjectType -from dcim.models import Cable, Interface, Site +from dcim.choices import InterfaceTypeChoices +from dcim.models import Cable, Device, DeviceRole, DeviceType, Interface, Manufacturer, Site from ipam.models import ASN, RIR from netbox.choices import ImportFormatChoices from users.models import ObjectPermission @@ -341,7 +342,7 @@ class ProviderNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase): } -class TestCase(ViewTestCases.PrimaryObjectViewTestCase): +class CircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = CircuitTermination @classmethod @@ -518,3 +519,255 @@ class CircuitGroupAssignmentTestCase( cls.bulk_edit_data = { 'priority': CircuitPriorityChoices.PRIORITY_INACTIVE, } + + +class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase): + model = VirtualCircuit + + @classmethod + def setUpTestData(cls): + provider = Provider.objects.create(name='Provider 1', slug='provider-1') + provider_networks = ( + ProviderNetwork(provider=provider, name='Provider Network 1'), + ProviderNetwork(provider=provider, name='Provider Network 2'), + ) + ProviderNetwork.objects.bulk_create(provider_networks) + provider_accounts = ( + ProviderAccount(provider=provider, account='Provider Account 1'), + ProviderAccount(provider=provider, account='Provider Account 2'), + ) + ProviderAccount.objects.bulk_create(provider_accounts) + + virtual_circuits = ( + VirtualCircuit( + provider_network=provider_networks[0], + provider_account=provider_accounts[0], + cid='Virtual Circuit 1' + ), + VirtualCircuit( + provider_network=provider_networks[0], + provider_account=provider_accounts[0], + cid='Virtual Circuit 2' + ), + VirtualCircuit( + provider_network=provider_networks[0], + provider_account=provider_accounts[0], + cid='Virtual Circuit 3' + ), + ) + VirtualCircuit.objects.bulk_create(virtual_circuits) + + tags = create_tags('Alpha', 'Bravo', 'Charlie') + + cls.form_data = { + 'cid': 'Virtual Circuit X', + 'provider_network': provider_networks[1].pk, + 'provider_account': provider_accounts[1].pk, + 'status': CircuitStatusChoices.STATUS_PLANNED, + 'description': 'A new virtual circuit', + 'comments': 'Some comments', + 'tags': [t.pk for t in tags], + } + + cls.csv_data = ( + "cid,provider_network,provider_account,status", + f"Virtual Circuit 4,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}", + f"Virtual Circuit 5,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}", + f"Virtual Circuit 6,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}", + ) + + cls.csv_update_data = ( + "id,cid,description,status", + f"{virtual_circuits[0].pk},Virtual Circuit A,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}", + f"{virtual_circuits[1].pk},Virtual Circuit B,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}", + f"{virtual_circuits[2].pk},Virtual Circuit C,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}", + ) + + cls.bulk_edit_data = { + 'provider_network': provider_networks[1].pk, + 'provider_account': provider_accounts[1].pk, + 'status': CircuitStatusChoices.STATUS_DECOMMISSIONED, + 'description': 'New description', + 'comments': 'New comments', + } + + +class VirtualCircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase): + model = VirtualCircuitTermination + + @classmethod + def setUpTestData(cls): + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + site = Site.objects.create(name='Site 1', slug='site-1') + + devices = ( + Device(site=site, name='hub', device_type=device_type, role=device_role), + Device(site=site, name='spoke1', device_type=device_type, role=device_role), + Device(site=site, name='spoke2', device_type=device_type, role=device_role), + Device(site=site, name='spoke3', device_type=device_type, role=device_role), + ) + Device.objects.bulk_create(devices) + + physical_interfaces = ( + Interface(device=devices[0], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[1], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[2], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[3], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + ) + Interface.objects.bulk_create(physical_interfaces) + + virtual_interfaces = ( + # Point-to-point VCs + Interface( + device=devices[0], + name='eth0.1', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[0], + name='eth0.2', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[0], + name='eth0.3', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[1], + name='eth0.1', + parent=physical_interfaces[1], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[2], + name='eth0.1', + parent=physical_interfaces[2], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[3], + name='eth0.1', + parent=physical_interfaces[3], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + + # Hub and spoke VCs + Interface( + device=devices[0], + name='eth0.9', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[1], + name='eth0.9', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[2], + name='eth0.9', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + Interface( + device=devices[3], + name='eth0.9', + parent=physical_interfaces[0], + type=InterfaceTypeChoices.TYPE_VIRTUAL + ), + ) + Interface.objects.bulk_create(virtual_interfaces) + + provider = Provider.objects.create(name='Provider 1', slug='provider-1') + provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1') + provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1') + + virtual_circuits = ( + VirtualCircuit( + provider_network=provider_network, + provider_account=provider_account, + cid='Virtual Circuit 1' + ), + VirtualCircuit( + provider_network=provider_network, + provider_account=provider_account, + cid='Virtual Circuit 2' + ), + VirtualCircuit( + provider_network=provider_network, + provider_account=provider_account, + cid='Virtual Circuit 3' + ), + VirtualCircuit( + provider_network=provider_network, + provider_account=provider_account, + cid='Virtual Circuit 4' + ), + ) + VirtualCircuit.objects.bulk_create(virtual_circuits) + + virtual_circuit_terminations = ( + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[0], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[0] + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[0], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[3] + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[1], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[1] + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[1], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[4] + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[2], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[2] + ), + VirtualCircuitTermination( + virtual_circuit=virtual_circuits[2], + role=VirtualCircuitTerminationRoleChoices.ROLE_PEER, + interface=virtual_interfaces[5] + ), + ) + VirtualCircuitTermination.objects.bulk_create(virtual_circuit_terminations) + + cls.form_data = { + 'virtual_circuit': virtual_circuits[3].pk, + 'role': VirtualCircuitTerminationRoleChoices.ROLE_HUB, + 'interface': virtual_interfaces[6].pk + } + + cls.csv_data = ( + "virtual_circuit,role,interface,description", + f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_HUB},{virtual_interfaces[6].pk},Hub", + f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},{virtual_interfaces[7].pk},Spoke 1", + f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},{virtual_interfaces[8].pk},Spoke 2", + f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},{virtual_interfaces[9].pk},Spoke 3", + ) + + cls.csv_update_data = ( + "id,role,description", + f"{virtual_circuit_terminations[0].pk},{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},New description", + f"{virtual_circuit_terminations[1].pk},{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},New description", + f"{virtual_circuit_terminations[2].pk},{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},New description", + ) + + cls.bulk_edit_data = { + 'description': 'New description', + }