| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- from django import forms
- from django.apps import apps
- from django.contrib.contenttypes.models import ContentType
- from django.core.exceptions import ValidationError
- from django.utils.translation import gettext_lazy as _
- from dcim.forms.common import InterfaceCommonForm
- from dcim.forms.mixins import ScopedForm
- from dcim.models import Device, DeviceRole, MACAddress, Platform, Rack, Region, Site, SiteGroup
- from extras.models import ConfigTemplate
- from ipam.choices import VLANQinQRoleChoices
- from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
- from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
- from netbox.forms.mixins import OwnerMixin
- from tenancy.forms import TenancyForm
- from utilities.forms import ConfirmationForm
- from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField
- from utilities.forms.rendering import FieldSet
- from utilities.forms.widgets import HTMXSelect
- from virtualization.models import *
- __all__ = (
- 'ClusterAddDevicesForm',
- 'ClusterForm',
- 'ClusterGroupForm',
- 'ClusterRemoveDevicesForm',
- 'ClusterTypeForm',
- 'VirtualDiskForm',
- 'VirtualMachineForm',
- 'VMInterfaceForm',
- )
- class ClusterTypeForm(OrganizationalModelForm):
- fieldsets = (
- FieldSet('name', 'slug', 'description', 'tags', name=_('Cluster Type')),
- )
- class Meta:
- model = ClusterType
- fields = (
- 'name', 'slug', 'description', 'owner', 'comments', 'tags',
- )
- class ClusterGroupForm(OrganizationalModelForm):
- fieldsets = (
- FieldSet('name', 'slug', 'description', 'tags', name=_('Cluster Group')),
- )
- class Meta:
- model = ClusterGroup
- fields = (
- 'name', 'slug', 'description', 'owner', 'comments', 'tags',
- )
- class ClusterForm(TenancyForm, ScopedForm, PrimaryModelForm):
- type = DynamicModelChoiceField(
- label=_('Type'),
- queryset=ClusterType.objects.all(),
- quick_add=True
- )
- group = DynamicModelChoiceField(
- label=_('Group'),
- queryset=ClusterGroup.objects.all(),
- required=False,
- quick_add=True
- )
- fieldsets = (
- FieldSet('name', 'type', 'group', 'status', 'description', 'tags', name=_('Cluster')),
- FieldSet('scope_type', 'scope', name=_('Scope')),
- FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
- )
- class Meta:
- model = Cluster
- fields = (
- 'name', 'type', 'group', 'status', 'tenant', 'scope_type', 'description', 'owner', 'comments', 'tags',
- )
- class ClusterAddDevicesForm(forms.Form):
- region = DynamicModelChoiceField(
- label=_('Region'),
- queryset=Region.objects.all(),
- required=False,
- null_option='None'
- )
- site_group = DynamicModelChoiceField(
- label=_('Site group'),
- queryset=SiteGroup.objects.all(),
- required=False,
- null_option='None'
- )
- site = DynamicModelChoiceField(
- label=_('Site'),
- queryset=Site.objects.all(),
- required=False,
- query_params={
- 'region_id': '$region',
- 'group_id': '$site_group',
- }
- )
- rack = DynamicModelChoiceField(
- label=_('Rack'),
- queryset=Rack.objects.all(),
- required=False,
- null_option='None',
- query_params={
- 'site_id': '$site'
- }
- )
- devices = DynamicModelMultipleChoiceField(
- label=_('Devices'),
- queryset=Device.objects.all(),
- query_params={
- 'site_id': '$site',
- 'rack_id': '$rack',
- 'cluster_id': 'null',
- }
- )
- class Meta:
- fields = [
- 'region', 'site', 'rack', 'devices',
- ]
- def __init__(self, cluster, *args, **kwargs):
- self.cluster = cluster
- super().__init__(*args, **kwargs)
- self.fields['devices'].choices = []
- def clean(self):
- super().clean()
- # If the Cluster is assigned to a Site or Location, all Devices must be assigned to that same scope.
- if self.cluster.scope is not None:
- for device in self.cleaned_data.get('devices', []):
- for scope_field in ['site', 'location']:
- device_scope = getattr(device, scope_field)
- if (
- self.cluster.scope_type.model_class() == apps.get_model('dcim', scope_field) and
- device_scope != self.cluster.scope
- ):
- raise ValidationError({
- 'devices': _(
- "{device} belongs to a different {scope_field} ({device_scope}) than the "
- "cluster ({cluster_scope})"
- ).format(
- device=device,
- scope_field=scope_field,
- device_scope=device_scope,
- cluster_scope=self.cluster.scope
- )
- })
- class ClusterRemoveDevicesForm(ConfirmationForm):
- pk = forms.ModelMultipleChoiceField(
- queryset=Device.objects.all(),
- widget=forms.MultipleHiddenInput()
- )
- class VirtualMachineForm(TenancyForm, PrimaryModelForm):
- site = DynamicModelChoiceField(
- label=_('Site'),
- queryset=Site.objects.all(),
- required=False
- )
- cluster = DynamicModelChoiceField(
- label=_('Cluster'),
- queryset=Cluster.objects.all(),
- required=False,
- selector=True,
- query_params={
- 'site_id': ['$site', 'null']
- },
- )
- device = DynamicModelChoiceField(
- label=_('Device'),
- queryset=Device.objects.all(),
- required=False,
- query_params={
- 'cluster_id': '$cluster',
- 'site_id': '$site',
- },
- help_text=_("Optionally pin this VM to a specific host device within the cluster")
- )
- role = DynamicModelChoiceField(
- label=_('Role'),
- queryset=DeviceRole.objects.all(),
- required=False,
- query_params={
- "vm_role": "True"
- }
- )
- platform = DynamicModelChoiceField(
- label=_('Platform'),
- queryset=Platform.objects.all(),
- required=False,
- selector=True
- )
- local_context_data = JSONField(
- required=False,
- label=''
- )
- config_template = DynamicModelChoiceField(
- queryset=ConfigTemplate.objects.all(),
- required=False,
- label=_('Config template')
- )
- fieldsets = (
- FieldSet('name', 'role', 'status', 'start_on_boot', 'description', 'serial', 'tags', name=_('Virtual Machine')),
- FieldSet('site', 'cluster', 'device', name=_('Site/Cluster')),
- FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
- FieldSet('platform', 'primary_ip4', 'primary_ip6', 'config_template', name=_('Management')),
- FieldSet('vcpus', 'memory', 'disk', name=_('Resources')),
- FieldSet('local_context_data', name=_('Config Context')),
- )
- class Meta:
- model = VirtualMachine
- fields = [
- 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant',
- 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'owner',
- 'comments', 'tags', 'local_context_data', 'config_template',
- ]
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- if self.instance.pk:
- # Disable the disk field if one or more VirtualDisks have been created
- if self.instance.virtualdisks.exists():
- self.fields['disk'].widget.attrs['disabled'] = True
- self.fields['disk'].help_text = _("Disk size is managed via the attachment of virtual disks.")
- # Compile list of choices for primary IPv4 and IPv6 addresses
- for family in [4, 6]:
- ip_choices = [(None, '---------')]
- # Gather PKs of all interfaces belonging to this VM
- interface_ids = self.instance.interfaces.values_list('pk', flat=True)
- # Collect interface IPs
- interface_ips = IPAddress.objects.filter(
- address__family=family,
- assigned_object_type=ContentType.objects.get_for_model(VMInterface),
- assigned_object_id__in=interface_ids
- )
- if interface_ips:
- ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
- ip_choices.append(('Interface IPs', ip_list))
- # Collect NAT IPs
- nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
- address__family=family,
- nat_inside__assigned_object_type=ContentType.objects.get_for_model(VMInterface),
- nat_inside__assigned_object_id__in=interface_ids
- )
- if nat_ips:
- ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
- ip_choices.append(('NAT IPs', ip_list))
- self.fields['primary_ip{}'.format(family)].choices = ip_choices
- else:
- # An object that doesn't exist yet can't have any IPs assigned to it
- self.fields.pop('primary_ip4')
- self.fields.pop('primary_ip6')
- #
- # Virtual machine components
- #
- class VMComponentForm(OwnerMixin, NetBoxModelForm):
- virtual_machine = DynamicModelChoiceField(
- label=_('Virtual machine'),
- queryset=VirtualMachine.objects.all(),
- selector=True
- )
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # Disable reassignment of VirtualMachine when editing an existing instance
- if self.instance.pk:
- self.fields['virtual_machine'].disabled = True
- class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
- primary_mac_address = DynamicModelChoiceField(
- queryset=MACAddress.objects.all(),
- label=_('Primary MAC address'),
- required=False,
- quick_add=True,
- quick_add_params={'vminterface': '$pk'}
- )
- parent = DynamicModelChoiceField(
- queryset=VMInterface.objects.all(),
- required=False,
- label=_('Parent interface'),
- query_params={
- 'virtual_machine_id': '$virtual_machine',
- }
- )
- bridge = DynamicModelChoiceField(
- queryset=VMInterface.objects.all(),
- required=False,
- label=_('Bridged interface'),
- query_params={
- 'virtual_machine_id': '$virtual_machine',
- }
- )
- vlan_group = DynamicModelChoiceField(
- queryset=VLANGroup.objects.all(),
- required=False,
- label=_('VLAN group')
- )
- untagged_vlan = DynamicModelChoiceField(
- queryset=VLAN.objects.all(),
- required=False,
- label=_('Untagged VLAN'),
- query_params={
- 'group_id': '$vlan_group',
- 'available_on_virtualmachine': '$virtual_machine',
- }
- )
- tagged_vlans = DynamicModelMultipleChoiceField(
- queryset=VLAN.objects.all(),
- required=False,
- label=_('Tagged VLANs'),
- query_params={
- 'group_id': '$vlan_group',
- 'available_on_virtualmachine': '$virtual_machine',
- }
- )
- qinq_svlan = DynamicModelChoiceField(
- queryset=VLAN.objects.all(),
- required=False,
- label=_('Q-in-Q Service VLAN'),
- query_params={
- 'group_id': '$vlan_group',
- 'available_on_virtualmachine': '$virtual_machine',
- 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
- }
- )
- vrf = DynamicModelChoiceField(
- queryset=VRF.objects.all(),
- required=False,
- label=_('VRF')
- )
- vlan_translation_policy = DynamicModelChoiceField(
- queryset=VLANTranslationPolicy.objects.all(),
- required=False,
- label=_('VLAN Translation Policy')
- )
- fieldsets = (
- FieldSet('virtual_machine', 'name', 'description', 'tags', name=_('Interface')),
- FieldSet('vrf', 'primary_mac_address', name=_('Addressing')),
- FieldSet('mtu', 'enabled', name=_('Operation')),
- FieldSet('parent', 'bridge', name=_('Related Interfaces')),
- FieldSet(
- 'mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy',
- name=_('802.1Q Switching')
- ),
- )
- class Meta:
- model = VMInterface
- fields = [
- 'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mtu', 'description', 'mode', 'vlan_group',
- 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'primary_mac_address',
- 'owner', 'tags',
- ]
- labels = {
- 'mode': _('802.1Q Mode'),
- }
- widgets = {
- 'mode': HTMXSelect(),
- }
- class VirtualDiskForm(VMComponentForm):
- fieldsets = (
- FieldSet('virtual_machine', 'name', 'size', 'description', 'tags', name=_('Disk')),
- )
- class Meta:
- model = VirtualDisk
- fields = [
- 'virtual_machine', 'name', 'size', 'description', 'owner', 'tags',
- ]
|