mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Add a management command to rebuild all the connected endpoints
This commit is contained in:
parent
3908b905e5
commit
f3c0a88e66
126
netbox/extras/management/commands/retrace.py
Normal file
126
netbox/extras/management/commands/retrace.py
Normal file
@ -0,0 +1,126 @@
|
||||
from django.apps import apps
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from circuits.models import CircuitTermination
|
||||
from dcim.choices import CableStatusChoices
|
||||
from dcim.models import CableTermination, FrontPort, RearPort
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Recalculate connected endpoints 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_models(self, names):
|
||||
"""
|
||||
Compile a list of models to be retraced. If no names are specified, all models which have a connected endpoint
|
||||
will be included.
|
||||
"""
|
||||
models = []
|
||||
|
||||
if names:
|
||||
# Collect all NaturalOrderingFields present on the specified models
|
||||
for name in names:
|
||||
try:
|
||||
app_label, model_name = name.split('.')
|
||||
except ValueError:
|
||||
raise CommandError(
|
||||
"Invalid format: {}. Models must be specified in the form app_label.ModelName.".format(name)
|
||||
)
|
||||
try:
|
||||
app_config = apps.get_app_config(app_label)
|
||||
except LookupError as e:
|
||||
raise CommandError(str(e))
|
||||
try:
|
||||
model = app_config.get_model(model_name)
|
||||
except LookupError:
|
||||
raise CommandError("Unknown model: {}.{}".format(app_label, model_name))
|
||||
|
||||
if not issubclass(model, CableTermination) or not hasattr(model, 'connected_endpoint'):
|
||||
raise CommandError(
|
||||
"Invalid model: {}.{} does not have a connected endpoint".format(app_label, model_name)
|
||||
)
|
||||
models.append(model)
|
||||
|
||||
else:
|
||||
# Find *all* models with NaturalOrderingFields
|
||||
for app_config in apps.get_app_configs():
|
||||
for model in app_config.models.values():
|
||||
if issubclass(model, CableTermination) and hasattr(model, 'connected_endpoint'):
|
||||
models.append(model)
|
||||
|
||||
return models
|
||||
|
||||
def handle(self, *args, verbosity, **options):
|
||||
|
||||
models = self._get_models(args)
|
||||
|
||||
if verbosity >= 1:
|
||||
self.stdout.write("Retracing {} models.".format(len(models)))
|
||||
|
||||
for model in models:
|
||||
# Print the model and field name
|
||||
if verbosity >= 1:
|
||||
self.stdout.write(f"{model._meta.label}...", )
|
||||
self.stdout.flush()
|
||||
|
||||
count = 0
|
||||
|
||||
# Update any endpoints for this Cable.
|
||||
endpoints = model.objects.all()
|
||||
for endpoint in endpoints:
|
||||
path, split_ends, position_stack = endpoint.trace()
|
||||
# Determine overall path status (connected or planned)
|
||||
path_status = True
|
||||
for segment in path:
|
||||
if segment[1] is None or segment[1].status != CableStatusChoices.STATUS_CONNECTED:
|
||||
path_status = False
|
||||
break
|
||||
|
||||
endpoint_a = path[0][0]
|
||||
if not split_ends and not position_stack:
|
||||
endpoint_b = path[-1][2]
|
||||
if endpoint_b is None and len(path) >= 2 and isinstance(path[-2][2], CircuitTermination):
|
||||
# Simulate the previous behaviour and use the circuit termination as connected endpoint
|
||||
endpoint_b = path[-2][2]
|
||||
else:
|
||||
endpoint_b = None
|
||||
|
||||
# Patch panel ports are not connected endpoints, all other cable terminations are
|
||||
if isinstance(endpoint_a, CableTermination) and not isinstance(endpoint_a, (FrontPort, RearPort)) and \
|
||||
isinstance(endpoint_b, CableTermination) and not isinstance(endpoint_b, (FrontPort, RearPort)):
|
||||
if verbosity >= 3:
|
||||
self.stdout.write(f"Updating path endpoints: "
|
||||
f"{endpoint_a.parent} {endpoint_a} <-> {endpoint_b.parent} {endpoint_b}")
|
||||
self.stdout.flush()
|
||||
|
||||
endpoint_a.connected_endpoint = endpoint_b
|
||||
endpoint_a.connection_status = path_status
|
||||
endpoint_a.save()
|
||||
endpoint_b.connected_endpoint = endpoint_a
|
||||
endpoint_b.connection_status = path_status
|
||||
endpoint_b.save()
|
||||
elif endpoint_b is None:
|
||||
if verbosity >= 3:
|
||||
self.stdout.write(f"Clearing path endpoint: {endpoint_a.parent} {endpoint_a}")
|
||||
self.stdout.flush()
|
||||
|
||||
# There is no endpoint, so clean up any left overs
|
||||
endpoint_a.connected_endpoint = None
|
||||
endpoint_a.connection_status = path_status
|
||||
endpoint_a.save()
|
||||
|
||||
count += 1
|
||||
|
||||
# Print the total count of alterations for the field
|
||||
if verbosity >= 2:
|
||||
self.stdout.write(self.style.SUCCESS(f"{count} {model._meta.verbose_name_plural} updated"))
|
||||
elif verbosity >= 1:
|
||||
self.stdout.write(self.style.SUCCESS(str(count)))
|
||||
|
||||
if verbosity >= 1:
|
||||
self.stdout.write(self.style.SUCCESS("Done."))
|
Loading…
Reference in New Issue
Block a user