Compare commits

...

1 Commits

Author SHA1 Message Date
Martin Hauser
951d856c3c feat(dcim): Add Cable cloning with Termination mapping
Introduce `clone()` method for the Cable model to enable cloning
its attributes, including termination type and parent selectors.
Updates mappings to align with CableForm workflows, supporting
"Clone" and "Create & Add Another" actions.

Fixes #21429
2026-02-17 18:30:36 +01:00
3 changed files with 48 additions and 13 deletions

View File

@@ -14,6 +14,10 @@ def get_cable_form(a_type, b_type):
def __new__(mcs, name, bases, attrs):
# NOTE: Cable.clone() mirrors the parent selector mapping below:
# termination_{end}_device / termination_{end}_powerpanel / termination_{end}_circuit
# This supports both the "Clone" and "Create & Add Another" workflows.
# If you change the mapping here, update Cable.clone() accordingly.
for cable_end, term_cls in (('a', a_type), ('b', b_type)):
# Device component

View File

@@ -304,6 +304,50 @@ class Cable(PrimaryModel):
except UnsupportedCablePath as e:
raise AbortRequest(e)
def clone(self):
"""
Return attributes suitable for cloning this cable.
In addition to the fields defined in `clone_fields`, include the termination
type and parent selector fields used by dcim.forms.connections.get_cable_form().
"""
attrs = super().clone()
# Mirror dcim.forms.connections.get_cable_form() parent-field logic
for cable_end, terminations in (('a', self.a_terminations), ('b', self.b_terminations)):
if not terminations:
continue
term_cls = type(terminations[0])
term_label = term_cls._meta.label_lower
# Matches CableForm choices: "<app_label>.<model>"
attrs[f'{cable_end}_terminations_type'] = term_label
# Device component
if hasattr(term_cls, 'device'):
device_ids = sorted({t.device_id for t in terminations if t.device_id})
if device_ids:
attrs[f'termination_{cable_end}_device'] = device_ids
# PowerFeed
elif term_label == 'dcim.powerfeed':
powerpanel_ids = sorted({t.power_panel_id for t in terminations if t.power_panel_id})
if powerpanel_ids:
attrs[f'termination_{cable_end}_powerpanel'] = powerpanel_ids
# CircuitTermination
elif term_label == 'circuits.circuittermination':
circuit_ids = sorted({t.circuit_id for t in terminations if t.circuit_id})
if circuit_ids:
attrs[f'termination_{cable_end}_circuit'] = circuit_ids
# Never clone the actual terminations, as they are already occupied
attrs.pop('a_terminations', None)
attrs.pop('b_terminations', None)
return attrs
def serialize_object(self, exclude=None):
data = serialize_object(self, exclude=exclude or [])

View File

@@ -3902,19 +3902,6 @@ class CableEditView(generic.ObjectEditView):
return super().alter_object(obj, request, url_args, url_kwargs)
def get_extra_addanother_params(self, request):
params = {
'a_terminations_type': request.GET.get('a_terminations_type'),
'b_terminations_type': request.GET.get('b_terminations_type')
}
for key in request.POST:
if 'device' in key or 'power_panel' in key or 'circuit' in key:
params.update({key: request.POST.get(key)})
return params
@register_model_view(Cable, 'delete')
class CableDeleteView(generic.ObjectDeleteView):