feat: automate patchpanel mappings and cable tenancy

This commit is contained in:
2026-08-06 15:09:08 +02:00
parent e1153cfce4
commit 0c4a737d41
9 changed files with 405 additions and 20 deletions
+96 -12
View File
@@ -42,25 +42,105 @@ def tenant_id_from_object(obj, depth=0):
def _related_object(form, field_name):
objects = _related_objects(form, field_name)
return objects[0] if objects else None
def _related_objects(form, field_name):
field = form.fields.get(field_name)
if field is None or not hasattr(field, "queryset"):
return None
return []
value = None
values = []
if form.is_bound:
value = form.data.get(form.add_prefix(field_name))
if not value:
value = form.initial.get(field_name)
if hasattr(value, "_meta"):
return value
if isinstance(value, (list, tuple)):
value = value[0] if value else None
if not value:
return None
field_name = form.add_prefix(field_name)
if hasattr(form.data, "getlist"):
values = form.data.getlist(field_name)
else:
values = form.data.get(field_name)
if not values:
values = form.initial.get(field_name)
if not isinstance(values, (list, tuple, set)):
values = [values]
objects = [value for value in values if hasattr(value, "_meta")]
object_ids = [value for value in values if value and not hasattr(value, "_meta")]
if not object_ids:
return objects
try:
return field.queryset.filter(pk=value).first()
objects.extend(field.queryset.filter(pk__in=object_ids))
except (TypeError, ValueError):
pass
return objects
def _is_cable(instance):
meta = getattr(instance, "_meta", None)
return bool(
meta
and getattr(meta, "app_label", None) == "dcim"
and getattr(meta, "model_name", None) == "cable"
)
def _instance_cable_terminations(instance):
if not _is_cable(instance):
return []
terminations = []
for name in ("a_terminations", "b_terminations"):
try:
values = getattr(instance, name, None) or []
except (ObjectDoesNotExist, ValueError):
continue
terminations.extend(values)
return terminations
def tenant_id_from_cable_terminations(terminations):
"""Return the common derivable tenant, or None for no/conflicting tenants."""
tenant_ids = {tenant_id for obj in terminations if (tenant_id := tenant_id_from_object(obj))}
if len(tenant_ids) == 1:
return tenant_ids.pop()
return None
def _cable_form_tenant_result(form):
instance = getattr(form, "instance", None)
is_cable_form = _is_cable(instance) or any(name in form.fields for name in ("a_terminations", "b_terminations"))
if not is_cable_form:
return None, False
terminations = []
for field_name in (
"a_terminations",
"b_terminations",
"termination_a_device",
"termination_b_device",
"termination_a_powerpanel",
"termination_b_powerpanel",
"termination_a_circuit",
"termination_b_circuit",
):
terminations.extend(_related_objects(form, field_name))
if not terminations:
terminations = _instance_cable_terminations(instance)
tenant_ids = {tenant_id for obj in terminations if (tenant_id := tenant_id_from_object(obj))}
if len(tenant_ids) == 1:
return tenant_ids.pop(), True
if len(tenant_ids) > 1:
return None, True
return None, False
def apply_cable_instance_tenant(instance):
"""Assign a cable's tenant when all derivable terminations agree."""
if not _is_cable(instance) or getattr(instance, "tenant_id", None):
return None
tenant_id = tenant_id_from_cable_terminations(_instance_cable_terminations(instance))
if tenant_id:
instance.tenant_id = tenant_id
return tenant_id
def infer_tenant_id(form):
@@ -77,6 +157,10 @@ def infer_tenant_id(form):
except (TypeError, ValueError):
pass
cable_tenant_id, cable_context_found = _cable_form_tenant_result(form)
if cable_context_found:
return cable_tenant_id
for relation in PARENT_RELATIONS:
if tenant_id := tenant_id_from_object(_related_object(form, relation)):
return tenant_id