diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html
index 053c69121..a03d60523 100644
--- a/netbox/templates/tenancy/tenant.html
+++ b/netbox/templates/tenancy/tenant.html
@@ -142,6 +142,10 @@
Site |
diff --git a/netbox/templates/virtualization/cluster_edit.html b/netbox/templates/virtualization/cluster_edit.html
index 629c779ec..0e188d8ab 100644
--- a/netbox/templates/virtualization/cluster_edit.html
+++ b/netbox/templates/virtualization/cluster_edit.html
@@ -8,6 +8,7 @@
{% render_field form.name %}
{% render_field form.type %}
{% render_field form.group %}
+ {% render_field form.tenant %}
{% render_field form.site %}
diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py
index 28ae04694..7599029c5 100644
--- a/netbox/tenancy/api/serializers.py
+++ b/netbox/tenancy/api/serializers.py
@@ -31,11 +31,12 @@ class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer):
virtualmachine_count = serializers.IntegerField(read_only=True)
vlan_count = serializers.IntegerField(read_only=True)
vrf_count = serializers.IntegerField(read_only=True)
+ cluster_count = serializers.IntegerField(read_only=True)
class Meta:
model = Tenant
fields = [
'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'created',
'last_updated', 'circuit_count', 'device_count', 'ipaddress_count', 'prefix_count', 'rack_count',
- 'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count',
+ 'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count', 'cluster_count',
]
diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py
index 965ae2853..c7690d04b 100644
--- a/netbox/tenancy/views.py
+++ b/netbox/tenancy/views.py
@@ -9,7 +9,7 @@ from ipam.models import IPAddress, Prefix, VLAN, VRF
from utilities.views import (
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
)
-from virtualization.models import VirtualMachine
+from virtualization.models import VirtualMachine, Cluster
from . import filters, forms, tables
from .models import Tenant, TenantGroup
@@ -80,6 +80,7 @@ class TenantView(PermissionRequiredMixin, View):
'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
'virtualmachine_count': VirtualMachine.objects.filter(tenant=tenant).count(),
+ 'cluster_count': Cluster.objects.filter(tenant=tenant).count(),
}
return render(request, 'tenancy/tenant.html', {
diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py
index 0b98ce44a..75f36fbb6 100644
--- a/netbox/virtualization/api/serializers.py
+++ b/netbox/virtualization/api/serializers.py
@@ -38,6 +38,7 @@ class ClusterGroupSerializer(ValidatedModelSerializer):
class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
type = NestedClusterTypeSerializer()
group = NestedClusterGroupSerializer(required=False, allow_null=True)
+ tenant = NestedTenantSerializer(required=False, allow_null=True)
site = NestedSiteSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False)
device_count = serializers.IntegerField(read_only=True)
@@ -46,7 +47,7 @@ class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
class Meta:
model = Cluster
fields = [
- 'id', 'name', 'type', 'group', 'site', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
+ 'id', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'device_count', 'virtualmachine_count',
]
diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py
index f6d7f1230..94b75d154 100644
--- a/netbox/virtualization/api/views.py
+++ b/netbox/virtualization/api/views.py
@@ -41,7 +41,7 @@ class ClusterGroupViewSet(ModelViewSet):
class ClusterViewSet(CustomFieldModelViewSet):
queryset = Cluster.objects.prefetch_related(
- 'type', 'group', 'site', 'tags'
+ 'type', 'group', 'tenant', 'site', 'tags'
).annotate(
device_count=get_subquery(Device, 'cluster'),
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py
index 8365d6f91..a438d8598 100644
--- a/netbox/virtualization/filters.py
+++ b/netbox/virtualization/filters.py
@@ -4,6 +4,7 @@ from netaddr import EUI
from netaddr.core import AddrFormatError
from dcim.models import DeviceRole, Interface, Platform, Region, Site
+from tenancy.models import Tenant
from extras.filters import CustomFieldFilterSet
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import (
@@ -56,6 +57,10 @@ class ClusterFilter(CustomFieldFilterSet):
to_field_name='slug',
label='Cluster type (slug)',
)
+ tenant = django_filters.ModelMultipleChoiceFilter(
+ queryset=Tenant.objects.all(),
+ label="Tenant (ID)"
+ )
site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label='Site (ID)',
diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py
index 2cf55fcde..8094b0fbe 100644
--- a/netbox/virtualization/forms.py
+++ b/netbox/virtualization/forms.py
@@ -86,7 +86,7 @@ class ClusterForm(BootstrapMixin, CustomFieldForm):
class Meta:
model = Cluster
fields = [
- 'name', 'type', 'group', 'site', 'comments', 'tags',
+ 'name', 'type', 'group', 'tenant', 'site', 'comments', 'tags',
]
widgets = {
'type': APISelect(
@@ -128,6 +128,15 @@ class ClusterCSVForm(forms.ModelForm):
'invalid_choice': 'Invalid site name.',
}
)
+ tenant = forms.ModelChoiceField(
+ queryset=Tenant.objects.all(),
+ to_field_name='name',
+ required=False,
+ help_text='Name of assigned tenant',
+ error_messages={
+ 'invalid_choice': 'Invalid tenant name'
+ }
+ )
class Meta:
model = Cluster
@@ -153,6 +162,10 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
api_url="/api/virtualization/cluster-groups/"
)
)
+ tenant = forms.ModelChoiceField(
+ queryset=Tenant.objects.all(),
+ required=False
+ )
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False,
@@ -166,7 +179,7 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
class Meta:
nullable_fields = [
- 'group', 'site', 'comments',
+ 'group', 'site', 'comments', 'tenant',
]
@@ -193,6 +206,15 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
null_option=True,
)
)
+ tenant = FilterChoiceField(
+ queryset=Tenant.objects.all(),
+ null_label='-- None --',
+ required=False,
+ widget=APISelectMultiple(
+ api_url="/api/tenancy/tenants/",
+ null_option=True,
+ )
+ )
site = FilterChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
diff --git a/netbox/virtualization/migrations/0010_cluster_add_tenant.py b/netbox/virtualization/migrations/0010_cluster_add_tenant.py
new file mode 100644
index 000000000..425b32635
--- /dev/null
+++ b/netbox/virtualization/migrations/0010_cluster_add_tenant.py
@@ -0,0 +1,18 @@
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tenancy', '0001_initial'),
+ ('virtualization', '0009_custom_tag_models'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cluster',
+ name='tenant',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='clusters', to='tenancy.Tenant'),
+ ),
+ ]
diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py
index 6fea769e5..c47f516cf 100644
--- a/netbox/virtualization/models.py
+++ b/netbox/virtualization/models.py
@@ -103,6 +103,13 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
blank=True,
null=True
)
+ tenant = models.ForeignKey(
+ to='tenancy.Tenant',
+ on_delete=models.PROTECT,
+ related_name='tenants',
+ blank=True,
+ null=True
+ )
site = models.ForeignKey(
to='dcim.Site',
on_delete=models.PROTECT,
@@ -150,6 +157,7 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
self.type.name,
self.group.name if self.group else None,
self.site.name if self.site else None,
+ self.tenant.name if self.tenant else None,
self.comments,
)
diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py
index 6034dd8dc..ba4554ff5 100644
--- a/netbox/virtualization/tables.py
+++ b/netbox/virtualization/tables.py
@@ -84,13 +84,14 @@ class ClusterGroupTable(BaseTable):
class ClusterTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
+ tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
device_count = tables.Column(accessor=Accessor('devices.count'), orderable=False, verbose_name='Devices')
vm_count = tables.Column(accessor=Accessor('virtual_machines.count'), orderable=False, verbose_name='VMs')
class Meta(BaseTable.Meta):
model = Cluster
- fields = ('pk', 'name', 'type', 'group', 'site', 'device_count', 'vm_count')
+ fields = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
#
diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py
index 06a39e651..73eccb4b2 100644
--- a/netbox/virtualization/views.py
+++ b/netbox/virtualization/views.py
@@ -96,7 +96,7 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
class ClusterListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'virtualization.view_cluster'
- queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
+ queryset = Cluster.objects.prefetch_related('type', 'group', 'site', 'tenant')
table = tables.ClusterTable
filter = filters.ClusterFilter
filter_form = forms.ClusterFilterForm
|