diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index 863c5c430..4a2a972f3 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -26,26 +26,26 @@ __all__ = ( class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet): region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), - field_name='circuits__terminations___site__region', + field_name='circuits__terminations___region', lookup_expr='in', label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), - field_name='circuits__terminations___site__region', + field_name='circuits__terminations___region', lookup_expr='in', to_field_name='slug', label=_('Region (slug)'), ) site_group_id = TreeNodeMultipleChoiceFilter( queryset=SiteGroup.objects.all(), - field_name='circuits__terminations___site__group', + field_name='circuits__terminations___site_group', lookup_expr='in', label=_('Site group (ID)'), ) site_group = TreeNodeMultipleChoiceFilter( queryset=SiteGroup.objects.all(), - field_name='circuits__terminations___site__group', + field_name='circuits__terminations___site_group', lookup_expr='in', to_field_name='slug', label=_('Site group (slug)'), @@ -193,26 +193,26 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), - field_name='terminations___site__region', + field_name='terminations___region', lookup_expr='in', label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), - field_name='terminations___site__region', + field_name='terminations___region', lookup_expr='in', to_field_name='slug', label=_('Region (slug)'), ) site_group_id = TreeNodeMultipleChoiceFilter( queryset=SiteGroup.objects.all(), - field_name='terminations___site__group', + field_name='terminations___site_group', lookup_expr='in', label=_('Site group (ID)'), ) site_group = TreeNodeMultipleChoiceFilter( queryset=SiteGroup.objects.all(), - field_name='terminations___site__group', + field_name='terminations___site_group', lookup_expr='in', to_field_name='slug', label=_('Site group (slug)'), diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py index 451fe158f..eab87b1f5 100644 --- a/netbox/circuits/forms/bulk_import.py +++ b/netbox/circuits/forms/bulk_import.py @@ -143,7 +143,7 @@ class CircuitTerminationImportRelatedForm(BaseCircuitTerminationImportForm): 'pp_info', 'description' ] labels = { - 'termination_id': 'Termination ID', + 'termination_id': _('Termination ID'), } @@ -156,7 +156,7 @@ class CircuitTerminationImportForm(NetBoxModelImportForm, BaseCircuitTermination 'pp_info', 'description', 'tags' ] labels = { - 'termination_id': 'Termination ID', + 'termination_id': _('Termination ID'), } diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 9674db4d9..a87e327af 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -1,5 +1,4 @@ import datetime -import json from django.contrib.contenttypes.models import ContentType from django.test import override_settings @@ -192,36 +191,31 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase): @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[]) def test_bulk_import_objects_with_terminations(self): - json_data = """ + site = Site.objects.first() + json_data = f""" [ - { + {{ "cid": "Circuit 7", "provider": "Provider 1", "type": "Circuit Type 1", "status": "active", "description": "Testing Import", "terminations": [ - { + {{ "term_side": "A", "termination_type": "dcim.site", - "termination_id": "1" - }, - { + "termination_id": "{site.pk}" + }}, + {{ "term_side": "Z", "termination_type": "dcim.site", - "termination_id": "1" - } + "termination_id": "{site.pk}" + }} ] - } + }} ] """ - # Fix up the termination site id - site = Site.objects.first() - data = json.loads(json_data) - data[0]["terminations"][0]["termination_id"] = data[0]["terminations"][1]["termination_id"] = site.id - json_data = json.dumps(data) - initial_count = self._get_queryset().count() data = { 'data': json_data, diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index c04df1203..4059065bf 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -257,8 +257,7 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView): class CircuitListView(generic.ObjectListView): queryset = Circuit.objects.prefetch_related( - 'tenant__group', 'termination_a___site', 'termination_z___site', - 'termination_a___provider_network', 'termination_z___provider_network', + 'tenant__group', 'termination_a__termination', 'termination_z__termination', ) filterset = filtersets.CircuitFilterSet filterset_form = forms.CircuitFilterForm @@ -298,8 +297,7 @@ class CircuitBulkImportView(generic.BulkImportView): class CircuitBulkEditView(generic.BulkEditView): queryset = Circuit.objects.prefetch_related( - 'termination_a___site', 'termination_z___site', - 'termination_a___provider_network', 'termination_z___provider_network', + 'tenant__group', 'termination_a__termination', 'termination_z__termination', ) filterset = filtersets.CircuitFilterSet table = tables.CircuitTable @@ -308,8 +306,7 @@ class CircuitBulkEditView(generic.BulkEditView): class CircuitBulkDeleteView(generic.BulkDeleteView): queryset = Circuit.objects.prefetch_related( - 'termination_a___site', 'termination_z___site', - 'termination_a___provider_network', 'termination_z___provider_network', + 'tenant__group', 'termination_a__termination', 'termination_z__termination', ) filterset = filtersets.CircuitFilterSet table = tables.CircuitTable diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index 30de9c088..05fe08d5d 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -461,6 +461,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, @@ -704,6 +708,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, @@ -748,6 +756,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, diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 055165ef4..4818c563c 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -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' + ), + ), + ), }