Просмотр исходного кода

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
Martin Hauser 2 дней назад
Родитель
Сommit
951d856c3c
3 измененных файлов с 48 добавлено и 13 удалено
  1. 4 0
      netbox/dcim/forms/connections.py
  2. 44 0
      netbox/dcim/models/cables.py
  3. 0 13
      netbox/dcim/views.py

+ 4 - 0
netbox/dcim/forms/connections.py

@@ -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

+ 44 - 0
netbox/dcim/models/cables.py

@@ -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 [])
 

+ 0 - 13
netbox/dcim/views.py

@@ -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):