Просмотр исходного кода

Add 'available_on' VLAN filters for devices & VMs

Jeremy Stretch 4 лет назад
Родитель
Сommit
b91e5763e2
5 измененных файлов с 134 добавлено и 100 удалено
  1. 16 42
      netbox/dcim/forms.py
  2. 14 0
      netbox/ipam/filters.py
  3. 2 1
      netbox/ipam/models/vlans.py
  4. 84 0
      netbox/ipam/querysets.py
  5. 18 57
      netbox/virtualization/forms.py

+ 16 - 42
netbox/dcim/forms.py

@@ -3077,20 +3077,12 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         required=False,
-        label='Untagged VLAN',
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        label='Untagged VLAN'
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         required=False,
-        label='Tagged VLANs',
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        label='Tagged VLANs'
     )
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -3124,9 +3116,9 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
         self.fields['parent'].widget.add_query_param('device_id', device.pk)
         self.fields['lag'].widget.add_query_param('device_id', device.pk)
 
-        # Add current site to VLANs query params
-        self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
-        self.fields['tagged_vlans'].widget.add_query_param('site_id', device.site.pk)
+        # Limit VLAN choices by device
+        self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
+        self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
 
 
 class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
@@ -3177,19 +3169,11 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
     )
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
     field_order = (
         'device', 'name_pattern', 'label_pattern', 'type', 'enabled', 'parent', 'lag', 'mtu', 'mac_address',
@@ -3199,12 +3183,10 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        # Add current site to VLANs query params
-        device = Device.objects.get(
-            pk=self.initial.get('device') or self.data.get('device')
-        )
-        self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
-        self.fields['tagged_vlans'].widget.add_query_param('site_id', device.site.pk)
+        # Limit VLAN choices by device
+        device_id = self.initial.get('device') or self.data.get('device')
+        self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device_id)
+        self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device_id)
 
 
 class InterfaceBulkCreateForm(
@@ -3264,19 +3246,11 @@ class InterfaceBulkEditForm(
     )
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
 
     class Meta:
@@ -3293,9 +3267,9 @@ class InterfaceBulkEditForm(
             self.fields['parent'].widget.add_query_param('device_id', device.pk)
             self.fields['lag'].widget.add_query_param('device_id', device.pk)
 
-            # Add current site to VLANs query params
-            self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
-            self.fields['tagged_vlans'].widget.add_query_param('site_id', device.site.pk)
+            # Limit VLAN choices by device
+            self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
+            self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
 
         else:
             # See #4523

+ 14 - 0
netbox/ipam/filters.py

@@ -635,6 +635,14 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
         choices=VLANStatusChoices,
         null_value=None
     )
+    available_on_device = django_filters.ModelChoiceFilter(
+        queryset=Device.objects.all(),
+        method='get_for_device'
+    )
+    available_on_virtualmachine = django_filters.ModelChoiceFilter(
+        queryset=VirtualMachine.objects.all(),
+        method='get_for_virtualmachine'
+    )
     tag = TagFilter()
 
     class Meta:
@@ -651,6 +659,12 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
             pass
         return queryset.filter(qs_filter)
 
+    def get_for_device(self, queryset, name, value):
+        return queryset.get_for_device(value)
+
+    def get_for_virtualmachine(self, queryset, name, value):
+        return queryset.get_for_virtualmachine(value)
+
 
 class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
     q = django_filters.CharFilter(

+ 2 - 1
netbox/ipam/models/vlans.py

@@ -9,6 +9,7 @@ from dcim.models import Interface
 from extras.utils import extras_features
 from ipam.choices import *
 from ipam.constants import *
+from ipam.querysets import VLANQuerySet
 from netbox.models import OrganizationalModel, PrimaryModel
 from utilities.querysets import RestrictedQuerySet
 from virtualization.models import VMInterface
@@ -156,7 +157,7 @@ class VLAN(PrimaryModel):
         blank=True
     )
 
-    objects = RestrictedQuerySet.as_manager()
+    objects = VLANQuerySet.as_manager()
 
     csv_headers = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
     clone_fields = [

+ 84 - 0
netbox/ipam/querysets.py

@@ -1,3 +1,6 @@
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
+
 from utilities.querysets import RestrictedQuerySet
 
 
@@ -20,3 +23,84 @@ class PrefixQuerySet(RestrictedQuerySet):
                             'AND COALESCE(U1."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
             }
         )
