common.py 6.8 KB

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