Coverage for dcim/forms/connections.py: 10%
51 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 import forms
2from django.contrib.contenttypes.models import ContentType
3from django.utils.translation import gettext_lazy as _
5from circuits.models import Circuit, CircuitTermination
6from dcim.models import *
7from utilities.forms.fields import DynamicModelMultipleChoiceField
9from .model_forms import CableForm
12def get_cable_form(a_type, b_type):
14 class FormMetaclass(forms.models.ModelFormMetaclass):
16 def __new__(mcs, name, bases, attrs):
18 # NOTE: Cable.clone() mirrors the parent selector mapping below:
19 # termination_{end}_device / termination_{end}_powerpanel / termination_{end}_circuit
20 # This supports both the "Clone" and "Create & Add Another" workflows.
21 # If you change the mapping here, update Cable.clone() accordingly.
22 for cable_end, term_cls in (('a', a_type), ('b', b_type)):
24 # Device component
25 if hasattr(term_cls, 'device'):
27 # Dynamically change the param field for interfaces to use virtual_chassis filter
28 query_param_device_field = 'device_id'
29 if term_cls == Interface:
30 query_param_device_field = 'virtual_chassis_member_or_master_id'
32 attrs[f'termination_{cable_end}_device'] = DynamicModelMultipleChoiceField(
33 queryset=Device.objects.all(),
34 label=_('Device'),
35 required=False,
36 selector=True,
37 initial_params={
38 f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'
39 }
40 )
41 attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
42 queryset=term_cls.objects.all(),
43 label=term_cls._meta.verbose_name.title(),
44 context={
45 'disabled': '_occupied',
46 'parent': 'device',
47 },
48 query_params={
49 query_param_device_field: f'$termination_{cable_end}_device',
50 'kind': 'physical', # Exclude virtual interfaces
51 }
52 )
54 # PowerFeed
55 elif term_cls == PowerFeed:
57 attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelMultipleChoiceField(
58 queryset=PowerPanel.objects.all(),
59 label=_('Power Panel'),
60 required=False,
61 selector=True,
62 initial_params={
63 'powerfeeds__in': f'${cable_end}_terminations'
64 }
65 )
66 attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
67 queryset=term_cls.objects.all(),
68 label=_('Power Feed'),
69 context={
70 'disabled': '_occupied',
71 'parent': 'powerpanel',
72 },
73 query_params={
74 'power_panel_id': f'$termination_{cable_end}_powerpanel',
75 }
76 )
78 # CircuitTermination
79 elif term_cls == CircuitTermination:
81 attrs[f'termination_{cable_end}_circuit'] = DynamicModelMultipleChoiceField(
82 queryset=Circuit.objects.all(),
83 label=_('Circuit'),
84 selector=True,
85 initial_params={
86 'terminations__in': f'${cable_end}_terminations'
87 }
88 )
89 attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
90 queryset=term_cls.objects.all(),
91 label=_('Side'),
92 context={
93 'disabled': '_occupied',
94 'parent': 'circuit',
95 },
96 query_params={
97 'circuit_id': f'$termination_{cable_end}_circuit',
98 }
99 )
101 return super().__new__(mcs, name, bases, attrs)
103 class _CableForm(CableForm, metaclass=FormMetaclass):
105 def __init__(self, *args, initial=None, **kwargs):
106 initial = initial or {}
108 if a_type:
109 a_ct = ContentType.objects.get_for_model(a_type)
110 initial['a_terminations_type'] = f'{a_ct.app_label}.{a_ct.model}'
111 if b_type:
112 b_ct = ContentType.objects.get_for_model(b_type)
113 initial['b_terminations_type'] = f'{b_ct.app_label}.{b_ct.model}'
115 # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()
116 for field_name in ('a_terminations', 'b_terminations'):
117 if field_name in initial and type(initial[field_name]) is not list:
118 initial[field_name] = [initial[field_name]]
120 super().__init__(*args, initial=initial, **kwargs)
122 if self.instance and self.instance.pk:
123 # Initialize A/B terminations when modifying an existing Cable instance
124 if (
125 a_type and self.instance.a_terminations and
126 a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0])
127 ):
128 self.initial['a_terminations'] = self.instance.a_terminations
129 if (
130 b_type and self.instance.b_terminations and
131 b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0])
132 ):
133 self.initial['b_terminations'] = self.instance.b_terminations
134 else:
135 # Need to clear terminations if swapped type - but need to do it only
136 # if not from instance
137 if a_type:
138 initial.pop('a_terminations', None)
139 if b_type:
140 initial.pop('b_terminations', None)
142 def clean(self):
143 super().clean()
145 # Set the A/B terminations on the Cable instance
146 self.instance.a_terminations = self.cleaned_data.get('a_terminations', [])
147 self.instance.b_terminations = self.cleaned_data.get('b_terminations', [])
149 return _CableForm