Coverage for dcim/forms/object_import.py: 47%

146 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django import forms 

2from django.utils.translation import gettext_lazy as _ 

3 

4from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices 

5from dcim.models import * 

6from wireless.choices import WirelessRoleChoices 

7 

8__all__ = ( 

9 'ConsolePortTemplateImportForm', 

10 'ConsoleServerPortTemplateImportForm', 

11 'CoolingIntakeTemplateImportForm', 

12 'CoolingOutflowTemplateImportForm', 

13 'DeviceBayTemplateImportForm', 

14 'FrontPortTemplateImportForm', 

15 'InterfaceTemplateImportForm', 

16 'InventoryItemTemplateImportForm', 

17 'ModuleBayTemplateImportForm', 

18 'PortTemplateMappingImportForm', 

19 'PowerOutletTemplateImportForm', 

20 'PowerPortTemplateImportForm', 

21 'RearPortTemplateImportForm', 

22) 

23 

24 

25# 

26# Component template import forms 

27# 

28 

29class ConsolePortTemplateImportForm(forms.ModelForm): 

30 

31 class Meta: 

32 model = ConsolePortTemplate 

33 fields = [ 

34 'device_type', 'module_type', 'name', 'label', 'type', 'description', 

35 ] 

36 

37 

38class ConsoleServerPortTemplateImportForm(forms.ModelForm): 

39 

40 class Meta: 

41 model = ConsoleServerPortTemplate 

42 fields = [ 

43 'device_type', 'module_type', 'name', 'label', 'type', 'description', 

44 ] 

45 

46 

47class PowerPortTemplateImportForm(forms.ModelForm): 

48 

49 class Meta: 

50 model = PowerPortTemplate 

51 fields = [ 

52 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 

53 ] 

54 

55 

56class PowerOutletTemplateImportForm(forms.ModelForm): 

57 power_port = forms.ModelChoiceField( 

58 label=_('Power port'), 

59 queryset=PowerPortTemplate.objects.all(), 

60 to_field_name='name', 

61 required=False 

62 ) 

63 

64 class Meta: 

65 model = PowerOutletTemplate 

66 fields = [ 

67 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 

68 ] 

69 

70 def clean_device_type(self): 

71 if device_type := self.cleaned_data['device_type']: 

72 power_port = self.fields['power_port'] 

73 power_port.queryset = power_port.queryset.filter(device_type=device_type) 

74 

75 return device_type 

76 

77 def clean_module_type(self): 

78 if module_type := self.cleaned_data['module_type']: 

79 power_port = self.fields['power_port'] 

80 power_port.queryset = power_port.queryset.filter(module_type=module_type) 

81 

82 return module_type 

83 

84 

85class CoolingIntakeTemplateImportForm(forms.ModelForm): 

86 

87 class Meta: 

88 model = CoolingIntakeTemplate 

89 fields = [ 

90 'device_type', 'module_type', 'name', 'label', 'type', 'diameter', 'diameter_unit', 

91 'max_flow', 'max_flow_unit', 'description', 

92 ] 

93 

94 

95class CoolingOutflowTemplateImportForm(forms.ModelForm): 

96 cooling_intake = forms.ModelChoiceField( 

97 label=_('Cooling intake'), 

98 queryset=CoolingIntakeTemplate.objects.all(), 

99 to_field_name='name', 

100 required=False 

101 ) 

102 

103 class Meta: 

104 model = CoolingOutflowTemplate 

105 fields = [ 

106 'device_type', 'module_type', 'name', 'label', 'type', 'diameter', 'diameter_unit', 

107 'cooling_intake', 'description', 

108 ] 

109 

110 def clean_device_type(self): 

111 if device_type := self.cleaned_data['device_type']: 

112 cooling_intake = self.fields['cooling_intake'] 

113 cooling_intake.queryset = cooling_intake.queryset.filter(device_type=device_type) 

114 

115 return device_type 

116 

117 def clean_module_type(self): 

118 if module_type := self.cleaned_data['module_type']: 

119 cooling_intake = self.fields['cooling_intake'] 

