Fixes #21160: Fix performance issue rendering FilterSet forms w/ large choicesets (#21200)
Some checks failed
CI / build (20.x, 3.12) (push) Waiting to run
CI / build (20.x, 3.13) (push) Waiting to run
CI / build (20.x, 3.14) (push) Waiting to run
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
Arthur Hanson
2026-01-16 14:34:12 -08:00
committed by GitHub
parent 3e2a26984f
commit 52a2b934a0

View File

@@ -1,6 +1,8 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from utilities.forms.widgets.apiselect import APISelect, APISelectMultiple
__all__ = ( __all__ = (
'FilterModifierWidget', 'FilterModifierWidget',
'MODIFIER_EMPTY_FALSE', 'MODIFIER_EMPTY_FALSE',
@@ -94,9 +96,37 @@ class FilterModifierWidget(forms.Widget):
# to the original widget before rendering # to the original widget before rendering
self.original_widget.attrs.update(self.attrs) 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 # Get context from the original widget
original_context = self.original_widget.get_context(name, value, attrs) 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 # Build our wrapper context
context = super().get_context(name, value, attrs) context = super().get_context(name, value, attrs)
context['widget']['original_widget'] = original_context['widget'] context['widget']['original_widget'] = original_context['widget']