+
+
+class VLANQuerySet(RestrictedQuerySet):
+
+    def get_for_device(self, device):
+        """
+        Return all VLANs available to the specified Device.
+        """
+        from .models import VLANGroup
+
+        # Find all relevant VLANGroups
+        q = Q()
+        if device.site.region:
+            q |= Q(
+                scope_type=ContentType.objects.get_by_natural_key('dcim', 'region'),
+                scope_id__in=device.site.region.get_ancestors(include_self=True)
+            )
+        if device.site.group:
+            q |= Q(
+                scope_type=ContentType.objects.get_by_natural_key('dcim', 'sitegroup'),
+                scope_id__in=device.site.group.get_ancestors(include_self=True)
+            )
+        q |= Q(
+            scope_type=ContentType.objects.get_by_natural_key('dcim', 'site'),
+            scope_id=device.site_id
+        )
+        if device.location:
+            q |= Q(
+                scope_type=ContentType.objects.get_by_natural_key('dcim', 'location'),
+                scope_id__in=device.location.get_ancestors(include_self=True)
+            )
+        if device.rack:
+            q |= Q(
+                scope_type=ContentType.objects.get_by_natural_key('dcim', 'rack'),
+                scope_id=device.rack_id
+            )
+
+        return self.filter(
+            Q(group__in=VLANGroup.objects.filter(q)) |
+            Q(site=device.site) |
+            Q(group__isnull=True, site__isnull=True)  # Global VLANs
+        )
+
+    def get_for_virtualmachine(self, vm):
+        """
+        Return all VLANs available to the specified VirtualMachine.
+        """
+        from .models import VLANGroup
+
+        # Find all relevant VLANGroups
+        q = Q()
+        if vm.cluster.site:
+            if vm.cluster.site.region:
+                q |= Q(
+                    scope_type=ContentType.objects.get_by_natural_key('dcim', 'region'),
+                    scope_id__in=vm.cluster.site.region.get_ancestors(include_self=True)
+                )
+            if vm.cluster.site.group:
+                q |= Q(
+                    scope_type=ContentType.objects.get_by_natural_key('dcim', 'sitegroup'),
+                    scope_id__in=vm.cluster.site.group.get_ancestors(include_self=True)
+                )
+            q |= Q(
+                scope_type=ContentType.objects.get_by_natural_key('dcim', 'site'),
+                scope_id=vm.cluster.site_id
+            )
+        if vm.cluster.group:
+            q |= Q(
+                scope_type=ContentType.objects.get_by_natural_key('virtualization', 'clustergroup'),
+                scope_id=vm.cluster.group_id
+            )
+        q |= Q(
+            scope_type=ContentType.objects.get_by_natural_key('virtualization', 'cluster'),
+            scope_id=vm.cluster_id
+        )
+
+        return self.filter(
+            Q(group__in=VLANGroup.objects.filter(q)) |
+            Q(site=vm.cluster.site) |
+            Q(group__isnull=True, site__isnull=True)  # Global VLANs
+        )

+ 18 - 57
netbox/virtualization/forms.py

@@ -606,20 +606,12 @@ class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm)
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         required=False,
-        label='Untagged VLAN',
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        label='Untagged VLAN'
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         required=False,
-        label='Tagged VLANs',
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        label='Tagged VLANs'
     )
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -646,15 +638,10 @@ class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm)
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        virtual_machine = VirtualMachine.objects.get(
-            pk=self.initial.get('virtual_machine') or self.data.get('virtual_machine')
-        )
-
-        # Add current site to VLANs query params
-        site = virtual_machine.site
-        if site:
-            self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
-            self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
+        # Limit VLAN choices by virtual machine
+        vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
+        self.fields['untagged_vlan'].widget.add_query_param('available_on_vm', vm_id)
+        self.fields['tagged_vlans'].widget.add_query_param('available_on_vm', vm_id)
 
 
 class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm):
@@ -689,19 +676,11 @@ class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm):
     )
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
@@ -711,15 +690,10 @@ class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        virtual_machine = VirtualMachine.objects.get(
-            pk=self.initial.get('virtual_machine') or self.data.get('virtual_machine')
-        )
-
-        # Add current site to VLANs query params
-        site = virtual_machine.site
-        if site:
-            self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
-            self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
+        # Limit VLAN choices by virtual machine
+        vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
+        self.fields['untagged_vlan'].widget.add_query_param('available_on_vm', vm_id)
+        self.fields['tagged_vlans'].widget.add_query_param('available_on_vm', vm_id)
 
 
 class VMInterfaceCSVForm(CSVModelForm):
@@ -777,19 +751,11 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
     )
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
-        required=False,
-        brief_mode=False,
-        query_params={
-            'site_id': 'null',
-        }
+        required=False
     )
 
     class Meta:
@@ -800,15 +766,10 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        # Limit available VLANs based on the parent VirtualMachine
-        if 'virtual_machine' in self.initial:
-            parent_obj = VirtualMachine.objects.filter(pk=self.initial['virtual_machine']).first()
-
-            site = getattr(parent_obj.cluster, 'site', None)
-            if site is not None:
-                # Add current site to VLANs query params
-                self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
-                self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
+        # Limit VLAN choices by virtual machine
+        vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine')
+        self.fields['untagged_vlan'].widget.add_query_param('available_on_vm', vm_id)
+        self.fields['tagged_vlans'].widget.add_query_param('available_on_vm', vm_id)
 
 
 class VMInterfaceBulkRenameForm(BulkRenameForm):