Browse Source

Closes #1493: Added functional roles for virtual machines

Jeremy Stretch 7 years ago
parent
commit
6243fbfd0d

+ 27 - 10
netbox/templates/virtualization/virtualmachine.html

@@ -58,24 +58,20 @@
                     </td>
                 </tr>
                 <tr>
-                    <td>Cluster</td>
+                    <td>Role</td>
                     <td>
-                        {% if vm.cluster.group %}
-                            <a href="{{ vm.cluster.group.get_absolute_url }}">{{ vm.cluster.group }}</a>
-                            <i class="fa fa-angle-right"></i>
+                        {% if vm.role %}
+                            <a href="{% url 'virtualization:virtualmachine_list' %}?role={{ vm.role.slug }}">{{ vm.role }}</a>
+                        {% else %}
+                            <span class="text-muted">None</span>
                         {% endif %}
-                        <a href="{{ vm.cluster.get_absolute_url }}">{{ vm.cluster }}</a>
                     </td>
                 </tr>
                 <tr>
-                    <td>Cluster Type</td>
-                    <td>{{ vm.cluster.type }}</td>
-                </tr>
-                <tr>
                     <td>Platform</td>
                     <td>
                         {% if vm.platform %}
-                            <span>{{ vm.platform }}</span>
+                            <a href="{% url 'virtualization:virtualmachine_list' %}?platform={{ vm.platform.slug }}">{{ vm.platform }}</a>
                         {% else %}
                             <span class="text-muted">None</span>
                         {% endif %}
@@ -129,6 +125,27 @@
         </div>
         <div class="panel panel-default">
             <div class="panel-heading">
+                <strong>Cluster</strong>
+            </div>
+            <table class="table table-hover panel-body attr-table">
+                <tr>
+                    <td>Cluster</td>
+                    <td>
+                        {% if vm.cluster.group %}
+                            <a href="{{ vm.cluster.group.get_absolute_url }}">{{ vm.cluster.group }}</a>
+                            <i class="fa fa-angle-right"></i>
+                        {% endif %}
+                        <a href="{{ vm.cluster.get_absolute_url }}">{{ vm.cluster }}</a>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Cluster Type</td>
+                    <td>{{ vm.cluster.type }}</td>
+                </tr>
+            </table>
+        </div>
+        <div class="panel panel-default">
+            <div class="panel-heading">
                 <strong>Resources</strong>
             </div>
             <table class="table table-hover panel-body attr-table">

+ 7 - 1
netbox/templates/virtualization/virtualmachine_edit.html

@@ -7,9 +7,15 @@
         <div class="panel-body">
             {% render_field form.name %}
             {% render_field form.status %}
+            {% render_field form.role %}
+            {% render_field form.platform %}
+        </div>
+    </div>
+    <div class="panel panel-default">
+        <div class="panel-heading"><strong>Cluster</strong></div>
+        <div class="panel-body">
             {% render_field form.cluster_group %}
             {% render_field form.cluster %}
-            {% render_field form.platform %}
         </div>
     </div>
     <div class="panel panel-default">

+ 6 - 5
netbox/virtualization/api/serializers.py

@@ -2,7 +2,7 @@ from __future__ import unicode_literals
 
 from rest_framework import serializers
 
-from dcim.api.serializers import NestedPlatformSerializer, NestedSiteSerializer
+from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
 from dcim.constants import VIFACE_FF_CHOICES
 from dcim.models import Interface
 from extras.api.customfields import CustomFieldModelSerializer
@@ -86,14 +86,15 @@ class WritableClusterSerializer(CustomFieldModelSerializer):
 class VirtualMachineSerializer(CustomFieldModelSerializer):
     status = ChoiceFieldSerializer(choices=STATUS_CHOICES)
     cluster = NestedClusterSerializer()
+    role = NestedDeviceRoleSerializer()
     tenant = NestedTenantSerializer()
     platform = NestedPlatformSerializer()
 
     class Meta:
         model = VirtualMachine
         fields = [
-            'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
-            'disk', 'comments', 'custom_fields',
+            'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus',
+            'memory', 'disk', 'comments', 'custom_fields',
         ]
 
 
@@ -110,8 +111,8 @@ class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
     class Meta:
         model = VirtualMachine
         fields = [
-            'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
-            'disk', 'comments', 'custom_fields',
+            'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus',
+            'memory', 'disk', 'comments', 'custom_fields',
         ]
 
 

+ 13 - 2
netbox/virtualization/filters.py

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
 import django_filters
 from django.db.models import Q
 
