Skip to content

Commit 1ad6d94

Browse files
Fixes #13843: Fix assignment of VLAN group scope during bulk edit (#13887)
* Update VLANGroup bulk edit form to support all scope types * Fixes #13843: Fix scope assignment for VLAN groups during bulk edit * Add missed static file * Restore graphiql static assets
1 parent b759d69 commit 1ad6d94

File tree

5 files changed

+84
-11
lines changed

5 files changed

+84
-11
lines changed

netbox/ipam/forms/bulk_edit.py

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from django import forms
2+
from django.contrib.contenttypes.models import ContentType
23
from django.utils.translation import gettext_lazy as _
34

4-
from dcim.models import Region, Site, SiteGroup
5+
from dcim.models import Location, Rack, Region, Site, SiteGroup
56
from ipam.choices import *
67
from ipam.constants import *
78
from ipam.models import *
@@ -10,9 +11,10 @@
1011
from tenancy.models import Tenant
1112
from utilities.forms import add_blank_choice
1213
from utilities.forms.fields import (
13-
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
14+
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
1415
)
1516
from utilities.forms.widgets import BulkEditNullBooleanSelect
17+
from virtualization.models import Cluster, ClusterGroup
1618

1719
__all__ = (
1820
'AggregateBulkEditForm',
@@ -407,11 +409,6 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
407409

408410

409411
class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
410-
site = DynamicModelChoiceField(
411-
label=_('Site'),
412-
queryset=Site.objects.all(),
413-
required=False
414-
)
415412
min_vid = forms.IntegerField(
416413
min_value=VLAN_VID_MIN,
417414
max_value=VLAN_VID_MAX,
@@ -429,12 +426,84 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
429426
max_length=200,
430427
required=False
431428
)
429+
scope_type = ContentTypeChoiceField(
430+
label=_('Scope type'),
431+
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
432+
required=False
433+
)
434+
scope_id = forms.IntegerField(
435+
required=False,
436+
widget=forms.HiddenInput()
437+
)
438+
region = DynamicModelChoiceField(
439+
label=_('Region'),
440+
queryset=Region.objects.all(),
441+
required=False
442+
)
443+
sitegroup = DynamicModelChoiceField(
444+
queryset=SiteGroup.objects.all(),
445+
required=False,
446+
label=_('Site group')
447+
)
448+
site = DynamicModelChoiceField(
449+
label=_('Site'),
450+
queryset=Site.objects.all(),
451+
required=False,
452+
query_params={
453+
'region_id': '$region',
454+
'group_id': '$sitegroup',
455+
}
456+
)
457+
location = DynamicModelChoiceField(
458+
label=_('Location'),
459+
queryset=Location.objects.all(),
460+
required=False,
461+
query_params={
462+
'site_id': '$site',
463+
}
464+
)
465+
rack = DynamicModelChoiceField(
466+
label=_('Rack'),
467+
queryset=Rack.objects.all(),
468+
required=False,
469+
query_params={
470+
'site_id': '$site',
471+
'location_id': '$location',
472+
}
473+
)
474+
clustergroup = DynamicModelChoiceField(
475+
queryset=ClusterGroup.objects.all(),
476+
required=False,
477+
label=_('Cluster group')
478+
)
479+
cluster = DynamicModelChoiceField(
480+
label=_('Cluster'),
481+
queryset=Cluster.objects.all(),
482+
required=False,
483+
query_params={
484+
'group_id': '$clustergroup',
485+
}
486+
)
432487

433488
model = VLANGroup
434489
fieldsets = (
435490
(None, ('site', 'min_vid', 'max_vid', 'description')),
491+
(_('Scope'), ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
436492
)
437-
nullable_fields = ('site', 'description')
493+
nullable_fields = ('description',)
494+
495+
def clean(self):
496+
super().clean()
497+
498+
# Assign scope based on scope_type
499+
if self.cleaned_data.get('scope_type'):
500+
scope_field = self.cleaned_data['scope_type'].model
501+
if scope_obj := self.cleaned_data.get(scope_field):
502+
self.cleaned_data['scope_id'] = scope_obj.pk
503+
self.changed_data.append('scope_id')
504+
else:
505+
self.cleaned_data.pop('scope_type')
506+
self.changed_data.remove('scope_type')
438507

439508

440509
class VLANBulkEditForm(NetBoxModelBulkEditForm):

netbox/netbox/views/generic/bulk_views.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from copy import deepcopy
44

55
from django.contrib import messages
6+
from django.contrib.contenttypes.fields import GenericRel
67
from django.contrib.contenttypes.models import ContentType
78
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
89
from django.db import transaction, IntegrityError
@@ -519,9 +520,11 @@ def _update_objects(self, form, request):
519520
model_field = self.queryset.model._meta.get_field(name)
520521
if isinstance(model_field, (ManyToManyField, ManyToManyRel)):
521522
m2m_fields[name] = model_field
523+
elif isinstance(model_field, GenericRel):
524+
# Ignore generic relations (these may be used for other purposes in the form)
525+
continue
522526
else:
523527
model_fields[name] = model_field
524-
525528
except FieldDoesNotExist:
526529
# This form field is used to modify a field rather than set its value directly
527530
model_fields[name] = None

netbox/project-static/dist/netbox.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)