فهرست منبع

Introduce NonUniqueModelMultipleChoiceFilter to apply distinct-value matching consistently

Move the DISTINCT ON queryset into a ModelMultipleChoiceFilter subclass which derives it from the filter's to_field_name via get_queryset(), and convert the 25 affected filter declarations to use it.
Jeremy Stretch 5 روز پیش
والد
کامیت
b5003d9697
6فایلهای تغییر یافته به همراه75 افزوده شده و 72 حذف شده
  1. 27 37
      netbox/dcim/filtersets.py
  2. 3 3
      netbox/extras/filtersets.py
  3. 9 11
      netbox/ipam/filtersets.py
  4. 10 0
      netbox/utilities/filters.py
  5. 8 4
      netbox/virtualization/filtersets.py
  6. 18 17
      netbox/vpn/filtersets.py

+ 27 - 37
netbox/dcim/filtersets.py

@@ -33,6 +33,7 @@ from utilities.filters import (
     MultiValueMACAddressFilter,
     MultiValueTimeZoneFilter,
     MultiValueWWNFilter,
+    NonUniqueModelMultipleChoiceFilter,
     NumericArrayFilter,
     TreeNodeMultipleChoiceFilter,
 )
@@ -286,10 +287,9 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, NestedGroupMode
         distinct=False,
         label=_('Parent location (ID)'),
     )
