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

Refactor virtualization forms

This commit is contained in:
jeremystretch
2021-09-28 10:25:07 -04:00
parent 8e849566d5
commit db522f96be
8 changed files with 1035 additions and 971 deletions

View File

@ -1,971 +0,0 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
from dcim.choices import InterfaceModeChoices
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
from dcim.forms.common import InterfaceCommonForm
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
from extras.forms import (
AddRemoveTagsForm, CustomFieldModelBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm,
CustomFieldModelFilterForm, CustomFieldsMixin, LocalConfigContextFilterForm,
)
from extras.models import Tag
from ipam.models import IPAddress, VLAN, VLANGroup
from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, BulkRenameForm, CommentField, ConfirmationForm,
CSVChoiceField, CSVModelChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
form_from_model, JSONField, SlugField, SmallTextarea, StaticSelect, StaticSelectMultiple, TagFilterField,
BOOLEAN_WITH_BLANK_CHOICES,
)
from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
#
# Cluster types
#
class ClusterTypeForm(BootstrapMixin, CustomFieldModelForm):
slug = SlugField()
class Meta:
model = ClusterType
fields = [
'name', 'slug', 'description',
]
class ClusterTypeCSVForm(CustomFieldModelCSVForm):
slug = SlugField()
class Meta:
model = ClusterType
fields = ('name', 'slug', 'description')
class ClusterTypeBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
widget=forms.MultipleHiddenInput
)
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['description']
class ClusterTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = ClusterType
field_groups = [
['q'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
#
# Cluster groups
#
class ClusterGroupForm(BootstrapMixin, CustomFieldModelForm):
slug = SlugField()
class Meta:
model = ClusterGroup
fields = [
'name', 'slug', 'description',
]
class ClusterGroupCSVForm(CustomFieldModelCSVForm):
slug = SlugField()
class Meta:
model = ClusterGroup
fields = ('name', 'slug', 'description')
class ClusterGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
widget=forms.MultipleHiddenInput
)
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['description']
class ClusterGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = ClusterGroup
field_groups = [
['q'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
#
# Clusters
#
class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
type = DynamicModelChoiceField(
queryset=ClusterType.objects.all()
)
group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False
)
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
initial_params={
'sites': '$site'
}
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
initial_params={
'sites': '$site'
}
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
comments = CommentField()
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = Cluster
fields = (
'name', 'type', 'group', 'tenant', 'region', 'site_group', 'site', 'comments', 'tags',
)
fieldsets = (
('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
('Tenancy', ('tenant_group', 'tenant')),
)
class ClusterCSVForm(CustomFieldModelCSVForm):
type = CSVModelChoiceField(
queryset=ClusterType.objects.all(),
to_field_name='name',
help_text='Type of cluster'
)
group = CSVModelChoiceField(
queryset=ClusterGroup.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned cluster group'
)
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned site'
)
tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned tenant'
)
class Meta:
model = Cluster
fields = ('name', 'type', 'group', 'site', 'comments')
class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Cluster.objects.all(),
widget=forms.MultipleHiddenInput()
)
type = DynamicModelChoiceField(
queryset=ClusterType.objects.all(),
required=False
)
group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
required=False
)
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
comments = CommentField(
widget=SmallTextarea,
label='Comments'
)
class Meta:
nullable_fields = [
'group', 'site', 'comments', 'tenant',
]
class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
model = Cluster
field_order = [
'q', 'type_id', 'region_id', 'site_id', 'group_id', 'tenant_group_id', 'tenant_id',
]
field_groups = [
['q', 'tag'],
['group_id', 'type_id'],
['region_id', 'site_group_id', 'site_id'],
['tenant_group_id', 'tenant_id'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
required=False,
label=_('Type'),
fetch_trigger='open'
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
null_option='None',
query_params={
'region_id': '$region_id',
'site_group_id': '$site_group_id',
},
label=_('Site'),
fetch_trigger='open'
)
group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
label=_('Group'),
fetch_trigger='open'
)
tag = TagFilterField(model)
class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
null_option='None'
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
null_option='None'
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site'
}
)
devices = DynamicModelMultipleChoiceField(
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': "{} belongs to a different site ({}) than the cluster ({})".format(
device, device.site, self.cluster.site
)
})
class ClusterRemoveDevicesForm(ConfirmationForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
)
#
# Virtual Machines
#
class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
cluster_group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
initial_params={
'clusters': '$cluster'
}
)
cluster = DynamicModelChoiceField(
queryset=Cluster.objects.all(),
query_params={
'group_id': '$cluster_group'
}
)
role = DynamicModelChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
query_params={
"vm_role": "True"
}
)
platform = DynamicModelChoiceField(
queryset=Platform.objects.all(),
required=False
)
local_context_data = JSONField(
required=False,
label=''
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = VirtualMachine
fields = [
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
]
fieldsets = (
('Virtual Machine', ('name', 'role', 'status', 'tags')),
('Cluster', ('cluster_group', 'cluster')),
('Tenancy', ('tenant_group', 'tenant')),
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
('Resources', ('vcpus', 'memory', 'disk')),
('Config Context', ('local_context_data',)),
)
help_texts = {
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
"config context",
}
widgets = {
"status": StaticSelect(),
'primary_ip4': StaticSelect(),
'primary_ip6': StaticSelect(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
# 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
class VirtualMachineCSVForm(CustomFieldModelCSVForm):
status = CSVChoiceField(
choices=VirtualMachineStatusChoices,
required=False,
help_text='Operational status of device'
)
cluster = CSVModelChoiceField(
queryset=Cluster.objects.all(),
to_field_name='name',
help_text='Assigned cluster'
)
role = CSVModelChoiceField(
queryset=DeviceRole.objects.filter(
vm_role=True
),
required=False,
to_field_name='name',
help_text='Functional role'
)
tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned tenant'
)
platform = CSVModelChoiceField(
queryset=Platform.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned platform'
)
class Meta:
model = VirtualMachine
fields = (
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
)
class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
widget=forms.MultipleHiddenInput()
)
status = forms.ChoiceField(
choices=add_blank_choice(VirtualMachineStatusChoices),
required=False,
initial='',
widget=StaticSelect(),
)
cluster = DynamicModelChoiceField(
queryset=Cluster.objects.all(),
required=False
)
role = DynamicModelChoiceField(
queryset=DeviceRole.objects.filter(
vm_role=True
),
required=False,
query_params={
"vm_role": "True"
}
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
required=False
)
platform = DynamicModelChoiceField(
queryset=Platform.objects.all(),
required=False
)
vcpus = forms.IntegerField(
required=False,
label='vCPUs'
)
memory = forms.IntegerField(
required=False,
label='Memory (MB)'
)
disk = forms.IntegerField(
required=False,
label='Disk (GB)'
)
comments = CommentField(
widget=SmallTextarea,
label='Comments'
)
class Meta:
nullable_fields = [
'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
]
class VirtualMachineFilterForm(
BootstrapMixin,
LocalConfigContextFilterForm,
TenancyFilterForm,
CustomFieldModelFilterForm
):
model = VirtualMachine
field_groups = [
['q', 'tag'],
['cluster_group_id', 'cluster_type_id', 'cluster_id'],
['region_id', 'site_group_id', 'site_id'],
['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data'],
['tenant_group_id', 'tenant_id'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
cluster_group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
label=_('Cluster group'),
fetch_trigger='open'
)
cluster_type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
required=False,
null_option='None',
label=_('Cluster type'),
fetch_trigger='open'
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Cluster'),
fetch_trigger='open'
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
null_option='None',
query_params={
'region_id': '$region_id',
'group_id': '$site_group_id',
},
label=_('Site'),
fetch_trigger='open'
)
role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
null_option='None',
query_params={
'vm_role': "True"
},
label=_('Role'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=VirtualMachineStatusChoices,
required=False,
widget=StaticSelectMultiple()
)
platform_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
required=False,
null_option='None',
label=_('Platform'),
fetch_trigger='open'
)
mac_address = forms.CharField(
required=False,
label='MAC address'
)
has_primary_ip = forms.NullBooleanField(
required=False,
label='Has a primary IP',
widget=StaticSelect(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
tag = TagFilterField(model)
#
# VM interfaces
#
class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
label='Parent interface'
)
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',
}
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
label='Tagged VLANs',
query_params={
'group_id': '$vlan_group',
}
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = VMInterface
fields = [
'virtual_machine', 'name', 'enabled', 'parent', 'mac_address', 'mtu', 'description', 'mode', 'tags',
'untagged_vlan', 'tagged_vlans',
]
widgets = {
'virtual_machine': forms.HiddenInput(),
'mode': StaticSelect()
}
labels = {
'mode': '802.1Q Mode',
}
help_texts = {
'mode': INTERFACE_MODE_HELP_TEXT,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
# Restrict parent interface assignment by VM
self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id)
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)
class VMInterfaceCreateForm(BootstrapMixin, CustomFieldsMixin, InterfaceCommonForm):
model = VMInterface
virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all()
)
name_pattern = ExpandableNameField(
label='Name'
)
enabled = forms.BooleanField(
required=False,
initial=True
)
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
query_params={
'virtual_machine_id': '$virtual_machine',
}
)
mac_address = forms.CharField(
required=False,
label='MAC Address'
)
description = forms.CharField(
max_length=200,
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect(),
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
field_order = (
'virtual_machine', 'name_pattern', 'enabled', 'parent', 'mtu', 'mac_address', 'description', 'mode',
'untagged_vlan', 'tagged_vlans', 'tags'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)
class VMInterfaceCSVForm(CustomFieldModelCSVForm):
virtual_machine = CSVModelChoiceField(
queryset=VirtualMachine.objects.all(),
to_field_name='name'
)
mode = CSVChoiceField(
choices=InterfaceModeChoices,
required=False,
help_text='IEEE 802.1Q operational mode (for L2 interfaces)'
)
class Meta:
model = VMInterface
fields = (
'virtual_machine', 'name', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
)
def clean_enabled(self):
# Make sure enabled is True when it's not included in the uploaded data
if 'enabled' not in self.data:
return True
else:
return self.cleaned_data['enabled']
class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=VMInterface.objects.all(),
widget=forms.MultipleHiddenInput()
)
virtual_machine = forms.ModelChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
disabled=True,
widget=forms.HiddenInput()
)
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False
)
enabled = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect()
)
mtu = forms.IntegerField(
required=False,
min_value=INTERFACE_MTU_MIN,
max_value=INTERFACE_MTU_MAX,
label='MTU'
)
description = forms.CharField(
max_length=100,
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect()
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False
)
class Meta:
nullable_fields = [
'parent', 'mtu', 'description',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'virtual_machine' in self.initial:
vm_id = self.initial.get('virtual_machine')
# Restrict parent interface assignment by VM
self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id)
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)
else:
# See 5643
if 'pk' in self.initial:
site = None
interfaces = VMInterface.objects.filter(pk__in=self.initial['pk']).prefetch_related(
'virtual_machine__cluster__site'
)
# Check interface sites. First interface should set site, further interfaces will either continue the
# loop or reset back to no site and break the loop.
for interface in interfaces:
if site is None:
site = interface.virtual_machine.cluster.site
elif interface.virtual_machine.cluster.site is not site:
site = None
break
if site is not None:
self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
class VMInterfaceBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(
queryset=VMInterface.objects.all(),
widget=forms.MultipleHiddenInput()
)
class VMInterfaceFilterForm(BootstrapMixin, forms.Form):
model = VMInterface
field_groups = [
['q', 'tag'],
['cluster_id', 'virtual_machine_id'],
['enabled', 'mac_address'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Cluster'),
fetch_trigger='open'
)
virtual_machine_id = DynamicModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
query_params={
'cluster_id': '$cluster_id'
},
label=_('Virtual machine'),
fetch_trigger='open'
)
enabled = forms.NullBooleanField(
required=False,
widget=StaticSelect(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
mac_address = forms.CharField(
required=False,
label='MAC address'
)
tag = TagFilterField(model)
#
# Bulk VirtualMachine component creation
#
class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
widget=forms.MultipleHiddenInput()
)
name_pattern = ExpandableNameField(
label='Name'
)
def clean_tags(self):
# Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
# must first convert the list of tags to a string.
return ','.join(self.cleaned_data.get('tags'))
class VMInterfaceBulkCreateForm(
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
VirtualMachineBulkAddComponentForm
):
pass

View File

@ -0,0 +1,6 @@
from .models import *
from .filtersets import *
from .object_create import *
from .bulk_create import *
from .bulk_edit import *
from .bulk_import import *

View File

@ -0,0 +1,30 @@
from django import forms
from utilities.forms import BootstrapMixin, ExpandableNameField, form_from_model
from virtualization.models import VMInterface, VirtualMachine
__all__ = (
'VMInterfaceBulkCreateForm',
)
class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
widget=forms.MultipleHiddenInput()
)
name_pattern = ExpandableNameField(
label='Name'
)
def clean_tags(self):
# Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
# must first convert the list of tags to a string.
return ','.join(self.cleaned_data.get('tags'))
class VMInterfaceBulkCreateForm(
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
VirtualMachineBulkAddComponentForm
):
pass

