mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge pull request #3572 from frelon/cluster-tenant
Add tenancy to cluster
This commit is contained in:
@ -142,6 +142,10 @@
|
|||||||
<h2><a href="{% url 'virtualization:virtualmachine_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.virtualmachine_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.virtualmachine_count }}</a></h2>
|
<h2><a href="{% url 'virtualization:virtualmachine_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.virtualmachine_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.virtualmachine_count }}</a></h2>
|
||||||
<p>Virtual machines</p>
|
<p>Virtual machines</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<h2><a href="{% url 'virtualization:cluster_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.cluster_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.cluster_count }}</a></h2>
|
||||||
|
<p>Clusters</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -83,6 +83,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tenant</td>
|
||||||
|
<td>
|
||||||
|
{% if cluster.tenant %}
|
||||||
|
<a href="{{ cluster.tenant.get_absolute_url }}">{{ cluster.tenant }}</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.type %}
|
{% render_field form.type %}
|
||||||
{% render_field form.group %}
|
{% render_field form.group %}
|
||||||
|
{% render_field form.tenant %}
|
||||||
{% render_field form.site %}
|
{% render_field form.site %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,11 +31,12 @@ class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
virtualmachine_count = serializers.IntegerField(read_only=True)
|
virtualmachine_count = serializers.IntegerField(read_only=True)
|
||||||
vlan_count = serializers.IntegerField(read_only=True)
|
vlan_count = serializers.IntegerField(read_only=True)
|
||||||
vrf_count = serializers.IntegerField(read_only=True)
|
vrf_count = serializers.IntegerField(read_only=True)
|
||||||
|
cluster_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tenant
|
model = Tenant
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
||||||
'last_updated', 'circuit_count', 'device_count', 'ipaddress_count', 'prefix_count', 'rack_count',
|
'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',
|
||||||
]
|
]
|
||||||
|
@ -9,7 +9,7 @@ from ipam.models import IPAddress, Prefix, VLAN, VRF
|
|||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine, Cluster
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .models import Tenant, TenantGroup
|
from .models import Tenant, TenantGroup
|
||||||
|
|
||||||
@ -80,6 +80,7 @@ class TenantView(PermissionRequiredMixin, View):
|
|||||||
'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
|
'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
|
||||||
'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
|
'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
|
||||||
'virtualmachine_count': VirtualMachine.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', {
|
return render(request, 'tenancy/tenant.html', {
|
||||||
|
@ -38,6 +38,7 @@ class ClusterGroupSerializer(ValidatedModelSerializer):
|
|||||||
class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
type = NestedClusterTypeSerializer()
|
type = NestedClusterTypeSerializer()
|
||||||
group = NestedClusterGroupSerializer(required=False, allow_null=True)
|
group = NestedClusterGroupSerializer(required=False, allow_null=True)
|
||||||
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
site = NestedSiteSerializer(required=False, allow_null=True)
|
site = NestedSiteSerializer(required=False, allow_null=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
device_count = serializers.IntegerField(read_only=True)
|
device_count = serializers.IntegerField(read_only=True)
|
||||||
@ -46,7 +47,7 @@ class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Cluster
|
model = Cluster
|
||||||
fields = [
|
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',
|
'device_count', 'virtualmachine_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class ClusterGroupViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class ClusterViewSet(CustomFieldModelViewSet):
|
class ClusterViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Cluster.objects.prefetch_related(
|
queryset = Cluster.objects.prefetch_related(
|
||||||
'type', 'group', 'site', 'tags'
|
'type', 'group', 'tenant', 'site', 'tags'
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'cluster'),
|
device_count=get_subquery(Device, 'cluster'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
|
virtualmachine_count=get_subquery(VirtualMachine, 'cluster')
|
||||||
|
@ -4,6 +4,7 @@ from netaddr import EUI
|
|||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from dcim.models import DeviceRole, Interface, Platform, Region, Site
|
from dcim.models import DeviceRole, Interface, Platform, Region, Site
|
||||||
|
from tenancy.models import Tenant
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
@ -56,6 +57,10 @@ class ClusterFilter(CustomFieldFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Cluster type (slug)',
|
label='Cluster type (slug)',
|
||||||
)
|
)
|
||||||
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
label="Tenant (ID)"
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
|
@ -86,7 +86,7 @@ class ClusterForm(BootstrapMixin, CustomFieldForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Cluster
|
model = Cluster
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'type', 'group', 'site', 'comments', 'tags',
|
'name', 'type', 'group', 'tenant', 'site', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'type': APISelect(
|
'type': APISelect(
|
||||||
@ -128,6 +128,15 @@ class ClusterCSVForm(forms.ModelForm):
|
|||||||
'invalid_choice': 'Invalid site name.',
|
'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:
|
class Meta:
|
||||||
model = Cluster
|
model = Cluster
|
||||||
@ -153,6 +162,10 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
|||||||
api_url="/api/virtualization/cluster-groups/"
|
api_url="/api/virtualization/cluster-groups/"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
tenant = forms.ModelChoiceField(
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -166,7 +179,7 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = [
|
nullable_fields = [
|
||||||
'group', 'site', 'comments',
|
'group', 'site', 'comments', 'tenant',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -193,6 +206,15 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
null_option=True,
|
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(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
18
netbox/virtualization/migrations/0010_cluster_add_tenant.py
Normal file
18
netbox/virtualization/migrations/0010_cluster_add_tenant.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -103,6 +103,13 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
tenant = models.ForeignKey(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='tenants',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
to='dcim.Site',
|
to='dcim.Site',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -150,6 +157,7 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
|
|||||||
self.type.name,
|
self.type.name,
|
||||||
self.group.name if self.group else None,
|
self.group.name if self.group else None,
|
||||||
self.site.name if self.site else None,
|
self.site.name if self.site else None,
|
||||||
|
self.tenant.name if self.tenant else None,
|
||||||
self.comments,
|
self.comments,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,13 +84,14 @@ class ClusterGroupTable(BaseTable):
|
|||||||
class ClusterTable(BaseTable):
|
class ClusterTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||||
device_count = tables.Column(accessor=Accessor('devices.count'), orderable=False, verbose_name='Devices')
|
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')
|
vm_count = tables.Column(accessor=Accessor('virtual_machines.count'), orderable=False, verbose_name='VMs')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Cluster
|
model = Cluster
|
||||||
fields = ('pk', 'name', 'type', 'group', 'site', 'device_count', 'vm_count')
|
fields = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -96,7 +96,7 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class ClusterListView(PermissionRequiredMixin, ObjectListView):
|
class ClusterListView(PermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'virtualization.view_cluster'
|
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
|
table = tables.ClusterTable
|
||||||
filter = filters.ClusterFilter
|
filter = filters.ClusterFilter
|
||||||
filter_form = forms.ClusterFilterForm
|
filter_form = forms.ClusterFilterForm
|
||||||
|
Reference in New Issue
Block a user