-    # Location slugs aren't unique, so match on distinct slugs to avoid redundant predicates
-    parent = django_filters.ModelMultipleChoiceFilter(
+    parent = NonUniqueModelMultipleChoiceFilter(
         field_name='parent__slug',
-        queryset=Location.objects.order_by('slug').distinct('slug'),
+        queryset=Location.objects.all(),
         distinct=False,
         to_field_name='slug',
         label=_('Parent location (slug)'),
@@ -1847,10 +1847,9 @@ class ModuleFilterSet(PrimaryModelFilterSet):
         distinct=False,
         label=_('Location (ID)'),
     )
-    # Location slugs aren't unique, so match on distinct slugs to avoid redundant predicates
-    location = django_filters.ModelMultipleChoiceFilter(
+    location = NonUniqueModelMultipleChoiceFilter(
         field_name='device__location__slug',
-        queryset=Location.objects.order_by('slug').distinct('slug'),
+        queryset=Location.objects.all(),
         distinct=False,
         to_field_name='slug',
         label=_('Location (slug)'),
@@ -1861,10 +1860,9 @@ class ModuleFilterSet(PrimaryModelFilterSet):
         distinct=False,
         label=_('Rack (ID)'),
     )
-    # Rack names aren't unique, so match on distinct names to avoid redundant predicates
-    rack = django_filters.ModelMultipleChoiceFilter(
+    rack = NonUniqueModelMultipleChoiceFilter(
         field_name='device__rack__name',
-        queryset=Rack.objects.order_by('name').distinct('name'),
+        queryset=Rack.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Rack (name)'),
@@ -1874,10 +1872,9 @@ class ModuleFilterSet(PrimaryModelFilterSet):
         distinct=False,
         label=_('Device (ID)'),
     )
-    # Device names aren't unique, so match on distinct names to avoid redundant predicates
-    device = django_filters.ModelMultipleChoiceFilter(
+    device = NonUniqueModelMultipleChoiceFilter(
         field_name='device__name',
-        queryset=Device.objects.order_by('name').distinct('name'),
+        queryset=Device.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Device (name)'),
@@ -1957,10 +1954,9 @@ class DeviceComponentFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
         distinct=False,
         label=_('Location (ID)'),
     )
-    # Location slugs aren't unique, so match on distinct slugs to avoid redundant predicates
-    location = django_filters.ModelMultipleChoiceFilter(
+    location = NonUniqueModelMultipleChoiceFilter(
         field_name='_location__slug',
-        queryset=Location.objects.order_by('slug').distinct('slug'),
+        queryset=Location.objects.all(),
         distinct=False,
         to_field_name='slug',
         label=_('Location (slug)'),
@@ -1971,10 +1967,9 @@ class DeviceComponentFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
         distinct=False,
         label=_('Rack (ID)'),
     )
-    # Rack names aren't unique, so match on distinct names to avoid redundant predicates
-    rack = django_filters.ModelMultipleChoiceFilter(
+    rack = NonUniqueModelMultipleChoiceFilter(
         field_name='_rack__name',
-        queryset=Rack.objects.order_by('name').distinct('name'),
+        queryset=Rack.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Rack (name)'),
@@ -1984,10 +1979,9 @@ class DeviceComponentFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
         distinct=False,
         label=_('Device (ID)'),
     )
-    # Device names aren't unique, so match on distinct names to avoid redundant predicates
-    device = django_filters.ModelMultipleChoiceFilter(
+    device = NonUniqueModelMultipleChoiceFilter(
         field_name='device__name',
-        queryset=Device.objects.order_by('name').distinct('name'),
+        queryset=Device.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Device (name)'),
@@ -2262,10 +2256,9 @@ class MACAddressFilterSet(PrimaryModelFilterSet):
         field_name='pk',
         label=_('Virtual machine (ID)'),
     )
-    # Interface names aren't unique, so match on distinct names to avoid redundant predicates
-    interface = django_filters.ModelMultipleChoiceFilter(
+    interface = NonUniqueModelMultipleChoiceFilter(
         field_name='interface__name',
-        queryset=Interface.objects.order_by('name').distinct('name'),
+        queryset=Interface.objects.all(),
         to_field_name='name',
         label=_('Interface (name)'),
     )
@@ -2274,9 +2267,9 @@ class MACAddressFilterSet(PrimaryModelFilterSet):
         queryset=Interface.objects.all(),
         label=_('Interface (ID)'),
     )
-    vminterface = django_filters.ModelMultipleChoiceFilter(
+    vminterface = NonUniqueModelMultipleChoiceFilter(
         field_name='vminterface__name',
-        queryset=VMInterface.objects.order_by('name').distinct('name'),
+        queryset=VMInterface.objects.all(),
         to_field_name='name',
         label=_('VM interface (name)'),
     )
@@ -2524,16 +2517,15 @@ class InterfaceFilterSet(
         queryset=VirtualDeviceContext.objects.all(),
         label=_('Virtual Device Context')
     )
-    # VDC names and identifiers aren't unique, so match on distinct values to avoid redundant predicates
-    vdc_identifier = django_filters.ModelMultipleChoiceFilter(
+    vdc_identifier = NonUniqueModelMultipleChoiceFilter(
         field_name='vdcs__identifier',
-        queryset=VirtualDeviceContext.objects.order_by('identifier').distinct('identifier'),
+        queryset=VirtualDeviceContext.objects.all(),
         to_field_name='identifier',
         label=_('Virtual Device Context (Identifier)')
     )
-    vdc = django_filters.ModelMultipleChoiceFilter(
+    vdc = NonUniqueModelMultipleChoiceFilter(
         field_name='vdcs__name',
-        queryset=VirtualDeviceContext.objects.order_by('name').distinct('name'),
+        queryset=VirtualDeviceContext.objects.all(),
         to_field_name='name',
         label=_('Virtual Device Context')
     )
@@ -2683,10 +2675,9 @@ class DeviceBayFilterSet(DeviceComponentFilterSet):
         distinct=False,
         label=_('Installed device (ID)'),
     )
-    # Device names aren't unique, so match on distinct names to avoid redundant predicates
-    installed_device = django_filters.ModelMultipleChoiceFilter(
+    installed_device = NonUniqueModelMultipleChoiceFilter(
         field_name='installed_device__name',
-        queryset=Device.objects.order_by('name').distinct('name'),
+        queryset=Device.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Installed device (name)'),
@@ -2771,10 +2762,9 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
         distinct=False,
         label=_('Master (ID)'),
     )
-    # Device names aren't unique, so match on distinct names to avoid redundant predicates
-    master = django_filters.ModelMultipleChoiceFilter(
+    master = NonUniqueModelMultipleChoiceFilter(
         field_name='master__name',
-        queryset=Device.objects.order_by('name').distinct('name'),
+        queryset=Device.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Master (name)'),

+ 3 - 3
netbox/extras/filtersets.py

@@ -15,6 +15,7 @@ from utilities.filters import (
     MultiValueCharFilter,
     MultiValueContentTypeFilter,
     MultiValueNumberFilter,
+    NonUniqueModelMultipleChoiceFilter,
 )
 from utilities.filtersets import register_filterset
 from virtualization.models import Cluster, ClusterGroup, ClusterType
@@ -760,10 +761,9 @@ class ConfigContextFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
         queryset=Location.objects.all(),
         label=_('Location'),
     )
-    # Location slugs aren't unique, so match on distinct slugs to avoid redundant predicates
-    location = django_filters.ModelMultipleChoiceFilter(
+    location = NonUniqueModelMultipleChoiceFilter(
         field_name='locations__slug',
-        queryset=Location.objects.order_by('slug').distinct('slug'),
+        queryset=Location.objects.all(),
         to_field_name='slug',
         label=_('Location (slug)'),
     )

+ 9 - 11
netbox/ipam/filtersets.py

@@ -22,6 +22,7 @@ from utilities.filters import (
     MultiValueCharFilter,
     MultiValueContentTypeFilter,
     MultiValueNumberFilter,
+    NonUniqueModelMultipleChoiceFilter,
     TreeNodeMultipleChoiceFilter,
 )
 from utilities.filtersets import register_filterset
@@ -397,10 +398,9 @@ class PrefixFilterSet(PrimaryModelFilterSet, ScopedFilterSet, TenancyFilterSet,
         to_field_name='id',
         label=_('VLAN Group (ID)'),
     )
-    # VLAN group slugs aren't unique, so match on distinct slugs to avoid redundant predicates
-    vlan_group = django_filters.ModelMultipleChoiceFilter(
+    vlan_group = NonUniqueModelMultipleChoiceFilter(
         field_name='vlan__group__slug',
-        queryset=VLANGroup.objects.order_by('slug').distinct('slug'),
+        queryset=VLANGroup.objects.all(),
         distinct=False,
         to_field_name='slug',
         label=_('VLAN Group (slug)'),
@@ -676,10 +676,9 @@ class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFi
         field_name='pk',
         label=_('Virtual machine (ID)'),
     )
-    # Interface names aren't unique, so match on distinct names to avoid redundant predicates
-    interface = django_filters.ModelMultipleChoiceFilter(
+    interface = NonUniqueModelMultipleChoiceFilter(
         field_name='interface__name',
-        queryset=Interface.objects.order_by('name').distinct('name'),
+        queryset=Interface.objects.all(),
         to_field_name='name',
         label=_('Interface (name)'),
     )
@@ -688,9 +687,9 @@ class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFi
         queryset=Interface.objects.all(),
         label=_('Interface (ID)'),
     )
-    vminterface = django_filters.ModelMultipleChoiceFilter(
+    vminterface = NonUniqueModelMultipleChoiceFilter(
         field_name='vminterface__name',
-        queryset=VMInterface.objects.order_by('name').distinct('name'),
+        queryset=VMInterface.objects.all(),
         to_field_name='name',
         label=_('VM interface (name)'),
     )
@@ -1048,10 +1047,9 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
         distinct=False,
         label=_('Group (ID)'),
     )
-    # VLAN group slugs aren't unique, so match on distinct slugs to avoid redundant predicates
-    group = django_filters.ModelMultipleChoiceFilter(
+    group = NonUniqueModelMultipleChoiceFilter(
         field_name='group__slug',
-        queryset=VLANGroup.objects.order_by('slug').distinct('slug'),
+        queryset=VLANGroup.objects.all(),
         distinct=False,
         to_field_name='slug',
         label=_('Group'),

+ 10 - 0
netbox/utilities/filters.py

@@ -25,6 +25,7 @@ __all__ = (
     'MultiValueTimeFilter',
     'MultiValueTimeZoneFilter',
     'MultiValueWWNFilter',
+    'NonUniqueModelMultipleChoiceFilter',
     'NullableCharFieldFilter',
     'NumericArrayFilter',
     'TreeNodeMultipleChoiceFilter',
@@ -177,6 +178,15 @@ class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
         return super().filter(qs, value)
 
 
+class NonUniqueModelMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
+    """
+    A ModelMultipleChoiceFilter for a non-unique to_field_name, which resolves each value to a single object.
+    """
+    def get_queryset(self, request):
+        to_field_name = self.extra['to_field_name']
+        return super().get_queryset(request).order_by(to_field_name).distinct(to_field_name)
+
+
 class NullableCharFieldFilter(django_filters.CharFilter):
     """
     Allow matching on null field values by passing a special string used to signify NULL.

+ 8 - 4
netbox/virtualization/filtersets.py

@@ -13,7 +13,12 @@ from ipam.filtersets import PrimaryIPFilterSet
 from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
 from users.filterset_mixins import OwnerFilterMixin
-from utilities.filters import MultiValueCharFilter, MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
+from utilities.filters import (
+    MultiValueCharFilter,
+    MultiValueMACAddressFilter,
+    NonUniqueModelMultipleChoiceFilter,
+    TreeNodeMultipleChoiceFilter,
+)
 from utilities.filtersets import register_filterset
 
 from .choices import *
@@ -204,10 +209,9 @@ class VirtualMachineFilterSet(
         distinct=False,
         label=_('Device (ID)'),
     )
-    # Device names aren't unique, so match on distinct names to avoid redundant predicates
-    device = django_filters.ModelMultipleChoiceFilter(
+    device = NonUniqueModelMultipleChoiceFilter(
         field_name='device__name',
-        queryset=Device.objects.order_by('name').distinct('name'),
+        queryset=Device.objects.all(),
         distinct=False,
         to_field_name='name',
         label=_('Device'),

+ 18 - 17
netbox/vpn/filtersets.py

@@ -7,7 +7,12 @@ from dcim.models import Device, Interface
 from ipam.models import VLAN, IPAddress, RouteTarget
 from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
 from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
-from utilities.filters import MultiValueBigNumberFilter, MultiValueCharFilter, MultiValueContentTypeFilter
+from utilities.filters import (
+    MultiValueBigNumberFilter,
+    MultiValueCharFilter,
+    MultiValueContentTypeFilter,
+    NonUniqueModelMultipleChoiceFilter,
+)
 from utilities.filtersets import register_filterset
 from virtualization.models import VirtualMachine, VMInterface
 
@@ -105,10 +110,9 @@ class TunnelTerminationFilterSet(NetBoxModelFilterSet):
         distinct=False,
     )
     termination_type = MultiValueContentTypeFilter()
-    # Interface names aren't unique, so match on distinct names to avoid redundant predicates
-    interface = django_filters.ModelMultipleChoiceFilter(
+    interface = NonUniqueModelMultipleChoiceFilter(
         field_name='interface__name',
-        queryset=Interface.objects.order_by('name').distinct('name'),
+        queryset=Interface.objects.all(),
         to_field_name='name',
         label=_('Interface (name)'),
     )
@@ -117,9 +121,9 @@ class TunnelTerminationFilterSet(NetBoxModelFilterSet):
         queryset=Interface.objects.all(),
         label=_('Interface (ID)'),
     )
-    vminterface = django_filters.ModelMultipleChoiceFilter(
+    vminterface = NonUniqueModelMultipleChoiceFilter(
         field_name='vminterface__name',
-        queryset=VMInterface.objects.order_by('name').distinct('name'),
+        queryset=VMInterface.objects.all(),
         to_field_name='name',
         label=_('VM interface (name)'),
     )
@@ -417,10 +421,9 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
         field_name='pk',
         label=_('Site (ID)'),
     )
-    # Device names aren't unique, so match on distinct names to avoid redundant predicates
-    device = django_filters.ModelMultipleChoiceFilter(
+    device = NonUniqueModelMultipleChoiceFilter(
         field_name='interface__device__name',
-        queryset=Device.objects.order_by('name').distinct('name'),
+        queryset=Device.objects.all(),
         to_field_name='name',
         label=_('Device (name)'),
     )
@@ -440,10 +443,9 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
         queryset=VirtualMachine.objects.all(),
         label=_('Virtual machine (ID)'),
     )
-    # Interface names aren't unique, so match on distinct names to avoid redundant predicates
-    interface = django_filters.ModelMultipleChoiceFilter(
+    interface = NonUniqueModelMultipleChoiceFilter(
         field_name='interface__name',
-        queryset=Interface.objects.order_by('name').distinct('name'),
+        queryset=Interface.objects.all(),
         to_field_name='name',
         label=_('Interface (name)'),
     )
@@ -452,9 +454,9 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
         queryset=Interface.objects.all(),
         label=_('Interface (ID)'),
     )
-    vminterface = django_filters.ModelMultipleChoiceFilter(
+    vminterface = NonUniqueModelMultipleChoiceFilter(
         field_name='vminterface__name',
-        queryset=VMInterface.objects.order_by('name').distinct('name'),
+        queryset=VMInterface.objects.all(),
         to_field_name='name',
         label=_('VM interface (name)'),
     )
@@ -463,10 +465,9 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
         queryset=VMInterface.objects.all(),
         label=_('VM Interface (ID)'),
     )
-    # VLAN names aren't unique, so match on distinct names to avoid redundant predicates
-    vlan = django_filters.ModelMultipleChoiceFilter(
+    vlan = NonUniqueModelMultipleChoiceFilter(
         field_name='vlan__name',
-        queryset=VLAN.objects.order_by('name').distinct('name'),
+        queryset=VLAN.objects.all(),
         to_field_name='name',
         label=_('VLAN (name)'),
     )