From 352860daf0c3939559c6038c513d6f9998eaa6be Mon Sep 17 00:00:00 2001 From: bctiemann Date: Thu, 28 May 2026 15:47:55 -0400 Subject: [PATCH] Fixes #22325: AttributeError when creating choice set with base choices (#22326) CHOICE_SETS values (IATA, ISO_3166, UN_LOCODE) are lists of (value, label) tuples, not dicts. The .values() call introduced by #21984 treated them as dicts, raising AttributeError: 'list' object has no attribute 'values' when full_clean() was invoked during choice set creation. Replace with a generator expression that extracts the first element from each tuple, matching the same pattern used elsewhere in the same model. Also covers the save() path when order_alphabetically=True but extra_choices is None (base-only choice set), preventing a TypeError when sorted() receives None. --- netbox/extras/models/customfields.py | 4 ++-- netbox/extras/tests/test_customfields.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index e7e8d2b84..011afa42d 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -972,7 +972,7 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, OwnerMixin, Chang extra_choice_values = set() if self.base_choices: - valid_choice_values.update(CHOICE_SETS.get(self.base_choices).values()) + valid_choice_values.update(value for value, _ in CHOICE_SETS.get(self.base_choices)) if self.extra_choices: for value, _label in self.extra_choices: @@ -1031,7 +1031,7 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, OwnerMixin, Chang def save(self, *args, **kwargs): # Sort choices if alphabetical ordering is enforced - if self.order_alphabetically: + if self.order_alphabetically and self.extra_choices: self.extra_choices = sorted(self.extra_choices, key=lambda x: x[0]) return super().save(*args, **kwargs) diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 8c695d382..c7c633991 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -466,6 +466,15 @@ class CustomFieldTestCase(TestCase): self.assertIn('choice_colors', cm.exception.message_dict) + @tag('regression') + def test_choice_set_with_base_choices_validates_without_error(self): + """Regression test for #22325: base-only choice sets must validate.""" + for base in ('IATA', 'ISO_3166', 'UN_LOCODE'): + with self.subTest(base=base): + choice_set = CustomFieldChoiceSet(name=f'Test {base}', base_choices=base, order_alphabetically=True) + choice_set.full_clean() # must not raise + choice_set.save() # must not raise (extra_choices is None) + def test_remove_selected_choice(self): """ Removing a ChoiceSet choice that is referenced by an object should raise