diff --git a/netbox/dcim/management/__init__.py b/netbox/dcim/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/dcim/management/commands/__init__.py b/netbox/dcim/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/dcim/management/commands/retrace_paths.py b/netbox/dcim/management/commands/retrace_paths.py new file mode 100644 index 000000000..76c29e89c --- /dev/null +++ b/netbox/dcim/management/commands/retrace_paths.py @@ -0,0 +1,67 @@ +from django.contrib.contenttypes.models import ContentType +from django.core.management.base import BaseCommand +from django.core.management.color import no_style +from django.db import connection +from django.db.models import Q + +from dcim.models import CablePath, Interface +from dcim.signals import create_cablepaths + +ENDPOINT_MODELS = ( + 'circuits.CircuitTermination', + 'dcim.ConsolePort', + 'dcim.ConsoleServerPort', + 'dcim.Interface', + 'dcim.PowerOutlet', + 'dcim.PowerPort', +) + + +class Command(BaseCommand): + help = "Recalculate natural ordering values for the specified models" + + def add_arguments(self, parser): + parser.add_argument( + 'args', metavar='app_label.ModelName', nargs='*', + help='One or more specific models (each prefixed with its app_label) to retrace', + ) + + def _get_content_types(self, model_names): + q = Q() + for model_name in model_names: + app_label, model = model_name.split('.') + q |= Q(app_label=app_label, model=model) + return ContentType.objects.filter(q) + + def handle(self, *model_names, **options): + # Determine the models for which we're retracing all paths + origin_types = self._get_content_types(model_names or ENDPOINT_MODELS) + self.stdout.write(f"Retracing paths for models: {', '.join([str(ct) for ct in origin_types])}") + + # Delete all existing CablePath instances + self.stdout.write(f"Deleting existing cable paths...") + deleted_count, _ = CablePath.objects.filter(origin_type__in=origin_types).delete() + self.stdout.write((self.style.SUCCESS(f' Deleted {deleted_count} paths'))) + + # Reset the SQL sequence. Can do this only if deleting _all_ CablePaths. + if not CablePath.objects.count(): + self.stdout.write(f'Resetting database sequence for CablePath...') + sequence_sql = connection.ops.sequence_reset_sql(no_style(), [CablePath]) + with connection.cursor() as cursor: + for sql in sequence_sql: + cursor.execute(sql) + self.stdout.write(self.style.SUCCESS(' Success.')) + + # Retrace interfaces + for ct in origin_types: + model = ct.model_class() + origins = model.objects.filter(cable__isnull=False) + print(f'Retracing {origins.count()} cabled {model._meta.verbose_name_plural}...') + i = 0 + for i, obj in enumerate(origins, start=1): + create_cablepaths(obj) + if not i % 1000: + self.stdout.write(f' {i}') + self.stdout.write(self.style.SUCCESS(f' Retraced {i} {model._meta.verbose_name_plural}')) + + self.stdout.write(self.style.SUCCESS('Finished.'))