diff --git a/netbox/templates/virtualization/cluster_list.html b/netbox/templates/virtualization/cluster_list.html index edb67e0ab..dad7a9e49 100644 --- a/netbox/templates/virtualization/cluster_list.html +++ b/netbox/templates/virtualization/cluster_list.html @@ -17,7 +17,7 @@

{% block title %}Clusters{% endblock %}

- {% include 'utilities/obj_table.html' %} + {% include 'utilities/obj_table.html' with bulk_edit_url='virtualization:cluster_bulk_edit' bulk_delete_url='virtualization:cluster_bulk_delete' %}
{% include 'inc/search_panel.html' %} diff --git a/netbox/templates/virtualization/virtualmachine_list.html b/netbox/templates/virtualization/virtualmachine_list.html index 5cd601be5..38e842e74 100644 --- a/netbox/templates/virtualization/virtualmachine_list.html +++ b/netbox/templates/virtualization/virtualmachine_list.html @@ -17,7 +17,7 @@

{% block title %}Virtual Machines{% endblock %}

- {% include 'utilities/obj_table.html' %} + {% include 'utilities/obj_table.html' with bulk_edit_url='virtualization:virtualmachine_bulk_edit' bulk_delete_url='virtualization:virtualmachine_bulk_delete' %}
{% include 'inc/search_panel.html' %} diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 346c82bbf..b1ce920e4 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -13,8 +13,8 @@ from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, - ChainedModelChoiceField, ChainedModelMultipleChoiceField, ComponentForm, ConfirmationForm, ExpandableNameField, - FilterChoiceField, SlugField, + ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, ConfirmationForm, + ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea, ) from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -78,6 +78,15 @@ class ClusterCSVForm(forms.ModelForm): fields = ['name', 'type', 'group'] +class ClusterBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): + pk = forms.ModelMultipleChoiceField(queryset=Cluster.objects.all(), widget=forms.MultipleHiddenInput) + type = forms.ModelChoiceField(queryset=ClusterType.objects.all(), required=False) + group = forms.ModelChoiceField(queryset=ClusterGroup.objects.all(), required=False) + + class Meta: + nullable_fields = ['group'] + + class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Cluster q = forms.CharField(required=False, label='Search') @@ -226,11 +235,16 @@ class VirtualMachineCSVForm(forms.ModelForm): class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput) - cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False, label='Cluster') + cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False) tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) + platform = forms.ModelChoiceField(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) class Meta: - nullable_fields = ['tenant'] + nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk'] class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm): diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index c8d5163b6..bc8c44767 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -25,7 +25,8 @@ urlpatterns = [ url(r'^clusters/$', views.ClusterListView.as_view(), name='cluster_list'), url(r'^clusters/add/$', views.ClusterCreateView.as_view(), name='cluster_add'), url(r'^clusters/import/$', views.ClusterBulkImportView.as_view(), name='cluster_import'), - # url(r'^clusters/edit/$', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'), + url(r'^clusters/edit/$', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'), + url(r'^clusters/delete/$', views.ClusterBulkDeleteView.as_view(), name='cluster_bulk_delete'), url(r'^clusters/(?P\d+)/$', views.ClusterView.as_view(), name='cluster'), url(r'^clusters/(?P\d+)/edit/$', views.ClusterEditView.as_view(), name='cluster_edit'), url(r'^clusters/(?P\d+)/delete/$', views.ClusterDeleteView.as_view(), name='cluster_delete'), @@ -36,7 +37,8 @@ urlpatterns = [ url(r'^virtual-machines/$', views.VirtualMachineListView.as_view(), name='virtualmachine_list'), url(r'^virtual-machines/add/$', views.VirtualMachineCreateView.as_view(), name='virtualmachine_add'), url(r'^virtual-machines/import/$', views.VirtualMachineBulkImportView.as_view(), name='virtualmachine_import'), - # url(r'^virtual-machines/edit/$', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'), + url(r'^virtual-machines/edit/$', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'), + url(r'^virtual-machines/delete/$', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'), url(r'^virtual-machines/(?P\d+)/$', views.VirtualMachineView.as_view(), name='virtualmachine'), url(r'^virtual-machines/(?P\d+)/edit/$', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'), url(r'^virtual-machines/(?P\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index f7b626589..c8b3ea8f2 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -138,10 +138,22 @@ class ClusterBulkImportView(PermissionRequiredMixin, BulkImportView): default_return_url = 'virtualization:cluster_list' +class ClusterBulkEditView(PermissionRequiredMixin, BulkEditView): + permission_required = 'virtualization.change_cluster' + cls = Cluster + filter = filters.ClusterFilter + table = tables.ClusterTable + form = forms.ClusterBulkEditForm + default_return_url = 'virtualization:cluster_list' + + class ClusterBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'virtualization.delete_cluster' cls = Cluster - queryset = Cluster.objects.annotate(vm_count=Count('virtual_machines')) + queryset = Cluster.objects.annotate( + device_count=Count('devices', distinct=True), + vm_count=Count('virtual_machines', distinct=True) + ) table = tables.ClusterTable default_return_url = 'virtualization:cluster_list' @@ -227,7 +239,7 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View): # class VirtualMachineListView(ObjectListView): - queryset = VirtualMachine.objects.select_related('tenant') + queryset = VirtualMachine.objects.select_related('cluster', 'tenant') filter = filters.VirtualMachineFilter filter_form = forms.VirtualMachineFilterForm table = tables.VirtualMachineTable @@ -277,13 +289,21 @@ class VirtualMachineBulkImportView(PermissionRequiredMixin, BulkImportView): class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'virtualization.change_virtualmachine' cls = VirtualMachine - queryset = VirtualMachine.objects.select_related('tenant') + queryset = VirtualMachine.objects.select_related('cluster', 'tenant') filter = filters.VirtualMachineFilter table = tables.VirtualMachineTable form = forms.VirtualMachineBulkEditForm default_return_url = 'virtualization:virtualmachine_list' +class VirtualMachineBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'virtualization.delete_virtualmachine' + cls = VirtualMachine + queryset = VirtualMachine.objects.select_related('cluster', 'tenant') + table = tables.VirtualMachineTable + default_return_url = 'virtualization:virtualmachine_list' + + # # VM interfaces #