common.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from django import forms
  2. from django.utils.translation import gettext_lazy as _
  3. from dcim.choices import *
  4. from dcim.constants import *
  5. from utilities.forms import get_field_value
  6. __all__ = (
  7. 'InterfaceCommonForm',
  8. 'ModuleCommonForm'
  9. )
  10. class InterfaceCommonForm(forms.Form):
  11. mtu = forms.IntegerField(
  12. required=False,
  13. min_value=INTERFACE_MTU_MIN,
  14. max_value=INTERFACE_MTU_MAX,
  15. label=_('MTU')
  16. )
  17. def __init__(self, *args, **kwargs):
  18. super().__init__(*args, **kwargs)
  19. # Determine the selected 802.1Q mode
  20. interface_mode = get_field_value(self, 'mode')
  21. # Delete VLAN tagging fields which are not relevant for the selected mode
  22. if interface_mode in (InterfaceModeChoices.MODE_ACCESS, InterfaceModeChoices.MODE_TAGGED_ALL):
  23. del self.fields['tagged_vlans']
  24. elif not interface_mode:
  25. del self.fields['vlan_group']
  26. del self.fields['untagged_vlan']
  27. del self.fields['tagged_vlans']
  28. if interface_mode != InterfaceModeChoices.MODE_Q_IN_Q:
  29. del self.fields['qinq_svlan']
  30. if self.instance and self.instance.pk:
  31. filter_name = f'{self._meta.model._meta.model_name}_id'
  32. self.fields['primary_mac_address'].widget.add_query_param(filter_name, self.instance.pk)
  33. def clean(self):
  34. super().clean()
  35. parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
  36. if 'tagged_vlans' in self.fields.keys():
  37. tagged_vlans = self.cleaned_data.get('tagged_vlans') if self.is_bound else \
  38. self.get_initial_for_field(self.fields['tagged_vlans'], 'tagged_vlans')
  39. else:
  40. tagged_vlans = []
  41. # Validate tagged VLANs; must be a global VLAN or in the same site
  42. if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED and tagged_vlans:
  43. valid_sites = [None, self.cleaned_data[parent_field].site]
  44. invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
  45. if invalid_vlans:
  46. raise forms.ValidationError({
  47. 'tagged_vlans': _(
  48. "The tagged VLANs ({vlans}) must belong to the same site as the interface's parent device/VM, "
  49. "or they must be global"
  50. ).format(vlans=', '.join(invalid_vlans))
  51. })
  52. class ModuleCommonForm(forms.Form):
  53. def _get_module_bay_tree(self, module_bay):
  54. module_bays = []
  55. while module_bay:
  56. module_bays.append(module_bay)
  57. if module_bay.module:
  58. module_bay = module_bay.module.module_bay
  59. else:
  60. module_bay = None
  61. module_bays.reverse()
  62. return module_bays
  63. def clean(self):
  64. super().clean()
  65. replicate_components = self.cleaned_data.get('replicate_components')
  66. adopt_components = self.cleaned_data.get('adopt_components')
  67. device = self.cleaned_data.get('device')
  68. module_type = self.cleaned_data.get('module_type')
  69. module_bay = self.cleaned_data.get('module_bay')
  70. if adopt_components:
  71. self.instance._adopt_components = True
  72. # Bail out if we are not installing a new module or if we are not replicating components (or if
  73. # validation has already failed)
  74. if self.errors or self.instance.pk or not replicate_components:
  75. self.instance._disable_replication = True
  76. return
  77. module_bays = self._get_module_bay_tree(module_bay)
  78. for templates, component_attribute in [
  79. ("consoleporttemplates", "consoleports"),
  80. ("consoleserverporttemplates", "consoleserverports"),
  81. ("interfacetemplates", "interfaces"),
  82. ("powerporttemplates", "powerports"),
  83. ("poweroutlettemplates", "poweroutlets"),
  84. ("rearporttemplates", "rearports"),
  85. ("frontporttemplates", "frontports")
  86. ]:
  87. # Prefetch installed components
  88. installed_components = {
  89. component.name: component for component in getattr(device, component_attribute).all()
  90. }
  91. # Get the templates for the module type.
  92. for template in getattr(module_type, templates).all():
  93. resolved_name = template.name
  94. # Installing modules with placeholders require that the bay has a position value
  95. if MODULE_TOKEN in template.name:
  96. if not module_bay.position:
  97. raise forms.ValidationError(
  98. _("Cannot install module with placeholder values in a module bay with no position defined.")
  99. )
  100. if len(module_bays) != template.name.count(MODULE_TOKEN):
  101. raise forms.ValidationError(
  102. _(
  103. "Cannot install module with placeholder values in a module bay tree {level} in tree "
  104. "but {tokens} placeholders given."
  105. ).format(
  106. level=len(module_bays), tokens=template.name.count(MODULE_TOKEN)
  107. )
  108. )
  109. for module_bay in module_bays:
  110. resolved_name = resolved_name.replace(MODULE_TOKEN, module_bay.position, 1)
  111. existing_item = installed_components.get(resolved_name)
  112. # It is not possible to adopt components already belonging to a module
  113. if adopt_components and existing_item and existing_item.module:
  114. raise forms.ValidationError(
  115. _("Cannot adopt {model} {name} as it already belongs to a module").format(
  116. model=template.component_model.__name__,
  117. name=resolved_name
  118. )
  119. )
  120. # If we are not adopting components we error if the component exists
  121. if not adopt_components and resolved_name in installed_components:
  122. raise forms.ValidationError(
  123. _("A {model} named {name} already exists").format(
  124. model=template.component_model.__name__,
  125. name=resolved_name
  126. )
  127. )