View File

@ -0,0 +1,239 @@
from django import forms
from dcim.choices import InterfaceModeChoices
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from ipam.models import VLAN
from tenancy.models import Tenant
from utilities.forms import (
add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, BulkRenameForm, CommentField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, SmallTextarea, StaticSelect
)
from virtualization.choices import *
from virtualization.models import *
__all__ = (
'ClusterBulkEditForm',
'ClusterGroupBulkEditForm',
'ClusterTypeBulkEditForm',
'VirtualMachineBulkEditForm',
'VMInterfaceBulkEditForm',
'VMInterfaceBulkRenameForm',
)
class ClusterTypeBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
widget=forms.MultipleHiddenInput
)
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['description']
class ClusterGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
widget=forms.MultipleHiddenInput
)
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['description']
class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Cluster.objects.all(),
widget=forms.MultipleHiddenInput()
)
type = DynamicModelChoiceField(
queryset=ClusterType.objects.all(),
required=False
)
group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
required=False
)
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
comments = CommentField(
widget=SmallTextarea,
label='Comments'
)
class Meta:
nullable_fields = [
'group', 'site', 'comments', 'tenant',
]
class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
widget=forms.MultipleHiddenInput()
)
status = forms.ChoiceField(
choices=add_blank_choice(VirtualMachineStatusChoices),
required=False,
initial='',
widget=StaticSelect(),
)
cluster = DynamicModelChoiceField(
queryset=Cluster.objects.all(),
required=False
)
role = DynamicModelChoiceField(
queryset=DeviceRole.objects.filter(
vm_role=True
),
required=False,
query_params={
"vm_role": "True"
}
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
required=False
)
platform = DynamicModelChoiceField(
queryset=Platform.objects.all(),
required=False
)
vcpus = forms.IntegerField(
required=False,
label='vCPUs'
)
memory = forms.IntegerField(
required=False,
label='Memory (MB)'
)
disk = forms.IntegerField(
required=False,
label='Disk (GB)'
)
comments = CommentField(
widget=SmallTextarea,
label='Comments'
)
class Meta:
nullable_fields = [
'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
]
class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=VMInterface.objects.all(),
widget=forms.MultipleHiddenInput()
)
virtual_machine = forms.ModelChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
disabled=True,
widget=forms.HiddenInput()
)
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False
)
enabled = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect()
)
mtu = forms.IntegerField(
required=False,
min_value=INTERFACE_MTU_MIN,
max_value=INTERFACE_MTU_MAX,
label='MTU'
)
description = forms.CharField(
max_length=100,
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect()
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False
)
class Meta:
nullable_fields = [
'parent', 'mtu', 'description',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'virtual_machine' in self.initial:
vm_id = self.initial.get('virtual_machine')
# Restrict parent interface assignment by VM
self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id)
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)
else:
# See 5643
if 'pk' in self.initial:
site = None
interfaces = VMInterface.objects.filter(pk__in=self.initial['pk']).prefetch_related(
'virtual_machine__cluster__site'
)
# Check interface sites. First interface should set site, further interfaces will either continue the
# loop or reset back to no site and break the loop.
for interface in interfaces:
if site is None:
site = interface.virtual_machine.cluster.site
elif interface.virtual_machine.cluster.site is not site:
site = None
break
if site is not None:
self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
class VMInterfaceBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(
queryset=VMInterface.objects.all(),
widget=forms.MultipleHiddenInput()
)

