1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00
Files
netbox-community-netbox/netbox/virtualization/forms/model_forms.py

383 lines
12 KiB
Python
Raw Permalink Normal View History

2021-09-28 10:25:07 -04:00
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
2021-09-28 10:25:07 -04:00
from dcim.forms.common import InterfaceCommonForm
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
from extras.models import ConfigTemplate
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
from netbox.forms import NetBoxModelForm
2021-09-28 10:25:07 -04:00
from tenancy.forms import TenancyForm
from utilities.forms import BootstrapMixin, ConfirmationForm
from utilities.forms.fields import (
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
2021-09-28 10:25:07 -04:00
)
from utilities.forms.widgets import HTMXSelect
2021-09-28 10:25:07 -04:00
from virtualization.models import *
__all__ = (
'ClusterAddDevicesForm',
'ClusterForm',
'ClusterGroupForm',
'ClusterRemoveDevicesForm',
'ClusterTypeForm',
'VirtualDiskForm',
2021-09-28 10:25:07 -04:00
'VirtualMachineForm',
'VMInterfaceForm',
)
class ClusterTypeForm(NetBoxModelForm):
2021-09-28 10:25:07 -04:00
slug = SlugField()
fieldsets = (
(_('Cluster Type'), (
'name', 'slug', 'description', 'tags',
)),
)
2021-09-28 10:25:07 -04:00
class Meta:
model = ClusterType
fields = (
'name', 'slug', 'description', 'tags',
)
2021-09-28 10:25:07 -04:00
class ClusterGroupForm(NetBoxModelForm):
2021-09-28 10:25:07 -04:00
slug = SlugField()
fieldsets = (
(_('Cluster Group'), (
'name', 'slug', 'description', 'tags',
)),
)
2021-09-28 10:25:07 -04:00
class Meta:
model = ClusterGroup
fields = (
'name', 'slug', 'description', 'tags',
)
2021-09-28 10:25:07 -04:00
class ClusterForm(TenancyForm, NetBoxModelForm):
2021-09-28 10:25:07 -04:00
type = DynamicModelChoiceField(
label=_('Type'),
2021-09-28 10:25:07 -04:00
queryset=ClusterType.objects.all()
)
group = DynamicModelChoiceField(
label=_('Group'),
2021-09-28 10:25:07 -04:00
queryset=ClusterGroup.objects.all(),
required=False
)
site = DynamicModelChoiceField(
label=_('Site'),
2021-09-28 10:25:07 -04:00
queryset=Site.objects.all(),
required=False,
selector=True
2021-09-28 10:25:07 -04:00
)
comments = CommentField()
fieldsets = (
(_('Cluster'), ('name', 'type', 'group', 'site', 'status', 'description', 'tags')),
(_('Tenancy'), ('tenant_group', 'tenant')),
)
2021-09-28 10:25:07 -04:00
class Meta:
model = Cluster
fields = (
'name', 'type', 'group', 'status', 'tenant', 'site', 'description', 'comments', 'tags',
2021-09-28 10:25:07 -04:00
)
class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
region = DynamicModelChoiceField(
label=_('Region'),
2021-09-28 10:25:07 -04:00
queryset=Region.objects.all(),
required=False,
null_option='None'
)
site_group = DynamicModelChoiceField(
label=_('Site group'),
2021-09-28 10:25:07 -04:00
queryset=SiteGroup.objects.all(),
required=False,
null_option='None'
)
site = DynamicModelChoiceField(
label=_('Site'),
2021-09-28 10:25:07 -04:00
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
rack = DynamicModelChoiceField(
label=_('Rack'),
2021-09-28 10:25:07 -04:00
queryset=Rack.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site'
}
)
devices = DynamicModelMultipleChoiceField(
label=_('Devices'),
2021-09-28 10:25:07 -04:00
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, all Devices must be assigned to that Site.
if self.cluster.site is not None:
for device in self.cleaned_data.get('devices', []):
if device.site != self.cluster.site:
raise ValidationError({
'devices': _(
"{device} belongs to a different site ({device_site}) than the cluster ({cluster_site})"
).format(
device=device,
device_site=device.site,
cluster_site=self.cluster.site
2021-09-28 10:25:07 -04:00
)
})
class ClusterRemoveDevicesForm(ConfirmationForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
)
class VirtualMachineForm(TenancyForm, NetBoxModelForm):
site = DynamicModelChoiceField(
label=_('Site'),
2022-07-12 12:30:07 -04:00
queryset=Site.objects.all(),
required=False
)
2021-09-28 10:25:07 -04:00
cluster = DynamicModelChoiceField(
label=_('Cluster'),
2021-09-28 10:25:07 -04:00
queryset=Cluster.objects.all(),
2022-07-12 12:30:07 -04:00
required=False,
selector=True,
2021-09-28 10:25:07 -04:00
query_params={
'site_id': '$site',
2021-09-28 10:25:07 -04:00
}
)
device = DynamicModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
required=False,
query_params={
'cluster_id': '$cluster',
'site_id': '$site',
2022-07-12 12:30:07 -04:00
},
help_text=_("Optionally pin this VM to a specific host device within the cluster")
)
2021-09-28 10:25:07 -04:00
role = DynamicModelChoiceField(
label=_('Role'),
2021-09-28 10:25:07 -04:00
queryset=DeviceRole.objects.all(),
required=False,
query_params={
"vm_role": "True"
}
)
platform = DynamicModelChoiceField(
label=_('Platform'),
2021-09-28 10:25:07 -04:00
queryset=Platform.objects.all(),
required=False,
selector=True
2021-09-28 10:25:07 -04:00
)
local_context_data = JSONField(
required=False,
label=''
)
config_template = DynamicModelChoiceField(
queryset=ConfigTemplate.objects.all(),
required=False,
label=_('Config template')
)
comments = CommentField()
2021-09-28 10:25:07 -04:00
fieldsets = (
(_('Virtual Machine'), ('name', 'role', 'status', 'description', 'tags')),
(_('Site/Cluster'), ('site', 'cluster', 'device')),
(_('Tenancy'), ('tenant_group', 'tenant')),
(_('Management'), ('platform', 'primary_ip4', 'primary_ip6', 'config_template')),
(_('Resources'), ('vcpus', 'memory', 'disk')),
(_('Config Context'), ('local_context_data',)),
)
2021-09-28 10:25:07 -04:00
class Meta:
model = VirtualMachine
fields = [
'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'tags', 'local_context_data',
'config_template',
2021-09-28 10:25:07 -04:00
]
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.")
2021-09-28 10:25:07 -04:00
# 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['primary_ip4'].choices = []
self.fields['primary_ip4'].widget.attrs['readonly'] = True
self.fields['primary_ip6'].choices = []
self.fields['primary_ip6'].widget.attrs['readonly'] = True
#
# Virtual machine components
#
class VMComponentForm(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):
2021-09-28 10:25:07 -04:00
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
label=_('Parent interface'),
query_params={
'virtual_machine_id': '$virtual_machine',
}
2021-09-28 10:25:07 -04:00
)
bridge = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
label=_('Bridged interface'),
query_params={
'virtual_machine_id': '$virtual_machine',
}
)
2021-09-28 10:25:07 -04:00
vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
label=_('VLAN group')
2021-09-28 10:25:07 -04:00
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
label=_('Untagged VLAN'),
2021-09-28 10:25:07 -04:00
query_params={
'group_id': '$vlan_group',
'available_on_virtualmachine': '$virtual_machine',
2021-09-28 10:25:07 -04:00
}
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
label=_('Tagged VLANs'),
2021-09-28 10:25:07 -04:00
query_params={
'group_id': '$vlan_group',
'available_on_virtualmachine': '$virtual_machine',
2021-09-28 10:25:07 -04:00
}
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('VRF')
)
2021-09-28 10:25:07 -04:00
fieldsets = (
(_('Interface'), ('virtual_machine', 'name', 'description', 'tags')),
(_('Addressing'), ('vrf', 'mac_address')),
(_('Operation'), ('mtu', 'enabled')),
(_('Related Interfaces'), ('parent', 'bridge')),
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
)
2021-09-28 10:25:07 -04:00
class Meta:
model = VMInterface
fields = [
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
2021-09-28 10:25:07 -04:00
]
labels = {
'mode': '802.1Q Mode',
}
widgets = {
'mode': HTMXSelect(),
}
class VirtualDiskForm(VMComponentForm):
fieldsets = (
(_('Disk'), ('virtual_machine', 'name', 'size', 'description', 'tags')),
)
class Meta:
model = VirtualDisk
fields = [
'virtual_machine', 'name', 'size', 'description', 'tags',
]