object_create.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. from django import forms
  2. from django.utils.translation import gettext as _
  3. from dcim.models import *
  4. from netbox.forms import NetBoxModelForm
  5. from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
  6. from . import model_forms
  7. __all__ = (
  8. 'ComponentCreateForm',
  9. 'ConsolePortCreateForm',
  10. 'ConsolePortTemplateCreateForm',
  11. 'ConsoleServerPortCreateForm',
  12. 'ConsoleServerPortTemplateCreateForm',
  13. 'DeviceBayCreateForm',
  14. 'DeviceBayTemplateCreateForm',
  15. 'FrontPortCreateForm',
  16. 'FrontPortTemplateCreateForm',
  17. 'InterfaceCreateForm',
  18. 'InterfaceTemplateCreateForm',
  19. 'InventoryItemCreateForm',
  20. 'InventoryItemTemplateCreateForm',
  21. 'ModuleBayCreateForm',
  22. 'ModuleBayTemplateCreateForm',
  23. 'PowerOutletCreateForm',
  24. 'PowerOutletTemplateCreateForm',
  25. 'PowerPortCreateForm',
  26. 'PowerPortTemplateCreateForm',
  27. 'RearPortCreateForm',
  28. 'RearPortTemplateCreateForm',
  29. 'VirtualChassisCreateForm',
  30. )
  31. class ComponentCreateForm(forms.Form):
  32. """
  33. Subclass this form when facilitating the creation of one or more component or component template objects based on
  34. a name pattern.
  35. """
  36. name = ExpandableNameField()
  37. label = ExpandableNameField(
  38. required=False,
  39. help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
  40. )
  41. # Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
  42. # ComponentCreateView when creating objects.
  43. replication_fields = ('name', 'label')
  44. def clean(self):
  45. super().clean()
  46. # Validate that all replication fields generate an equal number of values
  47. pattern_count = len(self.cleaned_data[self.replication_fields[0]])
  48. for field_name in self.replication_fields:
  49. value_count = len(self.cleaned_data[field_name])
  50. if self.cleaned_data[field_name] and value_count != pattern_count:
  51. raise forms.ValidationError({
  52. field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are '
  53. f'expected.'
  54. }, code='label_pattern_mismatch')
  55. #
  56. # Device component templates
  57. #
  58. class ConsolePortTemplateCreateForm(ComponentCreateForm, model_forms.ConsolePortTemplateForm):
  59. class Meta(model_forms.ConsolePortTemplateForm.Meta):
  60. exclude = ('name', 'label')
  61. class ConsoleServerPortTemplateCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortTemplateForm):
  62. class Meta(model_forms.ConsoleServerPortTemplateForm.Meta):
  63. exclude = ('name', 'label')
  64. class PowerPortTemplateCreateForm(ComponentCreateForm, model_forms.PowerPortTemplateForm):
  65. class Meta(model_forms.PowerPortTemplateForm.Meta):
  66. exclude = ('name', 'label')
  67. class PowerOutletTemplateCreateForm(ComponentCreateForm, model_forms.PowerOutletTemplateForm):
  68. class Meta(model_forms.PowerOutletTemplateForm.Meta):
  69. exclude = ('name', 'label')
  70. class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemplateForm):
  71. class Meta(model_forms.InterfaceTemplateForm.Meta):
  72. exclude = ('name', 'label')
  73. class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
  74. rear_port = forms.MultipleChoiceField(
  75. choices=[],
  76. label=_('Rear ports'),
  77. help_text=_('Select one rear port assignment for each front port being created.'),
  78. )
  79. # Override fieldsets from FrontPortTemplateForm to omit rear_port_position
  80. fieldsets = (
  81. (None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description')),
  82. )
  83. class Meta(model_forms.FrontPortTemplateForm.Meta):
  84. exclude = ('name', 'label', 'rear_port', 'rear_port_position')
  85. def __init__(self, *args, **kwargs):
  86. super().__init__(*args, **kwargs)
  87. # TODO: This needs better validation
  88. if 'device_type' in self.initial or self.data.get('device_type'):
  89. parent = DeviceType.objects.get(
  90. pk=self.initial.get('device_type') or self.data.get('device_type')
  91. )
  92. elif 'module_type' in self.initial or self.data.get('module_type'):
  93. parent = ModuleType.objects.get(
  94. pk=self.initial.get('module_type') or self.data.get('module_type')
  95. )
  96. else:
  97. return
  98. # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
  99. occupied_port_positions = [
  100. (front_port.rear_port_id, front_port.rear_port_position)
  101. for front_port in parent.frontporttemplates.all()
  102. ]
  103. # Populate rear port choices
  104. choices = []
  105. rear_ports = parent.rearporttemplates.all()
  106. for rear_port in rear_ports:
  107. for i in range(1, rear_port.positions + 1):
  108. if (rear_port.pk, i) not in occupied_port_positions:
  109. choices.append(
  110. ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
  111. )
  112. self.fields['rear_port'].choices = choices
  113. def get_iterative_data(self, iteration):
  114. # Assign rear port and position from selected set
  115. rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
  116. return {
  117. 'rear_port': int(rear_port),
  118. 'rear_port_position': int(position),
  119. }
  120. class RearPortTemplateCreateForm(ComponentCreateForm, model_forms.RearPortTemplateForm):
  121. class Meta(model_forms.RearPortTemplateForm.Meta):
  122. exclude = ('name', 'label')
  123. class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemplateForm):
  124. class Meta(model_forms.DeviceBayTemplateForm.Meta):
  125. exclude = ('name', 'label')
  126. class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
  127. position = ExpandableNameField(
  128. label=_('Position'),
  129. required=False,
  130. help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
  131. )
  132. replication_fields = ('name', 'label', 'position')
  133. class Meta(model_forms.ModuleBayTemplateForm.Meta):
  134. exclude = ('name', 'label', 'position')
  135. class InventoryItemTemplateCreateForm(ComponentCreateForm, model_forms.InventoryItemTemplateForm):
  136. class Meta(model_forms.InventoryItemTemplateForm.Meta):
  137. exclude = ('name', 'label')
  138. #
  139. # Device components
  140. #
  141. class ConsolePortCreateForm(ComponentCreateForm, model_forms.ConsolePortForm):
  142. class Meta(model_forms.ConsolePortForm.Meta):
  143. exclude = ('name', 'label')
  144. class ConsoleServerPortCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortForm):
  145. class Meta(model_forms.ConsoleServerPortForm.Meta):
  146. exclude = ('name', 'label')
  147. class PowerPortCreateForm(ComponentCreateForm, model_forms.PowerPortForm):
  148. class Meta(model_forms.PowerPortForm.Meta):
  149. exclude = ('name', 'label')
  150. class PowerOutletCreateForm(ComponentCreateForm, model_forms.PowerOutletForm):
  151. class Meta(model_forms.PowerOutletForm.Meta):
  152. exclude = ('name', 'label')
  153. class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
  154. class Meta(model_forms.InterfaceForm.Meta):
  155. exclude = ('name', 'label')
  156. def __init__(self, *args, **kwargs):
  157. super().__init__(*args, **kwargs)
  158. if 'module' in self.fields:
  159. self.fields['name'].help_text += ' The string <code>{module}</code> will be replaced with the position ' \
  160. 'of the assigned module, if any'
  161. class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
  162. rear_port = forms.MultipleChoiceField(
  163. choices=[],
  164. label=_('Rear ports'),
  165. help_text=_('Select one rear port assignment for each front port being created.'),
  166. )
  167. # Override fieldsets from FrontPortForm to omit rear_port_position
  168. fieldsets = (
  169. (None, (
  170. 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
  171. )),
  172. )
  173. class Meta(model_forms.FrontPortForm.Meta):
  174. exclude = ('name', 'label', 'rear_port', 'rear_port_position')
  175. def __init__(self, *args, **kwargs):
  176. super().__init__(*args, **kwargs)
  177. device = Device.objects.get(
  178. pk=self.initial.get('device') or self.data.get('device')
  179. )
  180. # Determine which rear port positions are occupied. These will be excluded from the list of available
  181. # mappings.
  182. occupied_port_positions = [
  183. (front_port.rear_port_id, front_port.rear_port_position)
  184. for front_port in device.frontports.all()
  185. ]
  186. # Populate rear port choices
  187. choices = []
  188. rear_ports = RearPort.objects.filter(device=device)
  189. for rear_port in rear_ports:
  190. for i in range(1, rear_port.positions + 1):
  191. if (rear_port.pk, i) not in occupied_port_positions:
  192. choices.append(
  193. ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
  194. )
  195. self.fields['rear_port'].choices = choices
  196. def get_iterative_data(self, iteration):
  197. # Assign rear port and position from selected set
  198. rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
  199. return {
  200. 'rear_port': int(rear_port),
  201. 'rear_port_position': int(position),
  202. }
  203. class RearPortCreateForm(ComponentCreateForm, model_forms.RearPortForm):
  204. class Meta(model_forms.RearPortForm.Meta):
  205. exclude = ('name', 'label')
  206. class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
  207. class Meta(model_forms.DeviceBayForm.Meta):
  208. exclude = ('name', 'label')
  209. class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
  210. position = ExpandableNameField(
  211. label=_('Position'),
  212. required=False,
  213. help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
  214. )
  215. replication_fields = ('name', 'label', 'position')
  216. class Meta(model_forms.ModuleBayForm.Meta):
  217. exclude = ('name', 'label', 'position')
  218. class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm):
  219. class Meta(model_forms.InventoryItemForm.Meta):
  220. exclude = ('name', 'label')
  221. #
  222. # Virtual chassis
  223. #
  224. class VirtualChassisCreateForm(NetBoxModelForm):
  225. region = DynamicModelChoiceField(
  226. queryset=Region.objects.all(),
  227. required=False,
  228. initial_params={
  229. 'sites': '$site'
  230. }
  231. )
  232. site_group = DynamicModelChoiceField(
  233. queryset=SiteGroup.objects.all(),
  234. required=False,
  235. initial_params={
  236. 'sites': '$site'
  237. }
  238. )
  239. site = DynamicModelChoiceField(
  240. queryset=Site.objects.all(),
  241. required=False,
  242. query_params={
  243. 'region_id': '$region',
  244. 'group_id': '$site_group',
  245. }
  246. )
  247. rack = DynamicModelChoiceField(
  248. queryset=Rack.objects.all(),
  249. required=False,
  250. null_option='None',
  251. query_params={
  252. 'site_id': '$site'
  253. }
  254. )
  255. members = DynamicModelMultipleChoiceField(
  256. queryset=Device.objects.all(),
  257. required=False,
  258. query_params={
  259. 'site_id': '$site',
  260. 'rack_id': '$rack',
  261. }
  262. )
  263. initial_position = forms.IntegerField(
  264. initial=1,
  265. required=False,
  266. help_text=_('Position of the first member device. Increases by one for each additional member.')
  267. )
  268. class Meta:
  269. model = VirtualChassis
  270. fields = [
  271. 'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
  272. ]
  273. def clean(self):
  274. super().clean()
  275. if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
  276. raise forms.ValidationError({
  277. 'initial_position': "A position must be specified for the first VC member."
  278. })
  279. def save(self, *args, **kwargs):
  280. instance = super().save(*args, **kwargs)
  281. # Assign VC members
  282. if instance.pk and self.cleaned_data['members']:
  283. initial_position = self.cleaned_data.get('initial_position', 1)
  284. for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
  285. member.virtual_chassis = instance
  286. member.vc_position = i
  287. member.save()
  288. return instance