1
0
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:
Jeremy Stretch
2019-10-23 11:53:22 -04:00
committed by GitHub
13 changed files with 80 additions and 8 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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',
] ]

View File

@ -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', {

View File

@ -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',
] ]

View File

@ -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')

View File

@ -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)',

View File

@ -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',

View 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'),
),
]

View File

@ -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,
) )

View File

@ -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')
# #

View File

@ -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