object_create.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. from django import forms
  2. from dcim.models import *
  3. from netbox.forms import NetBoxModelForm
  4. from utilities.forms import (
  5. BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
  6. )
  7. __all__ = (
  8. 'ComponentTemplateCreateForm',
  9. 'DeviceComponentCreateForm',
  10. 'FrontPortCreateForm',
  11. 'FrontPortTemplateCreateForm',
  12. 'InventoryItemCreateForm',
  13. 'ModularComponentTemplateCreateForm',
  14. 'ModuleBayCreateForm',
  15. 'ModuleBayTemplateCreateForm',
  16. 'VirtualChassisCreateForm',
  17. )
  18. class ComponentCreateForm(BootstrapMixin, forms.Form):
  19. """
  20. Subclass this form when facilitating the creation of one or more device component or component templates based on
  21. a name pattern.
  22. """
  23. name_pattern = ExpandableNameField(
  24. label='Name'
  25. )
  26. label_pattern = ExpandableNameField(
  27. label='Label',
  28. required=False,
  29. help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
  30. )
  31. def clean(self):
  32. super().clean()
  33. # Validate that all patterned fields generate an equal number of values
  34. patterned_fields = [
  35. field_name for field_name in self.fields if field_name.endswith('_pattern')
  36. ]
  37. pattern_count = len(self.cleaned_data['name_pattern'])
  38. for field_name in patterned_fields:
  39. value_count = len(self.cleaned_data[field_name])
  40. if self.cleaned_data[field_name] and value_count != pattern_count:
  41. raise forms.ValidationError({
  42. field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are '
  43. f'expected.'
  44. }, code='label_pattern_mismatch')
  45. class ComponentTemplateCreateForm(ComponentCreateForm):
  46. """
  47. Creation form for component templates that can be assigned only to a DeviceType.
  48. """
  49. device_type = DynamicModelChoiceField(
  50. queryset=DeviceType.objects.all(),
  51. )
  52. field_order = ('device_type', 'name_pattern', 'label_pattern')
  53. class ModularComponentTemplateCreateForm(ComponentCreateForm):
  54. """
  55. Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType.
  56. """
  57. name_pattern = ExpandableNameField(
  58. label='Name',
  59. help_text="""
  60. Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
  61. are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>. {module} is accepted as a substitution for
  62. the module bay position.
  63. """
  64. )
  65. device_type = DynamicModelChoiceField(
  66. queryset=DeviceType.objects.all(),
  67. required=False
  68. )
  69. module_type = DynamicModelChoiceField(
  70. queryset=ModuleType.objects.all(),
  71. required=False
  72. )
  73. field_order = ('device_type', 'module_type', 'name_pattern', 'label_pattern')
  74. class DeviceComponentCreateForm(ComponentCreateForm):
  75. device = DynamicModelChoiceField(
  76. queryset=Device.objects.all()
  77. )
  78. field_order = ('device', 'name_pattern', 'label_pattern')
  79. class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
  80. rear_port_set = forms.MultipleChoiceField(
  81. choices=[],
  82. label='Rear ports',
  83. help_text='Select one rear port assignment for each front port being created.',
  84. )
  85. field_order = (
  86. 'device_type', 'name_pattern', 'label_pattern', 'rear_port_set',
  87. )
  88. def __init__(self, *args, **kwargs):
  89. super().__init__(*args, **kwargs)
  90. # TODO: This needs better validation
  91. if 'device_type' in self.initial or self.data.get('device_type'):
  92. parent = DeviceType.objects.get(
  93. pk=self.initial.get('device_type') or self.data.get('device_type')
  94. )
  95. elif 'module_type' in self.initial or self.data.get('module_type'):
  96. parent = ModuleType.objects.get(
  97. pk=self.initial.get('module_type') or self.data.get('module_type')
  98. )
  99. else:
  100. return
  101. # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
  102. occupied_port_positions = [
  103. (front_port.rear_port_id, front_port.rear_port_position)
  104. for front_port in parent.frontporttemplates.all()
  105. ]
  106. # Populate rear port choices
  107. choices = []
  108. rear_ports = parent.rearporttemplates.all()
  109. for rear_port in rear_ports:
  110. for i in range(1, rear_port.positions + 1):
  111. if (rear_port.pk, i) not in occupied_port_positions:
  112. choices.append(
  113. ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
  114. )
  115. self.fields['rear_port_set'].choices = choices
  116. def get_iterative_data(self, iteration):
  117. # Assign rear port and position from selected set
  118. rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
  119. return {
  120. 'rear_port': int(rear_port),
  121. 'rear_port_position': int(position),
  122. }
  123. class FrontPortCreateForm(DeviceComponentCreateForm):
  124. rear_port_set = forms.MultipleChoiceField(
  125. choices=[],
  126. label='Rear ports',
  127. help_text='Select one rear port assignment for each front port being created.',
  128. )
  129. field_order = (
  130. 'device', 'name_pattern', 'label_pattern', 'rear_port_set',
  131. )
  132. def __init__(self, *args, **kwargs):
  133. super().__init__(*args, **kwargs)
  134. device = Device.objects.get(
  135. pk=self.initial.get('device') or self.data.get('device')
  136. )
  137. # Determine which rear port positions are occupied. These will be excluded from the list of available
  138. # mappings.
  139. occupied_port_positions = [
  140. (front_port.rear_port_id, front_port.rear_port_position)
  141. for front_port in device.frontports.all()
  142. ]
  143. # Populate rear port choices
  144. choices = []
  145. rear_ports = RearPort.objects.filter(device=device)
  146. for rear_port in rear_ports:
  147. for i in range(1, rear_port.positions + 1):
  148. if (rear_port.pk, i) not in occupied_port_positions:
  149. choices.append(
  150. ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
  151. )
  152. self.fields['rear_port_set'].choices = choices
  153. def get_iterative_data(self, iteration):
  154. # Assign rear port and position from selected set
  155. rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
  156. return {
  157. 'rear_port': int(rear_port),
  158. 'rear_port_position': int(position),
  159. }
  160. class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
  161. position_pattern = ExpandableNameField(
  162. label='Position',
  163. required=False,
  164. help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
  165. )
  166. field_order = ('device_type', 'name_pattern', 'label_pattern', 'position_pattern')
  167. class ModuleBayCreateForm(DeviceComponentCreateForm):
  168. position_pattern = ExpandableNameField(
  169. label='Position',
  170. required=False,
  171. help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
  172. )
  173. field_order = ('device', 'name_pattern', 'label_pattern', 'position_pattern')
  174. class InventoryItemCreateForm(ComponentCreateForm):
  175. # Device is assigned by the model form
  176. field_order = ('name_pattern', 'label_pattern')
  177. class VirtualChassisCreateForm(NetBoxModelForm):
  178. region = DynamicModelChoiceField(
  179. queryset=Region.objects.all(),
  180. required=False,
  181. initial_params={
  182. 'sites': '$site'
  183. }
  184. )
  185. site_group = DynamicModelChoiceField(
  186. queryset=SiteGroup.objects.all(),
  187. required=False,
  188. initial_params={
  189. 'sites': '$site'
  190. }
  191. )
  192. site = DynamicModelChoiceField(
  193. queryset=Site.objects.all(),
  194. required=False,
  195. query_params={
  196. 'region_id': '$region',
  197. 'group_id': '$site_group',
  198. }
  199. )
  200. rack = DynamicModelChoiceField(
  201. queryset=Rack.objects.all(),
  202. required=False,
  203. null_option='None',
  204. query_params={
  205. 'site_id': '$site'
  206. }
  207. )
  208. members = DynamicModelMultipleChoiceField(
  209. queryset=Device.objects.all(),
  210. required=False,
  211. query_params={
  212. 'site_id': '$site',
  213. 'rack_id': '$rack',
  214. }
  215. )
  216. initial_position = forms.IntegerField(
  217. initial=1,
  218. required=False,
  219. help_text='Position of the first member device. Increases by one for each additional member.'
  220. )
  221. class Meta:
  222. model = VirtualChassis
  223. fields = [
  224. 'name', 'domain', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
  225. ]
  226. def clean(self):
  227. super().clean()
  228. if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
  229. raise forms.ValidationError({
  230. 'initial_position': "A position must be specified for the first VC member."
  231. })
  232. def save(self, *args, **kwargs):
  233. instance = super().save(*args, **kwargs)
  234. # Assign VC members
  235. if instance.pk and self.cleaned_data['members']:
  236. initial_position = self.cleaned_data.get('initial_position', 1)
  237. for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
  238. member.virtual_chassis = instance
  239. member.vc_position = i
  240. member.save()
  241. return instance