diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index f4a6236cb..5c5b9e8d3 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -552,6 +552,12 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): rack = django_filters.NumberFilter( method='filter_scope' ) + clustergroup = django_filters.NumberFilter( + method='filter_scope' + ) + cluster = django_filters.NumberFilter( + method='filter_scope' + ) class Meta: model = VLANGroup @@ -559,7 +565,7 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): def filter_scope(self, queryset, name, value): return queryset.filter( - scope_type=ContentType.objects.get(app_label='dcim', model=name), + scope_type=ContentType.objects.get(model=name), scope_id=value ) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 9003f295c..37a3a9d2c 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -1,5 +1,4 @@ from django import forms -from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup @@ -14,7 +13,7 @@ from utilities.forms import ( DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, NumericArrayField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) -from virtualization.models import Cluster, VirtualMachine, VMInterface +from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface from .choices import * from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF @@ -1153,17 +1152,28 @@ class VLANGroupForm(BootstrapMixin, CustomFieldModelForm): 'location_id': '$location', } ) + cluster_group = DynamicModelChoiceField( + queryset=ClusterGroup.objects.all(), + required=False, + initial_params={ + 'clusters': '$cluster' + } + ) + cluster = DynamicModelChoiceField( + queryset=Cluster.objects.all(), + required=False, + query_params={ + 'group_id': '$cluster_group', + } + ) slug = SlugField() class Meta: model = VLANGroup fields = [ - 'name', 'slug', 'description', 'region', 'site_group', 'site', 'location', 'rack', + 'name', 'slug', 'description', 'region', 'site_group', 'site', 'location', 'rack', 'cluster_group', + 'cluster', ] - fieldsets = ( - ('VLAN Group', ('name', 'slug', 'description')), - ('Scope', ('region', 'site_group', 'site', 'location', 'rack')), - ) def __init__(self, *args, **kwargs): instance = kwargs.get('instance') @@ -1180,6 +1190,10 @@ class VLANGroupForm(BootstrapMixin, CustomFieldModelForm): initial['site_group'] = instance.scope elif type(instance.scope) is Region: initial['region'] = instance.scope + elif type(instance.scope) is Cluster: + initial['cluster'] = instance.scope + elif type(instance.scope) is ClusterGroup: + initial['cluster_group'] = instance.scope kwargs['initial'] = initial @@ -1189,8 +1203,10 @@ class VLANGroupForm(BootstrapMixin, CustomFieldModelForm): super().clean() # Assign scope object - self.instance.scope = self.cleaned_data['rack'] or self.cleaned_data['location'] or self.cleaned_data['site'] \ - or self.cleaned_data['site_group'] or self.cleaned_data['region'] or None + self.instance.scope = self.cleaned_data['rack'] or self.cleaned_data['location'] or \ + self.cleaned_data['site'] or self.cleaned_data['site_group'] or \ + self.cleaned_data['region'] or self.cleaned_data['cluster'] or \ + self.cleaned_data['cluster_group'] or None class VLANGroupCSVForm(CustomFieldModelCSVForm): diff --git a/netbox/ipam/migrations/0045_vlangroup_scope.py b/netbox/ipam/migrations/0045_vlangroup_scope.py index 0b658219b..8795750d2 100644 --- a/netbox/ipam/migrations/0045_vlangroup_scope.py +++ b/netbox/ipam/migrations/0045_vlangroup_scope.py @@ -23,7 +23,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='vlangroup', name='scope_type', - field=models.ForeignKey(blank=True, limit_choices_to=models.Q(('app_label', 'dcim'), ('model__in', ['region', 'sitegroup', 'site', 'location', 'rack'])), null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), + field=models.ForeignKey(blank=True, limit_choices_to=models.Q(model__in=['region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster']), null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), ), migrations.AlterModelOptions( name='vlangroup', diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 30c76d6cf..aa25fde46 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -35,8 +35,7 @@ class VLANGroup(OrganizationalModel): to=ContentType, on_delete=models.CASCADE, limit_choices_to=Q( - app_label='dcim', - model__in=['region', 'sitegroup', 'site', 'location', 'rack'] + model__in=['region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster'] ), blank=True, null=True diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index f262a6a17..822aca247 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -642,6 +642,7 @@ class VLANGroupListView(generic.ObjectListView): class VLANGroupEditView(generic.ObjectEditView): queryset = VLANGroup.objects.all() model_form = forms.VLANGroupForm + template_name = 'ipam/vlangroup_edit.html' class VLANGroupDeleteView(generic.ObjectDeleteView): @@ -655,7 +656,7 @@ class VLANGroupBulkImportView(generic.BulkImportView): class VLANGroupBulkDeleteView(generic.BulkDeleteView): - queryset = VLANGroup.objects.prefetch_related('site').annotate( + queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) filterset = filters.VLANGroupFilterSet diff --git a/netbox/templates/ipam/vlangroup_edit.html b/netbox/templates/ipam/vlangroup_edit.html new file mode 100644 index 000000000..2afedd981 --- /dev/null +++ b/netbox/templates/ipam/vlangroup_edit.html @@ -0,0 +1,49 @@ +{% extends 'generic/object_edit.html' %} +{% load form_helpers %} +{% load helpers %} + +{% block form %} +
+
VLAN Group
+
+ {% render_field form.name %} + {% render_field form.slug %} + {% render_field form.description %} +
+
+
+
+ Scope +
+
+ {% with virtual_tab_active=form.initial.cluster %} + +
+
+ {% render_field form.region %} + {% render_field form.site_group %} + {% render_field form.site %} + {% render_field form.location %} + {% render_field form.rack %} +
+
+ {% render_field form.cluster_group %} + {% render_field form.cluster %} +
+
+ The VLAN group will be limited in scope to the most-specific object selected above. + {% endwith %} +
+
+ {% if form.custom_fields %} +
+
Custom Fields
+
+ {% render_custom_fields form %} +
+
+ {% endif %} +{% endblock %}