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

1from django import forms 

2from django.contrib.contenttypes.models import ContentType 

3from django.utils.translation import gettext_lazy as _ 

4 

5from circuits.models import Circuit, CircuitTermination 

6from dcim.models import * 

7from utilities.forms.fields import DynamicModelMultipleChoiceField 

8 

9from .model_forms import CableForm 

10 

11 

12def get_cable_form(a_type, b_type): 

13 

14 class FormMetaclass(forms.models.ModelFormMetaclass): 

15 

16 def __new__(mcs, name, bases, attrs): 

17 

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

23 

24 # Device component 

25 if hasattr(term_cls, 'device'): 

26 

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' 

31 

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 ) 

53 

54 # PowerFeed 

55 elif term_cls == PowerFeed: 

56 

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 ) 

77 

78 # CircuitTermination 

79 elif term_cls == CircuitTermination: 

80 

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 ) 

100 

101 return super().__new__(mcs, name, bases, attrs) 

102 

103 class _CableForm(CableForm, metaclass=FormMetaclass): 

104 

105 def __init__(self, *args, initial=None, **kwargs): 

106 initial = initial or {} 

107 

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}' 

114 

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

119 

120 super().__init__(*args, initial=initial, **kwargs) 

121 

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) 

141 

142 def clean(self): 

143 super().clean() 

144 

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', []) 

148 

149 return _CableForm