diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 373b3e18d..0adbfcb0e 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -48,8 +48,7 @@ class CircuitTypeViewSet(CustomFieldModelViewSet): class CircuitViewSet(CustomFieldModelViewSet): queryset = Circuit.objects.prefetch_related( - Prefetch('terminations', queryset=CircuitTermination.objects.prefetch_related('site')), - 'type', 'tenant', 'provider', + 'type', 'tenant', 'provider', 'termination_a', 'termination_z' ).prefetch_related('tags') serializer_class = serializers.CircuitSerializer filterset_class = filters.CircuitFilterSet diff --git a/netbox/circuits/migrations/0027_cloud.py b/netbox/circuits/migrations/0027_cloud.py index 889b5151e..893371f8f 100644 --- a/netbox/circuits/migrations/0027_cloud.py +++ b/netbox/circuits/migrations/0027_cloud.py @@ -12,6 +12,7 @@ class Migration(migrations.Migration): ] operations = [ + # Create the new Cloud model migrations.CreateModel( name='Cloud', fields=[ @@ -37,6 +38,8 @@ class Migration(migrations.Migration): name='cloud', unique_together={('provider', 'name')}, ), + + # Add cloud FK to CircuitTermination migrations.AddField( model_name='circuittermination', name='cloud', @@ -47,4 +50,16 @@ class Migration(migrations.Migration): name='site', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.site'), ), + + # Add FKs to CircuitTermination on Circuit + migrations.AddField( + model_name='circuit', + name='termination_a', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'), + ), + migrations.AddField( + model_name='circuit', + name='termination_z', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'), + ), ] diff --git a/netbox/circuits/migrations/0028_cache_circuit_terminations.py b/netbox/circuits/migrations/0028_cache_circuit_terminations.py new file mode 100644 index 000000000..49631da07 --- /dev/null +++ b/netbox/circuits/migrations/0028_cache_circuit_terminations.py @@ -0,0 +1,37 @@ +import sys + +from django.db import migrations + + +def cache_circuit_terminations(apps, schema_editor): + Circuit = apps.get_model('circuits', 'Circuit') + CircuitTermination = apps.get_model('circuits', 'CircuitTermination') + + if 'test' not in sys.argv: + print(f"\n Caching circuit terminations...", flush=True) + + a_terminations = { + ct.circuit_id: ct.pk for ct in CircuitTermination.objects.filter(term_side='A') + } + z_terminations = { + ct.circuit_id: ct.pk for ct in CircuitTermination.objects.filter(term_side='Z') + } + for circuit in Circuit.objects.all(): + Circuit.objects.filter(pk=circuit.pk).update( + termination_a_id=a_terminations.get(circuit.pk), + termination_z_id=z_terminations.get(circuit.pk), + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0027_cloud'), + ] + + operations = [ + migrations.RunPython( + code=cache_circuit_terminations, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index b13dd9603..c2ff71126 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -9,7 +9,6 @@ from extras.utils import extras_features from netbox.models import BigIDModel, ChangeLoggedModel, OrganizationalModel, PrimaryModel from utilities.querysets import RestrictedQuerySet from .choices import * -from .querysets import CircuitQuerySet __all__ = ( @@ -236,7 +235,25 @@ class Circuit(PrimaryModel): blank=True ) - objects = CircuitQuerySet.as_manager() + # Cache associated CircuitTerminations + termination_a = models.ForeignKey( + to='circuits.CircuitTermination', + on_delete=models.SET_NULL, + related_name='+', + editable=False, + blank=True, + null=True + ) + termination_z = models.ForeignKey( + to='circuits.CircuitTermination', + on_delete=models.SET_NULL, + related_name='+', + editable=False, + blank=True, + null=True + ) + + objects = RestrictedQuerySet.as_manager() csv_headers = [ 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', @@ -271,20 +288,6 @@ class Circuit(PrimaryModel): def get_status_class(self): return CircuitStatusChoices.CSS_CLASSES.get(self.status) - def _get_termination(self, side): - for ct in self.terminations.all(): - if ct.term_side == side: - return ct - return None - - @property - def termination_a(self): - return self._get_termination('A') - - @property - def termination_z(self): - return self._get_termination('Z') - @extras_features('webhooks') class CircuitTermination(ChangeLoggedModel, PathEndpoint, CableTermination): @@ -345,6 +348,9 @@ class CircuitTermination(ChangeLoggedModel, PathEndpoint, CableTermination): unique_together = ['circuit', 'term_side'] def __str__(self): + if self.site: + return str(self.site) + return str(self.cloud) return f"Side {self.get_term_side_display()}" def clean(self): diff --git a/netbox/circuits/querysets.py b/netbox/circuits/querysets.py deleted file mode 100644 index 8a9bd50a4..000000000 --- a/netbox/circuits/querysets.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db.models import OuterRef, Subquery - -from utilities.querysets import RestrictedQuerySet - - -class CircuitQuerySet(RestrictedQuerySet): - - def annotate_sites(self): - """ - Annotate the A and Z termination site names for ordering. - """ - from circuits.models import CircuitTermination - _terminations = CircuitTermination.objects.filter(circuit=OuterRef('pk')) - return self.annotate( - a_side=Subquery(_terminations.filter(term_side='A').values('site__name')[:1]), - z_side=Subquery(_terminations.filter(term_side='Z').values('site__name')[:1]), - ) diff --git a/netbox/circuits/signals.py b/netbox/circuits/signals.py index 86db21400..7c9832d5b 100644 --- a/netbox/circuits/signals.py +++ b/netbox/circuits/signals.py @@ -1,17 +1,17 @@ -from django.db.models.signals import post_delete, post_save +from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone from .models import Circuit, CircuitTermination -@receiver((post_save, post_delete), sender=CircuitTermination) +@receiver(post_save, sender=CircuitTermination) def update_circuit(instance, **kwargs): """ - When a CircuitTermination has been modified, update the last_updated time of its parent Circuit. + When a CircuitTermination has been modified, update its parent Circuit. """ - circuits = Circuit.objects.filter(pk=instance.circuit_id) - time = timezone.now() - for circuit in circuits: - circuit.last_updated = time - circuit.save() + fields = { + 'last_updated': timezone.now(), + f'termination_{instance.term_side.lower()}': instance.pk, + } + Circuit.objects.filter(pk=instance.circuit_id).update(**fields) diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 94894368e..00b4613a7 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -83,11 +83,11 @@ class CircuitTable(BaseTable): ) status = ChoiceFieldColumn() tenant = TenantColumn() - a_side = tables.Column( - verbose_name='A Side' + termination_a = tables.Column( + verbose_name='Side A' ) - z_side = tables.Column( - verbose_name='Z Side' + termination_z = tables.Column( + verbose_name='Side Z' ) tags = TagColumn( url_name='circuits:circuit_list' @@ -96,7 +96,9 @@ class CircuitTable(BaseTable): class Meta(BaseTable.Meta): model = Circuit fields = ( - 'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'install_date', 'commit_rate', - 'description', 'tags', + 'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date', + 'commit_rate', 'description', 'tags', + ) + default_columns = ( + 'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'description', ) - default_columns = ('pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'description') diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 2484a84e4..b67bff81b 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -33,7 +33,7 @@ class ProviderView(generic.ObjectView): provider=instance ).prefetch_related( 'type', 'tenant', 'terminations__site' - ).annotate_sites() + ) circuits_table = tables.CircuitTable(circuits) circuits_table.columns.hide('provider') @@ -172,8 +172,8 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView): class CircuitListView(generic.ObjectListView): queryset = Circuit.objects.prefetch_related( - 'provider', 'type', 'tenant', 'terminations' - ).annotate_sites() + 'provider', 'type', 'tenant', 'termination_a', 'termination_z' + ) filterset = filters.CircuitFilterSet filterset_form = forms.CircuitFilterForm table = tables.CircuitTable diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index 2a466b4cd..797a11965 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -40,7 +40,7 @@ SEARCH_TYPES = OrderedDict(( ('circuit', { 'queryset': Circuit.objects.prefetch_related( 'type', 'provider', 'tenant', 'terminations__site' - ).annotate_sites(), + ), 'filterset': CircuitFilterSet, 'table': CircuitTable, 'url': 'circuits:circuit_list',