diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 3e2c99a2e..17bce784a 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -189,22 +189,22 @@ class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm): # if standardize these, we can simplify this code # Convert extra_choices Array Field from model to CharField for form - if 'extra_choices' in self.initial and self.initial['extra_choices']: - extra_choices = self.initial['extra_choices'] + if extra_choices := self.initial.get('extra_choices', None): if isinstance(extra_choices, str): extra_choices = [extra_choices] - choices = "" + choices = [] for choice in extra_choices: # Setup choices in Add Another use case if isinstance(choice, str): choice_str = ":".join(choice.replace("'", "").replace(" ", "")[1:-1].split(",")) - choices += choice_str + "\n" + choices.append(choice_str) # Setup choices in Edit use case elif isinstance(choice, list): - choice_str = ":".join(choice) - choices += choice_str + "\n" + value = choice[0].replace(':', '\\:') + label = choice[1].replace(':', '\\:') + choices.append(f'{value}:{label}') - self.initial['extra_choices'] = choices + self.initial['extra_choices'] = '\n'.join(choices) def clean_extra_choices(self): data = [] diff --git a/netbox/extras/tests/test_forms.py b/netbox/extras/tests/test_forms.py index 66c4e245e..b9f65c5e4 100644 --- a/netbox/extras/tests/test_forms.py +++ b/netbox/extras/tests/test_forms.py @@ -5,6 +5,7 @@ from dcim.forms import SiteForm from dcim.models import Site from extras.choices import CustomFieldTypeChoices from extras.forms import SavedFilterForm +from extras.forms.model_forms import CustomFieldChoiceSetForm from extras.models import CustomField, CustomFieldChoiceSet @@ -90,6 +91,31 @@ class CustomFieldModelFormTest(TestCase): self.assertIsNone(instance.custom_field_data[field_type]) +class CustomFieldChoiceSetFormTest(TestCase): + + def test_escaped_colons_preserved_on_edit(self): + choice_set = CustomFieldChoiceSet.objects.create( + name='Test Choice Set', + extra_choices=[['foo:bar', 'label'], ['value', 'label:with:colons']] + ) + + form = CustomFieldChoiceSetForm(instance=choice_set) + initial_choices = form.initial['extra_choices'] + + # colons are re-escaped + self.assertEqual(initial_choices, 'foo\\:bar:label\nvalue:label\\:with\\:colons') + + form = CustomFieldChoiceSetForm( + {'name': choice_set.name, 'extra_choices': initial_choices}, + instance=choice_set + ) + self.assertTrue(form.is_valid()) + updated = form.save() + + # cleaned extra choices are correct, which does actually mean a list of tuples + self.assertEqual(updated.extra_choices, [('foo:bar', 'label'), ('value', 'label:with:colons')]) + + class SavedFilterFormTest(TestCase): def test_basic_submit(self):