Explorar el Código

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.
bctiemann hace 1 mes
padre
commit
352860daf0

+ 2 - 2
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)

+ 9 - 0
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