From 0d8947bf36a1f26cda844f1d220691e5a07a81dc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 14 Sep 2017 14:35:34 -0400 Subject: [PATCH] Added a status field for virtual machines --- .../virtualization/virtualmachine.html | 6 +++++ .../virtualization/virtualmachine_edit.html | 1 + netbox/virtualization/api/serializers.py | 8 ++++-- netbox/virtualization/constants.py | 19 +++++++++++++ netbox/virtualization/filters.py | 4 +++ netbox/virtualization/forms.py | 27 +++++++++++++++---- .../0002_virtualmachine_add_status.py | 20 ++++++++++++++ netbox/virtualization/models.py | 9 +++++++ netbox/virtualization/tables.py | 7 ++++- 9 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 netbox/virtualization/constants.py create mode 100644 netbox/virtualization/migrations/0002_virtualmachine_add_status.py diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index a597ee060..cd66cb105 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -51,6 +51,12 @@ Name {{ vm.name }} + + Status + + {{ vm.get_status_display }} + + Cluster diff --git a/netbox/templates/virtualization/virtualmachine_edit.html b/netbox/templates/virtualization/virtualmachine_edit.html index 14acd13db..085f3cadf 100644 --- a/netbox/templates/virtualization/virtualmachine_edit.html +++ b/netbox/templates/virtualization/virtualmachine_edit.html @@ -6,6 +6,7 @@
Virtual Machine
{% render_field form.name %} + {% render_field form.status %} {% render_field form.cluster_group %} {% render_field form.cluster %} {% render_field form.platform %} diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 42d3f237e..6849a5396 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -8,6 +8,7 @@ from dcim.models import Interface from extras.api.customfields import CustomFieldModelSerializer from tenancy.api.serializers import NestedTenantSerializer from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer +from virtualization.constants import STATUS_CHOICES from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -82,6 +83,7 @@ class WritableClusterSerializer(CustomFieldModelSerializer): # class VirtualMachineSerializer(CustomFieldModelSerializer): + status = ChoiceFieldSerializer(choices=STATUS_CHOICES) cluster = NestedClusterSerializer() tenant = NestedTenantSerializer() platform = NestedPlatformSerializer() @@ -89,7 +91,8 @@ class VirtualMachineSerializer(CustomFieldModelSerializer): class Meta: model = VirtualMachine fields = [ - 'id', 'name', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', 'custom_fields', + 'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', + 'custom_fields', ] @@ -106,7 +109,8 @@ class WritableVirtualMachineSerializer(CustomFieldModelSerializer): class Meta: model = VirtualMachine fields = [ - 'id', 'name', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', 'custom_fields', + 'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', + 'custom_fields', ] diff --git a/netbox/virtualization/constants.py b/netbox/virtualization/constants.py new file mode 100644 index 000000000..b05a541eb --- /dev/null +++ b/netbox/virtualization/constants.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals + + +# VirtualMachine statuses (replicated from Device statuses) +STATUS_OFFLINE = 0 +STATUS_ACTIVE = 1 +STATUS_STAGED = 3 +STATUS_CHOICES = [ + [STATUS_ACTIVE, 'Active'], + [STATUS_OFFLINE, 'Offline'], + [STATUS_STAGED, 'Staged'], +] + +# Bootstrap CSS classes for VirtualMachine statuses +VM_STATUS_CLASSES = { + 0: 'warning', + 1: 'success', + 3: 'primary', +} diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 4efcd06e2..0cb06fa9c 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -7,6 +7,7 @@ from dcim.models import Platform from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter +from .constants import STATUS_CHOICES from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -55,6 +56,9 @@ class VirtualMachineFilter(CustomFieldFilterSet): method='search', label='Search', ) + status = django_filters.MultipleChoiceFilter( + choices=STATUS_CHOICES + ) cluster_group_id = NullableModelMultipleChoiceFilter( name='cluster__group', queryset=ClusterGroup.objects.all(), diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 8a1feaaea..7fc033d4c 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -12,10 +12,11 @@ from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFi from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, - ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, ConfirmationForm, - ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea, + add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, + ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, + ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea, ) +from .constants import STATUS_CHOICES from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -185,7 +186,9 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): class Meta: model = VirtualMachine - fields = ['name', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments'] + fields = [ + 'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', + ] def __init__(self, *args, **kwargs): @@ -200,6 +203,11 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): class VirtualMachineCSVForm(forms.ModelForm): + status = CSVChoiceField( + choices=STATUS_CHOICES, + required=False, + help_text='Operational status of device' + ) cluster = forms.ModelChoiceField( queryset=Cluster.objects.all(), to_field_name='name', @@ -229,11 +237,12 @@ class VirtualMachineCSVForm(forms.ModelForm): class Meta: model = VirtualMachine - fields = ['name', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments'] + fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments'] class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput) + status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='') 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) @@ -246,6 +255,13 @@ class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk'] +def vm_status_choices(): + status_counts = {} + for status in VirtualMachine.objects.values('status').annotate(count=Count('status')).order_by('status'): + status_counts[status['status']] = status['count'] + return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in STATUS_CHOICES] + + class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm): model = VirtualMachine q = forms.CharField(required=False, label='Search') @@ -258,6 +274,7 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm): queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')), label='Cluster' ) + status = forms.MultipleChoiceField(choices=vm_status_choices, required=False) # diff --git a/netbox/virtualization/migrations/0002_virtualmachine_add_status.py b/netbox/virtualization/migrations/0002_virtualmachine_add_status.py new file mode 100644 index 000000000..5b03b6e33 --- /dev/null +++ b/netbox/virtualization/migrations/0002_virtualmachine_add_status.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-14 17:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('virtualization', '0001_virtualization'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachine', + name='status', + field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [0, 'Offline'], [3, 'Staged']], default=1, verbose_name='Status'), + ), + ] diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index d1f949407..1501dabad 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -7,6 +7,7 @@ from django.utils.encoding import python_2_unicode_compatible from extras.models import CustomFieldModel, CustomFieldValue from utilities.models import CreatedUpdatedModel +from .constants import STATUS_ACTIVE, STATUS_CHOICES, VM_STATUS_CLASSES # @@ -139,6 +140,11 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel): max_length=64, unique=True ) + status = models.PositiveSmallIntegerField( + choices=STATUS_CHOICES, + default=STATUS_ACTIVE, + verbose_name='Status' + ) primary_ip4 = models.OneToOneField( to='ipam.IPAddress', on_delete=models.SET_NULL, @@ -187,3 +193,6 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel): def get_absolute_url(self): return reverse('virtualization:virtualmachine', args=[self.pk]) + + def get_status_class(self): + return VM_STATUS_CLASSES[self.status] diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 59f3f5393..28bf1c12a 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -20,6 +20,10 @@ CLUSTERGROUP_ACTIONS = """ {% endif %} """ +VIRTUALMACHINE_STATUS = """ +{{ record.get_status_display }} +""" + # # Cluster types @@ -79,12 +83,13 @@ class ClusterTable(BaseTable): class VirtualMachineTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() + status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS) cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')]) tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')]) class Meta(BaseTable.Meta): model = VirtualMachine - fields = ('pk', 'name', 'cluster', 'tenant', 'vcpus', 'memory', 'disk') + fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk') #