diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html
index cd66cb105..cb83142ea 100644
--- a/netbox/templates/virtualization/virtualmachine.html
+++ b/netbox/templates/virtualization/virtualmachine.html
@@ -58,24 +58,20 @@
Resources
diff --git a/netbox/templates/virtualization/virtualmachine_edit.html b/netbox/templates/virtualization/virtualmachine_edit.html
index 085f3cadf..7c240857f 100644
--- a/netbox/templates/virtualization/virtualmachine_edit.html
+++ b/netbox/templates/virtualization/virtualmachine_edit.html
@@ -7,9 +7,15 @@
{% render_field form.name %}
{% render_field form.status %}
+ {% render_field form.role %}
+ {% render_field form.platform %}
+
+
+
+
Cluster
+
{% 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 b85495d83..68f5118bf 100644
--- a/netbox/virtualization/api/serializers.py
+++ b/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',
]
diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py
index ea7686a23..31a002009 100644
--- a/netbox/virtualization/filters.py
+++ b/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)',
diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py
index bcae2f842..ef27ea994 100644
--- a/netbox/virtualization/forms.py
+++ b/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')
+ )
#
diff --git a/netbox/virtualization/migrations/0004_virtualmachine_add_role.py b/netbox/virtualization/migrations/0004_virtualmachine_add_role.py
new file mode 100644
index 000000000..10dec60fa
--- /dev/null
+++ b/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'),
+ ),
+ ]
diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py
index edf0385d4..47b179670 100644
--- a/netbox/virtualization/models.py
+++ b/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,
diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py
index 21314b51c..a40af6091 100644
--- a/netbox/virtualization/tables.py
+++ b/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')
#