mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-05 14:56:24 -06:00
9604 Add Termination to CircuitTermination (#17821)
* 9604 add scope type to CircuitTermination * 9604 add scope type to CircuitTermination * 9604 add scope type to CircuitTermination * 9604 model_forms * 9604 form filtersets * 9604 form bulk_import * 9604 form bulk_edit * 9604 serializers * 9604 graphql * 9604 tests and detail template * 9604 fix migration merge * 9604 fix tests * 9604 fix tests * 9604 fix table * 9604 updates * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * 9604 remove provider_network * 9604 fix tests * 9604 fix tests * 9604 fix forms * 9604 review changes * 9604 scope->termination * 9604 fix _circuit_terminations * 9604 fix _circuit_terminations * 9604 sitegroup -> site_group * 9604 update docs * 9604 fix form termination side reference * Misc cleanup * Fix terminations in circuits table * Fix missing imports * Clean up termination attrs display * Add termination & type to CircuitTerminationTable * Update cable tracing logic --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
@@ -462,6 +462,10 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, Organi
|
||||
devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
|
||||
children: List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
@strawberry_django.field
|
||||
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuit_terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Manufacturer,
|
||||
@@ -705,6 +709,10 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
|
||||
def parent(self) -> Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None:
|
||||
return self.parent
|
||||
|
||||
@strawberry_django.field
|
||||
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuit_terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Site,
|
||||
@@ -726,10 +734,13 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObje
|
||||
devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
|
||||
locations: List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]
|
||||
asns: List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]
|
||||
circuit_terminations: List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]
|
||||
clusters: List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]
|
||||
vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
|
||||
|
||||
@strawberry_django.field
|
||||
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuit_terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.SiteGroup,
|
||||
@@ -746,6 +757,10 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
|
||||
def parent(self) -> Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None:
|
||||
return self.parent
|
||||
|
||||
@strawberry_django.field
|
||||
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuit_terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualChassis,
|
||||
|
||||
@@ -344,7 +344,7 @@ class CableTermination(ChangeLoggedModel):
|
||||
)
|
||||
|
||||
# A CircuitTermination attached to a ProviderNetwork cannot have a Cable
|
||||
if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None:
|
||||
if self.termination_type.model == 'circuittermination' and self.termination._provider_network is not None:
|
||||
raise ValidationError(_("Circuit terminations attached to a provider network may not be cabled."))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -690,19 +690,19 @@ class CablePath(models.Model):
|
||||
).first()
|
||||
if circuit_termination is None:
|
||||
break
|
||||
elif circuit_termination.provider_network:
|
||||
elif circuit_termination._provider_network:
|
||||
# Circuit terminates to a ProviderNetwork
|
||||
path.extend([
|
||||
[object_to_path_node(circuit_termination)],
|
||||
[object_to_path_node(circuit_termination.provider_network)],
|
||||
[object_to_path_node(circuit_termination._provider_network)],
|
||||
])
|
||||
is_complete = True
|
||||
break
|
||||
elif circuit_termination.site and not circuit_termination.cable:
|
||||
# Circuit terminates to a Site
|
||||
elif circuit_termination.termination and not circuit_termination.cable:
|
||||
# Circuit terminates to a Region/Site/etc.
|
||||
path.extend([
|
||||
[object_to_path_node(circuit_termination)],
|
||||
[object_to_path_node(circuit_termination.site)],
|
||||
[object_to_path_node(circuit_termination.termination)],
|
||||
])
|
||||
break
|
||||
|
||||
|
||||
@@ -1167,7 +1167,7 @@ class CablePathTestCase(TestCase):
|
||||
[IF1] --C1-- [CT1]
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@@ -1198,7 +1198,7 @@ class CablePathTestCase(TestCase):
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@@ -1214,7 +1214,7 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
|
||||
# Create CT2
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='Z')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
|
||||
|
||||
# Check for partial path to site
|
||||
self.assertPathExists(
|
||||
@@ -1266,7 +1266,7 @@ class CablePathTestCase(TestCase):
|
||||
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
||||
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
|
||||
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@@ -1282,7 +1282,7 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
|
||||
# Create CT2
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='Z')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
|
||||
|
||||
# Check for partial path to site
|
||||
self.assertPathExists(
|
||||
@@ -1335,8 +1335,8 @@ class CablePathTestCase(TestCase):
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
site2 = Site.objects.create(name='Site 2', slug='site-2')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=site2, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=site2, term_side='Z')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@@ -1365,8 +1365,8 @@ class CablePathTestCase(TestCase):
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider)
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, provider_network=providernetwork, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=providernetwork, term_side='Z')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@@ -1413,8 +1413,8 @@ class CablePathTestCase(TestCase):
|
||||
frontport2_2 = FrontPort.objects.create(
|
||||
device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
|
||||
)
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
|
||||
|
||||
# Create cables
|
||||
cable1 = Cable(
|
||||
@@ -1499,10 +1499,10 @@ class CablePathTestCase(TestCase):
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
||||
circuit2 = Circuit.objects.create(provider=self.circuit.provider, type=self.circuit.type, cid='Circuit 2')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='Z')
|
||||
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, site=self.site, term_side='A')
|
||||
circuittermination4 = CircuitTermination.objects.create(circuit=circuit2, site=self.site, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
|
||||
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, termination=self.site, term_side='A')
|
||||
circuittermination4 = CircuitTermination.objects.create(circuit=circuit2, termination=self.site, term_side='Z')
|
||||
|
||||
# Create cables
|
||||
cable1 = Cable(
|
||||
|
||||
@@ -5135,7 +5135,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||
circuit = Circuit.objects.create(cid='Circuit 1', provider=provider, type=circuit_type)
|
||||
circuit_termination = CircuitTermination.objects.create(circuit=circuit, term_side='A', site=sites[0])
|
||||
circuit_termination = CircuitTermination.objects.create(circuit=circuit, term_side='A', termination=sites[0])
|
||||
|
||||
# Cables
|
||||
cables = (
|
||||
@@ -5308,9 +5308,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
def test_site(self):
|
||||
site = Site.objects.all()[:2]
|
||||
params = {'site_id': [site[0].pk, site[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 12)
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 11)
|
||||
params = {'site': [site[0].slug, site[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 12)
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 11)
|
||||
|
||||
def test_tenant(self):
|
||||
tenant = Tenant.objects.all()[:2]
|
||||
|
||||
@@ -762,9 +762,9 @@ class CableTestCase(TestCase):
|
||||
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||
circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1')
|
||||
circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2')
|
||||
CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A')
|
||||
CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z')
|
||||
CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A')
|
||||
CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='A')
|
||||
CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='Z')
|
||||
CircuitTermination.objects.create(circuit=circuit2, termination=provider_network, term_side='A')
|
||||
|
||||
def test_cable_creation(self):
|
||||
"""
|
||||
|
||||
+23
-3
@@ -242,6 +242,10 @@ class RegionView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
extra=(
|
||||
(Location.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
|
||||
(
|
||||
Circuit.objects.restrict(request.user, 'view').filter(terminations___region=instance).distinct(),
|
||||
'region_id'
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
@@ -324,6 +328,10 @@ class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
extra=(
|
||||
(Location.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
|
||||
(
|
||||
Circuit.objects.restrict(request.user, 'view').filter(terminations___site_group=instance).distinct(),
|
||||
'site_group_id'
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
@@ -404,8 +412,10 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
scope_id=instance.pk
|
||||
), 'site'),
|
||||
(ASN.objects.restrict(request.user, 'view').filter(sites=instance), 'site_id'),
|
||||
(Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(),
|
||||
'site_id'),
|
||||
(
|
||||
Circuit.objects.restrict(request.user, 'view').filter(terminations___site=instance).distinct(),
|
||||
'site_id'
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
@@ -475,7 +485,17 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
def get_extra_context(self, request, instance):
|
||||
locations = instance.get_descendants(include_self=True)
|
||||
return {
|
||||
'related_models': self.get_related_models(request, locations, [CableTermination]),
|
||||
'related_models': self.get_related_models(
|
||||
request,
|
||||
locations,
|
||||
[CableTermination],
|
||||
(
|
||||
(
|
||||
Circuit.objects.restrict(request.user, 'view').filter(terminations___location=instance).distinct(),
|
||||
'location_id'
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user