common.py 6.0 KB

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