mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
18296 Add Tenancy to VLAN Groups (#18690)
* 18296 add tenant to vlan groups * 18296 add tenant to vlan groups * 18296 add tenant to vlan groups * 18296 add tenant to vlan groups * 18296 review changes
This commit is contained in:
parent
ef89fc1264
commit
08b2fc424a
@ -37,6 +37,7 @@ class VLANGroupSerializer(NetBoxModelSerializer):
|
|||||||
scope = serializers.SerializerMethodField(read_only=True)
|
scope = serializers.SerializerMethodField(read_only=True)
|
||||||
vid_ranges = IntegerRangeSerializer(many=True, required=False)
|
vid_ranges = IntegerRangeSerializer(many=True, required=False)
|
||||||
utilization = serializers.CharField(read_only=True)
|
utilization = serializers.CharField(read_only=True)
|
||||||
|
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||||
|
|
||||||
# Related object counts
|
# Related object counts
|
||||||
vlan_count = RelatedObjectCountField('vlans')
|
vlan_count = RelatedObjectCountField('vlans')
|
||||||
@ -45,7 +46,7 @@ class VLANGroupSerializer(NetBoxModelSerializer):
|
|||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'vid_ranges',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'vid_ranges',
|
||||||
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization'
|
'tenant', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization'
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count')
|
||||||
validators = []
|
validators = []
|
||||||
|
@ -857,7 +857,7 @@ class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupFilterSet(OrganizationalModelFilterSet):
|
class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
||||||
scope_type = ContentTypeFilter()
|
scope_type = ContentTypeFilter()
|
||||||
region = django_filters.NumberFilter(
|
region = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
|
@ -430,11 +430,17 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
label=_('VLAN ID ranges'),
|
label=_('VLAN ID ranges'),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
tenant = DynamicModelChoiceField(
|
||||||
|
label=_('Tenant'),
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('site', 'vid_ranges', 'description'),
|
FieldSet('site', 'vid_ranges', 'description'),
|
||||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||||
|
FieldSet('tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description', 'scope')
|
nullable_fields = ('description', 'scope')
|
||||||
|
|
||||||
|
@ -438,10 +438,17 @@ class VLANGroupImportForm(NetBoxModelImportForm):
|
|||||||
vid_ranges = NumericRangeArrayField(
|
vid_ranges = NumericRangeArrayField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
tenant = CSVModelChoiceField(
|
||||||
|
label=_('Tenant'),
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Assigned tenant')
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = ('name', 'slug', 'scope_type', 'scope_id', 'vid_ranges', 'description', 'tags')
|
fields = ('name', 'slug', 'scope_type', 'scope_id', 'vid_ranges', 'tenant', 'description', 'tags')
|
||||||
labels = {
|
labels = {
|
||||||
'scope_id': 'Scope ID',
|
'scope_id': 'Scope ID',
|
||||||
}
|
}
|
||||||
|
@ -411,12 +411,13 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
class VLANGroupFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')),
|
FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')),
|
||||||
FieldSet('cluster_group', 'cluster', name=_('Cluster')),
|
FieldSet('cluster_group', 'cluster', name=_('Cluster')),
|
||||||
FieldSet('contains_vid', name=_('VLANs')),
|
FieldSet('contains_vid', name=_('VLANs')),
|
||||||
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
region = DynamicModelMultipleChoiceField(
|
region = DynamicModelMultipleChoiceField(
|
||||||
|
@ -598,7 +598,7 @@ class FHRPGroupAssignmentForm(forms.ModelForm):
|
|||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupForm(NetBoxModelForm):
|
class VLANGroupForm(TenancyForm, NetBoxModelForm):
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
vid_ranges = NumericRangeArrayField(
|
vid_ranges = NumericRangeArrayField(
|
||||||
label=_('VLAN IDs')
|
label=_('VLAN IDs')
|
||||||
@ -621,12 +621,13 @@ class VLANGroupForm(NetBoxModelForm):
|
|||||||
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
||||||
FieldSet('vid_ranges', name=_('Child VLANs')),
|
FieldSet('vid_ranges', name=_('Child VLANs')),
|
||||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'description', 'vid_ranges', 'scope_type', 'tags',
|
'name', 'slug', 'description', 'vid_ranges', 'scope_type', 'tenant_group', 'tenant', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -266,6 +266,7 @@ class VLANGroupType(OrganizationalObjectType):
|
|||||||
|
|
||||||
vlans: List[VLANType]
|
vlans: List[VLANType]
|
||||||
vid_ranges: List[str]
|
vid_ranges: List[str]
|
||||||
|
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||||
|
|
||||||
@strawberry_django.field
|
@strawberry_django.field
|
||||||
def scope(self) -> Annotated[Union[
|
def scope(self) -> Annotated[Union[
|
||||||
|
26
netbox/ipam/migrations/0077_vlangroup_tenant.py
Normal file
26
netbox/ipam/migrations/0077_vlangroup_tenant.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-02-20 17:49
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0076_natural_ordering'),
|
||||||
|
('tenancy', '0017_natural_ordering'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlangroup',
|
||||||
|
name='tenant',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name='vlan_groups',
|
||||||
|
to='tenancy.tenant',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -62,6 +62,13 @@ class VLANGroup(OrganizationalModel):
|
|||||||
verbose_name=_('VLAN ID ranges'),
|
verbose_name=_('VLAN ID ranges'),
|
||||||
default=default_vid_ranges
|
default=default_vid_ranges
|
||||||
)
|
)
|
||||||
|
tenant = models.ForeignKey(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='vlan_groups',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
_total_vlan_ids = models.PositiveBigIntegerField(
|
_total_vlan_ids = models.PositiveBigIntegerField(
|
||||||
default=VLAN_VID_MAX - VLAN_VID_MIN + 1
|
default=VLAN_VID_MAX - VLAN_VID_MIN + 1
|
||||||
)
|
)
|
||||||
|
@ -28,7 +28,7 @@ AVAILABLE_LABEL = mark_safe('<span class="badge text-bg-success">Available</span
|
|||||||
# VLAN groups
|
# VLAN groups
|
||||||
#
|
#
|
||||||
|
|
||||||
class VLANGroupTable(NetBoxTable):
|
class VLANGroupTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
verbose_name=_('Name'),
|
||||||
linkify=True
|
linkify=True
|
||||||
@ -65,9 +65,11 @@ class VLANGroupTable(NetBoxTable):
|
|||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'scope_type', 'scope', 'vid_ranges_list', 'vlan_count', 'slug', 'description',
|
'pk', 'id', 'name', 'scope_type', 'scope', 'vid_ranges_list', 'vlan_count', 'slug', 'description',
|
||||||
'tags', 'created', 'last_updated', 'actions', 'utilization',
|
'tenant', 'tenant_group', 'tags', 'created', 'last_updated', 'actions', 'utilization',
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'name', 'scope_type', 'scope', 'vlan_count', 'utilization', 'tenant', 'description'
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'utilization', 'description')
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1568,27 +1568,45 @@ class VLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
cluster = Cluster(name='Cluster 1', type=clustertype)
|
cluster = Cluster(name='Cluster 1', type=clustertype)
|
||||||
cluster.save()
|
cluster.save()
|
||||||
|
|
||||||
|
tenant_groups = (
|
||||||
|
TenantGroup(name='Tenant group 1', slug='tenant-group-1'),
|
||||||
|
TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
|
||||||
|
TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
|
||||||
|
)
|
||||||
|
for tenantgroup in tenant_groups:
|
||||||
|
tenantgroup.save()
|
||||||
|
|
||||||
|
tenants = (
|
||||||
|
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
|
||||||
|
Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1]),
|
||||||
|
Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[2]),
|
||||||
|
)
|
||||||
|
Tenant.objects.bulk_create(tenants)
|
||||||
|
|
||||||
vlan_groups = (
|
vlan_groups = (
|
||||||
VLANGroup(
|
VLANGroup(
|
||||||
name='VLAN Group 1',
|
name='VLAN Group 1',
|
||||||
slug='vlan-group-1',
|
slug='vlan-group-1',
|
||||||
vid_ranges=[NumericRange(1, 11), NumericRange(100, 200)],
|
vid_ranges=[NumericRange(1, 11), NumericRange(100, 200)],
|
||||||
scope=region,
|
scope=region,
|
||||||
description='foobar1'
|
description='foobar1',
|
||||||
|
tenant=tenants[0]
|
||||||
),
|
),
|
||||||
VLANGroup(
|
VLANGroup(
|
||||||
name='VLAN Group 2',
|
name='VLAN Group 2',
|
||||||
slug='vlan-group-2',
|
slug='vlan-group-2',
|
||||||
vid_ranges=[NumericRange(1, 11), NumericRange(200, 300)],
|
vid_ranges=[NumericRange(1, 11), NumericRange(200, 300)],
|
||||||
scope=sitegroup,
|
scope=sitegroup,
|
||||||
description='foobar2'
|
description='foobar2',
|
||||||
|
tenant=tenants[1]
|
||||||
),
|
),
|
||||||
VLANGroup(
|
VLANGroup(
|
||||||
name='VLAN Group 3',
|
name='VLAN Group 3',
|
||||||
slug='vlan-group-3',
|
slug='vlan-group-3',
|
||||||
vid_ranges=[NumericRange(1, 11), NumericRange(300, 400)],
|
vid_ranges=[NumericRange(1, 11), NumericRange(300, 400)],
|
||||||
scope=site,
|
scope=site,
|
||||||
description='foobar3'
|
description='foobar3',
|
||||||
|
tenant=tenants[1]
|
||||||
),
|
),
|
||||||
VLANGroup(
|
VLANGroup(
|
||||||
name='VLAN Group 4',
|
name='VLAN Group 4',
|
||||||
@ -1671,6 +1689,20 @@ class VLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'cluster': Cluster.objects.first().pk}
|
params = {'cluster': Cluster.objects.first().pk}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_tenant(self):
|
||||||
|
tenants = Tenant.objects.all()[:2]
|
||||||
|
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'tenant': [tenants[0].slug, tenants[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
def test_tenant_group(self):
|
||||||
|
tenant_groups = TenantGroup.objects.all()[:2]
|
||||||
|
params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
|
||||||
class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
|
@ -46,6 +46,15 @@
|
|||||||
<th scope="row">Utilization</th>
|
<th scope="row">Utilization</th>
|
||||||
<td>{% utilization_graph object.utilization %}</td>
|
<td>{% utilization_graph object.utilization %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Tenant" %}</th>
|
||||||
|
<td>
|
||||||
|
{% if object.tenant.group %}
|
||||||
|
{{ object.tenant.group|linkify }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ object.tenant|linkify|placeholder }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
Loading…
Reference in New Issue
Block a user