소스 검색

Fixes #22588: Filter VLANs by Site Group scope when assigning to a prefix (#22684)

Sri Chandraja Reddy Allala 6 일 전
부모
커밋
3e3d36cc2d
5개의 변경된 파일52개의 추가작업 그리고 6개의 파일을 삭제
  1. 8 0
      netbox/ipam/filtersets.py
  2. 11 3
      netbox/ipam/forms/model_forms.py
  3. 17 0
      netbox/ipam/querysets.py
  4. 5 0
      netbox/ipam/tests/test_filtersets.py
  5. 11 3
      netbox/ipam/tests/test_forms.py

+ 8 - 0
netbox/ipam/filtersets.py

@@ -1069,6 +1069,10 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
         queryset=Site.objects.all(),
         method='get_for_site'
     )
+    available_at_site_group = django_filters.ModelChoiceFilter(
+        queryset=SiteGroup.objects.all(),
+        method='get_for_site_group'
+    )
     available_on_device = django_filters.ModelChoiceFilter(
         queryset=Device.objects.select_related('cluster'),
         method='get_for_device'
@@ -1130,6 +1134,10 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     def get_for_site(self, queryset, name, value):
         return queryset.get_for_site(value)
 
+    @extend_schema_field(OpenApiTypes.STR)
+    def get_for_site_group(self, queryset, name, value):
+        return queryset.get_for_site_group(value)
+
     @extend_schema_field(OpenApiTypes.STR)
     def get_for_device(self, queryset, name, value):
         return queryset.get_for_device(value)

+ 11 - 3
netbox/ipam/forms/model_forms.py

@@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 
 from dcim.forms.mixins import ScopedForm
-from dcim.models import Device, Interface, Site
+from dcim.models import Device, Interface, Site, SiteGroup
 from ipam.choices import *
 from ipam.constants import *
 from ipam.formfields import IPNetworkFormField
@@ -245,9 +245,17 @@ class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        # #18605: only filter VLAN select list if scope field is a Site
+        # #18605: only filter VLAN select list if scope field is a Site or Site Group
         if scope_field := self.fields.get('scope', None):
-            if scope_field.queryset.model is not Site:
+            if scope_field.queryset.model is Site:
+                pass  # already filtered by available_at_site
+            elif scope_field.queryset.model is SiteGroup:
+                self.fields['vlan'].widget.dynamic_params.clear()
+                self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None)
+                self.fields['vlan'].widget.add_query_params({
+                    'available_at_site_group': '$scope',
+                })
+            else:
                 self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None)
 
 

+ 17 - 0
netbox/ipam/querysets.py

@@ -290,6 +290,23 @@ class VLANQuerySet(RestrictedQuerySet):
             Q(group__isnull=True, site__isnull=True)  # Global VLANs
         )
 
+    def get_for_site_group(self, site_group):
+        """
+        Return all VLANs available to the specified site group.
+        """
+        if site_group is None:
+            return self.none()
+        from .models import VLANGroup
+        q = Q(
+            scope_type=ContentType.objects.get_by_natural_key('dcim', 'sitegroup'),
+            scope_id__in=site_group.get_ancestors(include_self=True)
+        )
+        return self.filter(
+            Q(group__in=VLANGroup.objects.filter(q)) |
+            Q(group__scope_id__isnull=True, site__isnull=True) |  # Global group VLANs
+            Q(group__isnull=True, site__isnull=True)  # Global VLANs
+        )
+
     def get_for_device(self, device):
         """
         Return all VLANs available to the specified Device.

+ 5 - 0
netbox/ipam/tests/test_filtersets.py

@@ -2221,6 +2221,11 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'available_at_site': site_id}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)  # 4 scoped + 1 global group + 1 global
 
+    def test_available_at_site_group(self):
+        site_group = SiteGroup.objects.get(name='Site Group 1')
+        params = {'available_at_site_group': site_group.pk}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)  # 1 scoped + 1 global group + 1 global
+
     def test_interface(self):
         interface_id = Interface.objects.first().pk
         params = {'interface_id': interface_id}

+ 11 - 3
netbox/ipam/tests/test_forms.py

@@ -29,19 +29,27 @@ class PrefixFormTestCase(TestCase):
 
         assert form.fields['vlan'].widget.attrs['data-dynamic-params'] == self.default_dynamic_params
 
+    def test_vlan_field_sets_dynamic_params_for_scope_site_group(self):
+        """data-dynamic-params present with available_at_site_group when scope type is Site Group"""
+        site_group = SiteGroup.objects.create(name='Site Group 1', slug='site-group-1')
+        form = PrefixForm(data={
+            'scope_type': ContentType.objects.get_for_model(SiteGroup).id,
+            'scope': site_group,
+        })
+        expected = '[{"fieldName":"scope","queryParam":"available_at_site_group"}]'
+        assert form.fields['vlan'].widget.attrs['data-dynamic-params'] == expected
+
     def test_vlan_field_does_not_set_dynamic_params_for_other_scopes(self):
-        """data-dynamic-params not present when scope type is populated by is not Site"""
+        """data-dynamic-params not present when scope type is not Site or Site Group"""
         cases = [
             Region(name='Region 1', slug='region-1'),
             Location(site=self.site, name='Location 1', slug='location-1'),
-            SiteGroup(name='Site Group 1', slug='site-group-1'),
         ]
         for case in cases:
             form = PrefixForm(data={
                 'scope_type': ContentType.objects.get_for_model(case._meta.model).id,
                 'scope': case,
             })
-
             assert 'data-dynamic-params' not in form.fields['vlan'].widget.attrs