View File

@ -0,0 +1,125 @@
from dcim.choices import InterfaceModeChoices
from dcim.models import DeviceRole, Platform, Site
from extras.forms import CustomFieldModelCSVForm
from tenancy.models import Tenant
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
from virtualization.choices import *
from virtualization.models import *
__all__ = (
'ClusterCSVForm',
'ClusterGroupCSVForm',
'ClusterTypeCSVForm',
'VirtualMachineCSVForm',
'VMInterfaceCSVForm',
)
class ClusterTypeCSVForm(CustomFieldModelCSVForm):
slug = SlugField()
class Meta:
model = ClusterType
fields = ('name', 'slug', 'description')
class ClusterGroupCSVForm(CustomFieldModelCSVForm):
slug = SlugField()
class Meta:
model = ClusterGroup
fields = ('name', 'slug', 'description')
class ClusterCSVForm(CustomFieldModelCSVForm):
type = CSVModelChoiceField(
queryset=ClusterType.objects.all(),
to_field_name='name',
help_text='Type of cluster'
)
group = CSVModelChoiceField(
queryset=ClusterGroup.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned cluster group'
)
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned site'
)
tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned tenant'
)
class Meta:
model = Cluster
fields = ('name', 'type', 'group', 'site', 'comments')
class VirtualMachineCSVForm(CustomFieldModelCSVForm):
status = CSVChoiceField(
choices=VirtualMachineStatusChoices,
required=False,
help_text='Operational status of device'
)
cluster = CSVModelChoiceField(
queryset=Cluster.objects.all(),
to_field_name='name',
help_text='Assigned cluster'
)
role = CSVModelChoiceField(
queryset=DeviceRole.objects.filter(
vm_role=True
),
required=False,
to_field_name='name',
help_text='Functional role'
)
tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned tenant'
)
platform = CSVModelChoiceField(
queryset=Platform.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned platform'
)
class Meta:
model = VirtualMachine
fields = (
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
)
class VMInterfaceCSVForm(CustomFieldModelCSVForm):
virtual_machine = CSVModelChoiceField(
queryset=VirtualMachine.objects.all(),
to_field_name='name'
)
mode = CSVChoiceField(
choices=InterfaceModeChoices,
required=False,
help_text='IEEE 802.1Q operational mode (for L2 interfaces)'
)
class Meta:
model = VMInterface
fields = (
'virtual_machine', 'name', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
)
def clean_enabled(self):
# Make sure enabled is True when it's not included in the uploaded data
if 'enabled' not in self.data:
return True
else:
return self.cleaned_data['enabled']

