From bc75706b24685f4ea91308bae3049e2ec719a738 Mon Sep 17 00:00:00 2001 From: bctiemann Date: Fri, 29 May 2026 09:23:01 -0400 Subject: [PATCH] Fixes #22328: Add missing else branch to DynamicMultipleChoiceField.get_bound_field() (#22329) --- netbox/utilities/forms/fields/dynamic.py | 2 + netbox/utilities/tests/test_forms.py | 58 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/netbox/utilities/forms/fields/dynamic.py b/netbox/utilities/forms/fields/dynamic.py index 83b30ab48..17b5daccf 100644 --- a/netbox/utilities/forms/fields/dynamic.py +++ b/netbox/utilities/forms/fields/dynamic.py @@ -44,6 +44,8 @@ class DynamicMultipleChoiceField(forms.MultipleChoiceField): self.choices = [ choice for choice in self.choices if choice[0] and choice[0] in data ] + else: + self.choices = [] return bound_field diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py index 69e911191..791012200 100644 --- a/netbox/utilities/tests/test_forms.py +++ b/netbox/utilities/tests/test_forms.py @@ -5,6 +5,7 @@ from dcim.models import Site from netbox.choices import ImportFormatChoices from utilities.forms.bulk_import import BulkImportForm from utilities.forms.fields.csv import CSVSelectWidget +from utilities.forms.fields.dynamic import DynamicChoiceField, DynamicMultipleChoiceField from utilities.forms.forms import BulkRenameForm from utilities.forms.utils import ( expand_alphanumeric_pattern, @@ -557,6 +558,63 @@ class SelectMultipleWidgetTestCase(TestCase): self.assertEqual(widget.choices[1][1], [(3, 'Option 3')]) +class DynamicChoiceFieldTestCase(TestCase): + """ + Validate that DynamicChoiceField.get_bound_field() limits choices to the current + selection and clears them when nothing is selected. + """ + CHOICES = [('a', 'Option A'), ('b', 'Option B'), ('c', 'Option C')] + + def _make_form(self, data=None): + class TestForm(forms.Form): + field = DynamicChoiceField(choices=self.CHOICES, required=False) + return TestForm(data=data) + + def test_unbound_clears_choices(self): + form = self._make_form() + form.fields['field'].get_bound_field(form, 'field') + self.assertEqual(form.fields['field'].choices, []) + + def test_bound_with_value_filters_to_selection(self): + form = self._make_form(data={'field': 'b'}) + form.fields['field'].get_bound_field(form, 'field') + self.assertEqual(form.fields['field'].choices, [('b', 'Option B')]) + + def test_bound_with_no_value_clears_choices(self): + form = self._make_form(data={}) + form.fields['field'].get_bound_field(form, 'field') + self.assertEqual(form.fields['field'].choices, []) + + +class DynamicMultipleChoiceFieldTestCase(TestCase): + """ + Validate that DynamicMultipleChoiceField.get_bound_field() limits choices to + the current selection and clears them when nothing is selected. + """ + CHOICES = [('a', 'Option A'), ('b', 'Option B'), ('c', 'Option C')] + + def _make_form(self, data=None): + class TestForm(forms.Form): + field = DynamicMultipleChoiceField(choices=self.CHOICES, required=False) + return TestForm(data=data) + + def test_unbound_clears_choices(self): + """Regression test for #22328: unbound form must not retain the full choices list.""" + form = self._make_form() + form.fields['field'].get_bound_field(form, 'field') + self.assertEqual(form.fields['field'].choices, []) + + def test_bound_with_values_filters_to_selection(self): + form = self._make_form(data={'field': ['a', 'c']}) + form.fields['field'].get_bound_field(form, 'field') + self.assertEqual(form.fields['field'].choices, [('a', 'Option A'), ('c', 'Option C')]) + + def test_bound_with_no_values_clears_choices(self): + form = self._make_form(data={}) + form.fields['field'].get_bound_field(form, 'field') + self.assertEqual(form.fields['field'].choices, []) + + class GetCapacityUnitLabelTestCase(TestCase): """ Test the get_capacity_unit_label function for correct base unit label.