120 cooling_intake.queryset = cooling_intake.queryset.filter(module_type=module_type) 

121 

122 return module_type 

123 

124 

125class InterfaceTemplateImportForm(forms.ModelForm): 

126 parent = forms.ModelChoiceField( 

127 label=_('Parent'), 

128 queryset=InterfaceTemplate.objects.all(), 

129 to_field_name='name', 

130 required=False 

131 ) 

132 type = forms.ChoiceField( 

133 label=_('Type'), 

134 choices=InterfaceTypeChoices.CHOICES 

135 ) 

136 poe_mode = forms.ChoiceField( 

137 choices=InterfacePoEModeChoices, 

138 required=False, 

139 label=_('PoE mode') 

140 ) 

141 poe_type = forms.ChoiceField( 

142 choices=InterfacePoETypeChoices, 

143 required=False, 

144 label=_('PoE type') 

145 ) 

146 rf_role = forms.ChoiceField( 

147 choices=WirelessRoleChoices, 

148 required=False, 

149 label=_('Wireless role') 

150 ) 

151 

152 class Meta: 

153 model = InterfaceTemplate 

154 fields = [ 

155 'device_type', 'module_type', 'name', 'label', 'type', 'channels', 'channel_id', 'parent', 'enabled', 

156 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'rf_role', 

157 ] 

158 

159 def clean_device_type(self): 

160 if device_type := self.cleaned_data['device_type']: 

161 parent = self.fields['parent'] 

162 parent.queryset = parent.queryset.filter(device_type=device_type) 

163 

164 return device_type 

165 

166 def clean_module_type(self): 

167 if module_type := self.cleaned_data['module_type']: 

168 parent = self.fields['parent'] 

169 parent.queryset = parent.queryset.filter(module_type=module_type) 

170 

171 return module_type 

172 

173 

174class FrontPortTemplateImportForm(forms.ModelForm): 

175 type = forms.ChoiceField( 

176 label=_('Type'), 

177 choices=PortTypeChoices.CHOICES 

178 ) 

179 

180 class Meta: 

181 model = FrontPortTemplate 

182 fields = [ 

183 'device_type', 'module_type', 'name', 'type', 'color', 'positions', 'label', 'description', 

184 ] 

185 

186 

187class RearPortTemplateImportForm(forms.ModelForm): 

188 type = forms.ChoiceField( 

189 label=_('Type'), 

190 choices=PortTypeChoices.CHOICES 

191 ) 

192 

193 class Meta: 

194 model = RearPortTemplate 

195 fields = [ 

196 'device_type', 'module_type', 'name', 'type', 'color', 'positions', 'label', 'description', 

197 ] 

198 

199 

200class PortTemplateMappingImportForm(forms.ModelForm): 

201 front_port = forms.ModelChoiceField( 

202 label=_('Front port'), 

203 queryset=FrontPortTemplate.objects.all(), 

204 to_field_name='name', 

205 ) 

206 rear_port = forms.ModelChoiceField( 

207 label=_('Rear port'), 

208 queryset=RearPortTemplate.objects.all(), 

209 to_field_name='name', 

210 ) 

211 

212 class Meta: 

213 model = PortTemplateMapping 

214 fields = [ 

215 'device_type', 'module_type', 'front_port', 'front_port_position', 'rear_port', 'rear_port_position', 

216 ] 

217 

218 def clean_device_type(self): 

219 if device_type := self.cleaned_data['device_type']: 

220 front_port = self.fields['front_port'] 

221 rear_port = self.fields['rear_port'] 

222 front_port.queryset = front_port.queryset.filter(device_type=device_type) 

223 rear_port.queryset = rear_port.queryset.filter(device_type=device_type) 

224 return device_type 

225 

226 def clean_module_type(self): 

227 if module_type := self.cleaned_data['module_type']: 

228 front_port = self.fields['front_port'] 

229 rear_port = self.fields['rear_port'] 

230 front_port.queryset = front_port.queryset.filter(module_type=module_type) 

231 rear_port.queryset = rear_port.queryset.filter(module_type=module_type) 

232 return module_type 

233 

234 

