From 12861a19a8c3a5ad0deea9eac96fa6776bb6c1f2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 18 Oct 2023 16:21:29 -0700 Subject: [PATCH] 8356 add supplemental forms --- netbox/virtualization/filtersets.py | 27 +++++- netbox/virtualization/forms/bulk_create.py | 9 +- netbox/virtualization/forms/bulk_edit.py | 17 ++++ netbox/virtualization/forms/bulk_import.py | 15 ++++ netbox/virtualization/forms/filtersets.py | 18 ++++ netbox/virtualization/forms/model_forms.py | 26 ++++++ .../virtualization/models/virtualmachines.py | 1 + .../virtualization/tables/virtualmachines.py | 19 ++++- netbox/virtualization/urls.py | 9 ++ netbox/virtualization/views.py | 82 ++++++++++++++++++- 10 files changed, 219 insertions(+), 4 deletions(-) diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index 571dbe64b..ac72fa392 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -10,7 +10,7 @@ from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet from utilities.filters import MultiValueCharFilter, MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter from .choices import * -from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface +from .models import Cluster, ClusterGroup, ClusterType, VirtualDisk, VirtualMachine, VMInterface __all__ = ( 'ClusterFilterSet', @@ -303,3 +303,28 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet, CommonInterfaceFilterSet): Q(name__icontains=value) | Q(description__icontains=value) ) + + +class VirtualDiskFilterSet(NetBoxModelFilterSet): + virtual_machine_id = django_filters.ModelMultipleChoiceFilter( + field_name='virtual_machine', + queryset=VirtualMachine.objects.all(), + label=_('Virtual machine (ID)'), + ) + virtual_machine = django_filters.ModelMultipleChoiceFilter( + field_name='virtual_machine__name', + queryset=VirtualMachine.objects.all(), + to_field_name='name', + label=_('Virtual machine'), + ) + + class Meta: + model = VirtualDisk + fields = ['id', 'name', 'size',] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) + ) diff --git a/netbox/virtualization/forms/bulk_create.py b/netbox/virtualization/forms/bulk_create.py index 7153453ec..886b565be 100644 --- a/netbox/virtualization/forms/bulk_create.py +++ b/netbox/virtualization/forms/bulk_create.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from utilities.forms import BootstrapMixin, form_from_model from utilities.forms.fields import ExpandableNameField -from virtualization.models import VMInterface, VirtualMachine +from virtualization.models import VirtualDisk, VMInterface, VirtualMachine __all__ = ( 'VMInterfaceBulkCreateForm', @@ -30,3 +30,10 @@ class VMInterfaceBulkCreateForm( VirtualMachineBulkAddComponentForm ): replication_fields = ('name',) + + +class VirtualDiskBulkCreateForm( + form_from_model(VirtualDisk, ['tags']), + VirtualMachineBulkAddComponentForm +): + replication_fields = ('name', 'size') diff --git a/netbox/virtualization/forms/bulk_edit.py b/netbox/virtualization/forms/bulk_edit.py index a33ffac53..324649234 100644 --- a/netbox/virtualization/forms/bulk_edit.py +++ b/netbox/virtualization/forms/bulk_edit.py @@ -18,6 +18,7 @@ __all__ = ( 'ClusterBulkEditForm', 'ClusterGroupBulkEditForm', 'ClusterTypeBulkEditForm', + 'VirtualDiskBulkEditForm', 'VirtualMachineBulkEditForm', 'VMInterfaceBulkEditForm', 'VMInterfaceBulkRenameForm', @@ -315,3 +316,19 @@ class VMInterfaceBulkRenameForm(BulkRenameForm): queryset=VMInterface.objects.all(), widget=forms.MultipleHiddenInput() ) + + +class VirtualDiskBulkEditForm(NetBoxModelBulkEditForm): + virtual_machine = forms.ModelChoiceField( + label=_('Virtual machine'), + queryset=VirtualMachine.objects.all(), + required=False, + disabled=True, + widget=forms.HiddenInput() + ) + + model = VirtualDisk + fieldsets = ( + (None, ('mtu', 'enabled', 'vrf', 'size')), + ) + nullable_fields = () diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py index 04fe2d7ae..00f386af2 100644 --- a/netbox/virtualization/forms/bulk_import.py +++ b/netbox/virtualization/forms/bulk_import.py @@ -14,6 +14,7 @@ __all__ = ( 'ClusterImportForm', 'ClusterGroupImportForm', 'ClusterTypeImportForm', + 'VirtualDiskImportForm', 'VirtualMachineImportForm', 'VMInterfaceImportForm', ) @@ -199,3 +200,17 @@ class VMInterfaceImportForm(NetBoxModelImportForm): return True else: return self.cleaned_data['enabled'] + + +class VirtualDiskImportForm(NetBoxModelImportForm): + virtual_machine = CSVModelChoiceField( + label=_('Virtual machine'), + queryset=VirtualMachine.objects.all(), + to_field_name='name' + ) + + class Meta: + model = VirtualDisk + fields = ( + 'virtual_machine', 'name', 'size', 'tags' + ) diff --git a/netbox/virtualization/forms/filtersets.py b/netbox/virtualization/forms/filtersets.py index 99ac0cb77..49cf624df 100644 --- a/netbox/virtualization/forms/filtersets.py +++ b/netbox/virtualization/forms/filtersets.py @@ -16,6 +16,7 @@ __all__ = ( 'ClusterFilterForm', 'ClusterGroupFilterForm', 'ClusterTypeFilterForm', + 'VirtualDiskFilterForm', 'VirtualMachineFilterForm', 'VMInterfaceFilterForm', ) @@ -221,3 +222,20 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm): label=_('L2VPN') ) tag = TagFilterField(model) + + +class VirtualDiskFilterForm(NetBoxModelFilterSetForm): + model = VirtualDisk + fieldsets = ( + (None, ('q', 'filter_id', 'tag')), + (_('Virtual Machine'), ('virtual_machine_id')), + ) + virtual_machine_id = DynamicModelMultipleChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + query_params={ + 'cluster_id': '$cluster_id' + }, + label=_('Virtual machine') + ) + tag = TagFilterField(model) diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index 21dbc895a..215ee5c2f 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -22,6 +22,7 @@ __all__ = ( 'ClusterGroupForm', 'ClusterRemoveDevicesForm', 'ClusterTypeForm', + 'VirtualDiskForm', 'VirtualMachineForm', 'VMInterfaceForm', ) @@ -349,3 +350,28 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm): # Disable reassignment of VirtualMachine when editing an existing instance if self.instance.pk: self.fields['virtual_machine'].disabled = True + + +class VirtualDiskForm(NetBoxModelForm): + virtual_machine = DynamicModelChoiceField( + label=_('Virtual machine'), + queryset=VirtualMachine.objects.all(), + selector=True + ) + + fieldsets = ( + (_(''), ('virtual_machine', 'name', 'size', 'tags')), + ) + + class Meta: + model = VirtualDisk + fields = [ + 'virtual_machine', 'name', 'size', 'tags', + ] + + 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 diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index 35b3ff4af..d5a570e7e 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -21,6 +21,7 @@ from utilities.tracking import TrackingModelMixin from virtualization.choices import * __all__ = ( + 'VirtualDisk', 'VirtualMachine', 'VMInterface', ) diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py index f8473df1e..abfbed6eb 100644 --- a/netbox/virtualization/tables/virtualmachines.py +++ b/netbox/virtualization/tables/virtualmachines.py @@ -4,9 +4,10 @@ from django.utils.translation import gettext_lazy as _ from dcim.tables.devices import BaseInterfaceTable from netbox.tables import NetBoxTable, columns from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin -from virtualization.models import VirtualMachine, VMInterface +from virtualization.models import VirtualDisk, VirtualMachine, VMInterface __all__ = ( + 'VirtualDiskTable', 'VirtualMachineTable', 'VirtualMachineVMInterfaceTable', 'VMInterfaceTable', @@ -155,3 +156,19 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable): row_attrs = { 'data-name': lambda record: record.name, } + + +class VirtualDiskTable(VMInterfaceTable): + actions = columns.ActionsColumn( + actions=('edit', 'delete'), + ) + + class Meta(NetBoxTable.Meta): + model = VirtualDisk + fields = ( + 'pk', 'id', 'name', 'size', 'tags', 'actions', + ) + default_columns = ('pk', 'name', 'size') + row_attrs = { + 'data-name': lambda record: record.name, + } diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 9e5d5a670..380160bd1 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -48,4 +48,13 @@ urlpatterns = [ path('interfaces//', include(get_model_urls('virtualization', 'vminterface'))), path('virtual-machines/interfaces/add/', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_vminterface'), + # Virtual disks + # path('disks/', views.VirtualDiskListView.as_view(), name='virtualdisk_list'), + # path('disks/add/', views.VirtualDiskCreateView.as_view(), name='virtualdisk_add'), + # path('disks/import/', views.VirtualDiskBulkImportView.as_view(), name='virtualdisk_import'), + # path('disks/edit/', views.VirtualDiskBulkEditView.as_view(), name='virtualdisk_bulk_edit'), + # path('disks/rename/', views.VirtualDiskBulkRenameView.as_view(), name='virtualdisk_bulk_rename'), + # path('disks/delete/', views.VirtualDiskBulkDeleteView.as_view(), name='virtualdisk_bulk_delete'), + # path('disks//', include(get_model_urls('virtualization', 'virtualdisk'))), + # path('virtual-machines/disks/add/', views.VirtualMachineBulkAddDiskView.as_view(), name='virtualmachine_bulk_add_disk'), ] diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 173d7047b..3c6f5c567 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -21,7 +21,7 @@ from tenancy.views import ObjectContactsView from utilities.utils import count_related from utilities.views import ViewTab, register_model_view from . import filtersets, forms, tables -from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface +from .models import Cluster, ClusterGroup, ClusterType, VirtualDisk, VirtualMachine, VMInterface # @@ -574,3 +574,83 @@ class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView): def get_required_permission(self): return f'virtualization.add_vminterface' + + +# +# Virtual Disk +# + +class VirtualDiskListView(generic.ObjectListView): + queryset = VirtualDisk.objects.all() + filterset = filtersets.VirtualDiskFilterSet + filterset_form = forms.VirtualDiskFilterForm + table = tables.VirtualDiskTable + + +@register_model_view(VirtualDisk) +class VirtualDiskView(generic.ObjectView): + queryset = VirtualDisk.objects.all() + + def get_extra_context(self, request, instance): + + # Get child interfaces + child_interfaces = VirtualDisk.objects.restrict(request.user, 'view').filter(parent=instance) + child_interfaces_tables = tables.VirtualDiskTable( + child_interfaces, + exclude=('virtual_machine',), + orderable=False + ) + + # Get assigned VLANs and annotate whether each is tagged or untagged + vlans = [] + if instance.untagged_vlan is not None: + vlans.append(instance.untagged_vlan) + vlans[0].tagged = False + for vlan in instance.tagged_vlans.restrict(request.user).prefetch_related('site', 'group', 'tenant', 'role'): + vlan.tagged = True + vlans.append(vlan) + vlan_table = InterfaceVLANTable( + interface=instance, + data=vlans, + orderable=False + ) + + return { + 'child_interfaces_table': child_interfaces_tables, + 'vlan_table': vlan_table, + } + + +# class VirtualDiskCreateView(generic.ComponentCreateView): +# queryset = VirtualDisk.objects.all() +# form = forms.VirtualDiskCreateForm +# model_form = forms.VirtualDiskForm + + +@register_model_view(VirtualDisk, 'edit') +class VirtualDiskEditView(generic.ObjectEditView): + queryset = VirtualDisk.objects.all() + form = forms.VirtualDiskForm + + +@register_model_view(VirtualDisk, 'delete') +class VirtualDiskDeleteView(generic.ObjectDeleteView): + queryset = VirtualDisk.objects.all() + + +class VirtualDiskBulkImportView(generic.BulkImportView): + queryset = VirtualDisk.objects.all() + model_form = forms.VirtualDiskImportForm + + +class VirtualDiskBulkEditView(generic.BulkEditView): + queryset = VirtualDisk.objects.all() + filterset = filtersets.VirtualDiskFilterSet + table = tables.VirtualDiskTable + form = forms.VirtualDiskBulkEditForm + + +class VirtualDiskBulkDeleteView(generic.BulkDeleteView): + queryset = VirtualDisk.objects.all() + filterset = filtersets.VirtualDiskFilterSet + table = tables.VirtualDiskTable