From 52a2b934a0c278a8e185b880f91868e45b4fd227 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Fri, 16 Jan 2026 14:34:12 -0800 Subject: [PATCH] Fixes #21160: Fix performance issue rendering FilterSet forms w/ large choicesets (#21200) --- netbox/utilities/forms/widgets/modifiers.py | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/netbox/utilities/forms/widgets/modifiers.py b/netbox/utilities/forms/widgets/modifiers.py index 29c36fa3d..26e60dec2 100644 --- a/netbox/utilities/forms/widgets/modifiers.py +++ b/netbox/utilities/forms/widgets/modifiers.py @@ -1,6 +1,8 @@ from django import forms from django.utils.translation import gettext_lazy as _ +from utilities.forms.widgets.apiselect import APISelect, APISelectMultiple + __all__ = ( 'FilterModifierWidget', 'MODIFIER_EMPTY_FALSE', @@ -94,9 +96,37 @@ class FilterModifierWidget(forms.Widget): # to the original widget before rendering self.original_widget.attrs.update(self.attrs) + # For APISelect/APISelectMultiple widgets, temporarily clear choices to prevent queryset evaluation + original_choices = None + if isinstance(self.original_widget, (APISelect, APISelectMultiple)): + original_choices = self.original_widget.choices + + # Only keep selected choices to preserve current selection in HTML + if value: + values = value if isinstance(value, (list, tuple)) else [value] + + if hasattr(original_choices, 'queryset'): + queryset = original_choices.queryset + selected_objects = queryset.filter(pk__in=values) + # Build minimal choice list with just the selected values + self.original_widget.choices = [ + (obj.pk, str(obj)) for obj in selected_objects + ] + else: + self.original_widget.choices = [ + choice for choice in original_choices if choice[0] in values + ] + else: + # No selection - render empty select element + self.original_widget.choices = [] + # Get context from the original widget original_context = self.original_widget.get_context(name, value, attrs) + # Restore original choices if we modified them + if original_choices is not None: + self.original_widget.choices = original_choices + # Build our wrapper context context = super().get_context(name, value, attrs) context['widget']['original_widget'] = original_context['widget']