235class ModuleBayTemplateImportForm(forms.ModelForm): 

236 module_bay_types = forms.ModelMultipleChoiceField( 

237 label=_('Module bay types'), 

238 queryset=ModuleBayType.objects.all(), 

239 to_field_name='name', 

240 required=False, 

241 ) 

242 

243 class Meta: 

244 model = ModuleBayTemplate 

245 fields = [ 

246 'device_type', 'module_type', 'name', 'label', 'position', 'enabled', 'description', 

247 'module_bay_types', 

248 ] 

249 

250 def clean(self): 

251 """ 

252 Resolve each referenced bay type name against the parent type's own manufacturer, plus 

253 bay types having no manufacturer (global), preferring a manufacturer-specific match 

254 over a global one. ModuleBayType's unique constraint is on (manufacturer, name), not 

255 name alone, so a name can legitimately match both, and the field's name-based lookup 

256 resolves every match into cleaned_data rather than picking one. 

257 

258 This runs in clean() rather than in clean_module_bay_types() so that it does not depend 

259 on the parent having been cleaned first, which would make it sensitive to the order of 

260 Meta.fields. 

261 """ 

262 super().clean() 

263 

264 module_bay_types = self.cleaned_data.get('module_bay_types') 

265 if not module_bay_types: 

266 return 

267 

268 # If neither parent resolved, leave the field alone: ModularComponentTemplateModel.clean() 

269 # rejects a parentless template in _post_clean(), and reporting unresolvable names on top 

270 # of that would just be noise. 

271 parent = self.cleaned_data.get('device_type') or self.cleaned_data.get('module_type') 

272 if parent is None: 

273 return 

274 

275 by_name = {} 

276 for module_bay_type in module_bay_types: 

277 if module_bay_type.manufacturer_id not in (None, parent.manufacturer_id): 

278 continue 

279 existing = by_name.get(module_bay_type.name) 

280 if existing is None or module_bay_type.manufacturer_id is not None: 

281 by_name[module_bay_type.name] = module_bay_type 

282 

283 # A name matching only some other manufacturer's bay type is rejected rather than 

284 # resolved to it. 

285 for module_bay_type in module_bay_types: 

286 if module_bay_type.name not in by_name: 

287 raise forms.ValidationError({ 

288 'module_bay_types': forms.ValidationError( 

289 self.fields['module_bay_types'].error_messages['invalid_choice'], 

290 code='invalid_choice', 

291 params={'value': module_bay_type.name}, 

292 ) 

293 }) 

294 

295 self.cleaned_data['module_bay_types'] = list(by_name.values()) 

296 

297 

298class DeviceBayTemplateImportForm(forms.ModelForm): 

299 

300 class Meta: 

301 model = DeviceBayTemplate 

302 fields = [ 

303 'device_type', 'name', 'label', 'enabled', 'description', 

304 ] 

305 

306 

307class InventoryItemTemplateImportForm(forms.ModelForm): 

308 parent = forms.ModelChoiceField( 

309 label=_('Parent'), 

310 queryset=InventoryItemTemplate.objects.all(), 

311 required=False 

312 ) 

313 role = forms.ModelChoiceField( 

314 label=_('Role'), 

315 queryset=InventoryItemRole.objects.all(), 

316 to_field_name='name', 

317 required=False 

318 ) 

319 manufacturer = forms.ModelChoiceField( 

320 label=_('Manufacturer'), 

321 queryset=Manufacturer.objects.all(), 

322 to_field_name='name', 

323 required=False 

324 ) 

325 

326 class Meta: 

327 model = InventoryItemTemplate 

328 fields = [ 

329 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description', 

330 ] 

331 

332 def clean_device_type(self): 

333 if device_type := self.cleaned_data['device_type']: 

334 parent = self.fields['parent'] 

335 parent.queryset = parent.queryset.filter(device_type=device_type) 

336 

337 return device_type 

338 

339 def clean_module_type(self): 

340 if module_type := self.cleaned_data['module_type']: 

341 parent = self.fields['parent'] 

342 parent.queryset = parent.queryset.filter(module_type=module_type) 

343 

344 return module_type