-from dcim.models import Platform, Site
+from dcim.models import DeviceRole, Platform, Site
 from extras.filters import CustomFieldFilterSet
 from tenancy.models import Tenant
 from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
@@ -80,10 +80,21 @@ class VirtualMachineFilter(CustomFieldFilterSet):
         to_field_name='slug',
         label='Cluster group (slug)',
     )
-    cluster_id = NullableModelMultipleChoiceFilter(
+    cluster_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Cluster.objects.all(),
         label='Cluster (ID)',
     )
+    role_id = NullableModelMultipleChoiceFilter(
+        name='role_id',
+        queryset=DeviceRole.objects.all(),
+        label='Role (ID)',
+    )
+    role = NullableModelMultipleChoiceFilter(
+        name='role__slug',
+        queryset=DeviceRole.objects.all(),
+        to_field_name='slug',
+        label='Role (slug)',
+    )
     tenant_id = NullableModelMultipleChoiceFilter(
         queryset=Tenant.objects.all(),
         label='Tenant (ID)',

+ 31 - 5
netbox/virtualization/forms.py

@@ -8,7 +8,7 @@ from django.db.models import Count
 
 from dcim.constants import IFACE_FF_VIRTUAL, VIFACE_FF_CHOICES
 from dcim.formfields import MACAddressFormField
-from dcim.models import Device, Interface, Platform, Rack, Region, Site
+from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
 from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
@@ -222,7 +222,8 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     class Meta:
         model = VirtualMachine
         fields = [
-            'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
+            'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
+            'comments',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -251,6 +252,15 @@ class VirtualMachineCSVForm(forms.ModelForm):
             'invalid_choice': 'Invalid cluster name.',
         }
     )
+    role = forms.ModelChoiceField(
+        queryset=DeviceRole.objects.all(),
+        required=False,
+        to_field_name='name',
+        help_text='Name of functional role',
+        error_messages={
+            'invalid_choice': 'Invalid role name.'
+        }
+    )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
         required=False,
@@ -272,13 +282,14 @@ class VirtualMachineCSVForm(forms.ModelForm):
 
     class Meta:
         model = VirtualMachine
-        fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
+        fields = ['name', 'status', 'cluster', 'role', '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)
+    role = forms.ModelChoiceField(queryset=DeviceRole.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')
@@ -287,7 +298,7 @@ class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
     comments = CommentField(widget=SmallTextarea)
 
     class Meta:
-        nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
+        nullable_fields = ['role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
 
 
 def vm_status_choices():
@@ -303,13 +314,28 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
     cluster_group = FilterChoiceField(
         queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
-        null_option=(0, 'None'),
+        null_option=(0, 'None')
     )
     cluster_id = FilterChoiceField(
         queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
         label='Cluster'
     )
+    role = FilterChoiceField(
+        queryset=DeviceRole.objects.annotate(filter_count=Count('virtual_machines')),
+        to_field_name='slug',
+        null_option=(0, 'None')
+    )
     status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
+    tenant = FilterChoiceField(
+        queryset=Tenant.objects.annotate(filter_count=Count('virtual_machines')),
+        to_field_name='slug',
+        null_option=(0, 'None')
+    )
+    platform = FilterChoiceField(
+        queryset=Platform.objects.annotate(filter_count=Count('virtual_machines')),
+        to_field_name='slug',
+        null_option=(0, 'None')
+    )
 
 
 #

+ 22 - 0
netbox/virtualization/migrations/0004_virtualmachine_add_role.py

@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-29 14:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0044_virtualization'),
+        ('virtualization', '0003_cluster_add_site'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='virtualmachine',
+            name='role',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_machines', to='dcim.DeviceRole'),
+        ),
+    ]

+ 7 - 0
netbox/virtualization/models.py

@@ -179,6 +179,13 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
         default=STATUS_ACTIVE,
         verbose_name='Status'
     )
+    role = models.ForeignKey(
+        to='dcim.DeviceRole',
+        on_delete=models.PROTECT,
+        related_name='virtual_machines',
+        blank=True,
+        null=True
+    )
     primary_ip4 = models.OneToOneField(
         to='ipam.IPAddress',
         on_delete=models.SET_NULL,

+ 2 - 2
netbox/virtualization/tables.py

@@ -95,7 +95,7 @@ class VirtualMachineTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = VirtualMachine
-        fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk')
+        fields = ('pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk')
 
 
 class VirtualMachineDetailTable(VirtualMachineTable):
@@ -105,7 +105,7 @@ class VirtualMachineDetailTable(VirtualMachineTable):
 
     class Meta(BaseTable.Meta):
         model = VirtualMachine
-        fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip')
+        fields = ('pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip')
 
 
 #