Coverage for dcim/management/commands/trace_paths.py: 46%
54 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.core.management.base import BaseCommand
2from django.core.management.color import no_style
3from django.db import connection
4from django.db.models import Q
6from dcim.models import CablePath, ConsolePort, ConsoleServerPort, Interface, PowerFeed, PowerOutlet, PowerPort
7from dcim.signals import create_cablepaths
9ENDPOINT_MODELS = (
10 ConsolePort,
11 ConsoleServerPort,
12 Interface,
13 PowerFeed,
14 PowerOutlet,
15 PowerPort
16)
19class Command(BaseCommand):
20 help = "Generate any missing cable paths among all cable termination objects in NetBox"
22 def add_arguments(self, parser):
23 parser.add_argument(
24 "--force", action='store_true', dest='force',
25 help="Force recalculation of all existing cable paths"
26 )
27 parser.add_argument(
28 "--no-input", action='store_true', dest='no_input',
29 help="Do not prompt user for any input/confirmation"
30 )
32 def draw_progress_bar(self, percentage):
33 """
34 Draw a simple progress bar 20 increments wide illustrating the specified percentage.
35 """
36 bar_size = int(percentage / 5)
37 self.stdout.write(f"\r [{'#' * bar_size}{' ' * (20 - bar_size)}] {int(percentage)}%", ending='')
39 def handle(self, *model_names, **options):
41 # If --force was passed, first delete all existing CablePaths
42 if options['force']: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true
43 cable_paths = CablePath.objects.all()
44 paths_count = cable_paths.count()
46 # Prompt the user to confirm recalculation of all paths
47 if paths_count and not options['no_input']:
48 self.stdout.write(self.style.ERROR("WARNING: Forcing recalculation of all cable paths."))
49 self.stdout.write(
50 f"This will delete and recalculate all {paths_count} existing cable paths. Are you sure?"
51 )
52 confirmation = input("Type yes to confirm: ")
53 if confirmation != 'yes':
54 self.stdout.write(self.style.SUCCESS("Aborting"))
55 return
57 # Delete all existing CablePath instances
58 self.stdout.write(f"Deleting {paths_count} existing cable paths...")
59 deleted_count, _ = CablePath.objects.all().delete()
60 self.stdout.write(self.style.SUCCESS(f' Deleted {deleted_count} paths'))
62 # Reinitialize the model's PK sequence
63 self.stdout.write('Resetting database sequence for CablePath model')
64 sequence_sql = connection.ops.sequence_reset_sql(no_style(), [CablePath])
65 with connection.cursor() as cursor:
66 for sql in sequence_sql:
67 cursor.execute(sql)
69 # Retrace paths
70 for model in ENDPOINT_MODELS:
71 params = Q(cable__isnull=False)
72 if hasattr(model, 'wireless_link'):
73 params |= Q(wireless_link__isnull=False)
74 origins = model.objects.filter(params)
75 if not options['force']: 75 ↛ 77line 75 didn't jump to line 77 because the condition on line 75 was always true
76 origins = origins.filter(_path__isnull=True)
77 origins_count = origins.count()
78 if not origins_count: 78 ↛ 81line 78 didn't jump to line 81 because the condition on line 78 was always true
79 self.stdout.write(f'Found no missing {model._meta.verbose_name} paths; skipping')
80 continue
81 self.stdout.write(f'Retracing {origins_count} cabled {model._meta.verbose_name_plural}...')
82 i = 0
83 for i, obj in enumerate(origins, start=1):
84 create_cablepaths([obj])
85 if not i % 100:
86 self.draw_progress_bar(i * 100 / origins_count)
87 self.draw_progress_bar(100)
88 self.stdout.write(self.style.SUCCESS(f'\n Retraced {i} {model._meta.verbose_name_plural}'))
90 self.stdout.write(self.style.SUCCESS('Finished.'))