View File

@ -0,0 +1,237 @@
from django import forms
from django.utils.translation import gettext as _
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
from tenancy.forms import TenancyFilterForm
from utilities.forms import (
BootstrapMixin, DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple, TagFilterField,
BOOLEAN_WITH_BLANK_CHOICES,
)
from virtualization.choices import *
from virtualization.models import *
__all__ = (
'ClusterFilterForm',
'ClusterGroupFilterForm',
'ClusterTypeFilterForm',
'VirtualMachineFilterForm',
'VMInterfaceFilterForm',
)
class ClusterTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = ClusterType
field_groups = [
['q'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
class ClusterGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = ClusterGroup
field_groups = [
['q'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
model = Cluster
field_order = [
'q', 'type_id', 'region_id', 'site_id', 'group_id', 'tenant_group_id', 'tenant_id',
]
field_groups = [
['q', 'tag'],
['group_id', 'type_id'],
['region_id', 'site_group_id', 'site_id'],
['tenant_group_id', 'tenant_id'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
required=False,
label=_('Type'),
fetch_trigger='open'
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
null_option='None',
query_params={
'region_id': '$region_id',
'site_group_id': '$site_group_id',
},
label=_('Site'),
fetch_trigger='open'
)
group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
label=_('Group'),
fetch_trigger='open'
)
tag = TagFilterField(model)
class VirtualMachineFilterForm(
BootstrapMixin,
LocalConfigContextFilterForm,
TenancyFilterForm,
CustomFieldModelFilterForm
):
model = VirtualMachine
field_groups = [
['q', 'tag'],
['cluster_group_id', 'cluster_type_id', 'cluster_id'],
['region_id', 'site_group_id', 'site_id'],
['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data'],
['tenant_group_id', 'tenant_id'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
cluster_group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
label=_('Cluster group'),
fetch_trigger='open'
)
cluster_type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
required=False,
null_option='None',
label=_('Cluster type'),
fetch_trigger='open'
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Cluster'),
fetch_trigger='open'
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
null_option='None',
query_params={
'region_id': '$region_id',
'group_id': '$site_group_id',
},
label=_('Site'),
fetch_trigger='open'
)
role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
null_option='None',
query_params={
'vm_role': "True"
},
label=_('Role'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=VirtualMachineStatusChoices,
required=False,
widget=StaticSelectMultiple()
)
platform_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
required=False,
null_option='None',
label=_('Platform'),
fetch_trigger='open'
)
mac_address = forms.CharField(
required=False,
label='MAC address'
)
has_primary_ip = forms.NullBooleanField(
required=False,
label='Has a primary IP',
widget=StaticSelect(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
tag = TagFilterField(model)
class VMInterfaceFilterForm(BootstrapMixin, forms.Form):
model = VMInterface
field_groups = [
['q', 'tag'],
['cluster_id', 'virtual_machine_id'],
['enabled', 'mac_address'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Cluster'),
fetch_trigger='open'
)
virtual_machine_id = DynamicModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
query_params={
'cluster_id': '$cluster_id'
},
label=_('Virtual machine'),
fetch_trigger='open'
)
enabled = forms.NullBooleanField(
required=False,
widget=StaticSelect(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
mac_address = forms.CharField(
required=False,
label='MAC address'
)
tag = TagFilterField(model)

View File

@ -0,0 +1,324 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from dcim.forms.common import InterfaceCommonForm
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
from extras.forms import CustomFieldModelForm
from extras.models import Tag
from ipam.models import IPAddress, VLAN, VLANGroup
from tenancy.forms import TenancyForm
from utilities.forms import (
BootstrapMixin, CommentField, ConfirmationForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
JSONField, SlugField, StaticSelect,
)
from virtualization.models import *
__all__ = (
'ClusterAddDevicesForm',
'ClusterForm',
'ClusterGroupForm',
'ClusterRemoveDevicesForm',
'ClusterTypeForm',
'VirtualMachineForm',
'VMInterfaceForm',
)
class ClusterTypeForm(BootstrapMixin, CustomFieldModelForm):
slug = SlugField()
class Meta:
model = ClusterType
fields = [
'name', 'slug', 'description',
]
class ClusterGroupForm(BootstrapMixin, CustomFieldModelForm):
slug = SlugField()
class Meta:
model = ClusterGroup
fields = [
'name', 'slug', 'description',
]
class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
type = DynamicModelChoiceField(
queryset=ClusterType.objects.all()
)
group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False
)
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
initial_params={
'sites': '$site'
}
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
initial_params={
'sites': '$site'
}
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
comments = CommentField()
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = Cluster
fields = (
'name', 'type', 'group', 'tenant', 'region', 'site_group', 'site', 'comments', 'tags',
)
fieldsets = (
('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
('Tenancy', ('tenant_group', 'tenant')),
)
class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
null_option='None'
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
null_option='None'
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site'
}
)
devices = DynamicModelMultipleChoiceField(
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': "{} belongs to a different site ({}) than the cluster ({})".format(
device, device.site, self.cluster.site
)
})
class ClusterRemoveDevicesForm(ConfirmationForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
)
class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
cluster_group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
initial_params={
'clusters': '$cluster'
}
)
cluster = DynamicModelChoiceField(
queryset=Cluster.objects.all(),
query_params={
'group_id': '$cluster_group'
}
)
role = DynamicModelChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
query_params={
"vm_role": "True"
}
)
platform = DynamicModelChoiceField(
queryset=Platform.objects.all(),
required=False
)
local_context_data = JSONField(
required=False,
label=''
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = VirtualMachine
fields = [
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
]
fieldsets = (
('Virtual Machine', ('name', 'role', 'status', 'tags')),
('Cluster', ('cluster_group', 'cluster')),
('Tenancy', ('tenant_group', 'tenant')),
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
('Resources', ('vcpus', 'memory', 'disk')),
('Config Context', ('local_context_data',)),
)
help_texts = {
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
"config context",
}
widgets = {
"status": StaticSelect(),
'primary_ip4': StaticSelect(),
'primary_ip6': StaticSelect(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
# 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
class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
label='Parent interface'
)
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',
}
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
label='Tagged VLANs',
query_params={
'group_id': '$vlan_group',
}
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = VMInterface
fields = [
'virtual_machine', 'name', 'enabled', 'parent', 'mac_address', 'mtu', 'description', 'mode', 'tags',
'untagged_vlan', 'tagged_vlans',
]
widgets = {
'virtual_machine': forms.HiddenInput(),
'mode': StaticSelect()
}
labels = {
'mode': '802.1Q Mode',
}
help_texts = {
'mode': INTERFACE_MODE_HELP_TEXT,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
# Restrict parent interface assignment by VM
self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id)
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)

View File

@ -0,0 +1,74 @@
from django import forms
from dcim.choices import InterfaceModeChoices
from dcim.forms.common import InterfaceCommonForm
from extras.forms import CustomFieldsMixin
from extras.models import Tag
from ipam.models import VLAN
from utilities.forms import (
add_blank_choice, BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
StaticSelect,
)
from virtualization.models import VMInterface, VirtualMachine
__all__ = (
'VMInterfaceCreateForm',
)
class VMInterfaceCreateForm(BootstrapMixin, CustomFieldsMixin, InterfaceCommonForm):
model = VMInterface
virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all()
)
name_pattern = ExpandableNameField(
label='Name'
)
enabled = forms.BooleanField(
required=False,
initial=True
)
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
query_params={
'virtual_machine_id': '$virtual_machine',
}
)
mac_address = forms.CharField(
required=False,
label='MAC Address'
)
description = forms.CharField(
max_length=200,
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect(),
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
field_order = (
'virtual_machine', 'name_pattern', 'enabled', 'parent', 'mtu', 'mac_address', 'description', 'mode',
'untagged_vlan', 'tagged_vlans', 'tags'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)