Fixes #21064: Ensures that extra choices preserve nested colons

This commit is contained in:
Jason Novinger
2026-01-05 14:42:20 -06:00
committed by Jeremy Stretch
parent 4d90d559be
commit 5a64cb712d
2 changed files with 33 additions and 7 deletions

View File

@@ -189,22 +189,22 @@ class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm):
# if standardize these, we can simplify this code # if standardize these, we can simplify this code
# Convert extra_choices Array Field from model to CharField for form # Convert extra_choices Array Field from model to CharField for form
if 'extra_choices' in self.initial and self.initial['extra_choices']: if extra_choices := self.initial.get('extra_choices', None):
extra_choices = self.initial['extra_choices']
if isinstance(extra_choices, str): if isinstance(extra_choices, str):
extra_choices = [extra_choices] extra_choices = [extra_choices]
choices = "" choices = []
for choice in extra_choices: for choice in extra_choices:
# Setup choices in Add Another use case # Setup choices in Add Another use case
if isinstance(choice, str): if isinstance(choice, str):
choice_str = ":".join(choice.replace("'", "").replace(" ", "")[1:-1].split(",")) choice_str = ":".join(choice.replace("'", "").replace(" ", "")[1:-1].split(","))
choices += choice_str + "\n" choices.append(choice_str)
# Setup choices in Edit use case # Setup choices in Edit use case
elif isinstance(choice, list): elif isinstance(choice, list):
choice_str = ":".join(choice) value = choice[0].replace(':', '\\:')
choices += choice_str + "\n" 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): def clean_extra_choices(self):
data = [] data = []

View File

@@ -5,6 +5,7 @@ from dcim.forms import SiteForm
from dcim.models import Site from dcim.models import Site
from extras.choices import CustomFieldTypeChoices from extras.choices import CustomFieldTypeChoices
from extras.forms import SavedFilterForm from extras.forms import SavedFilterForm
from extras.forms.model_forms import CustomFieldChoiceSetForm
from extras.models import CustomField, CustomFieldChoiceSet from extras.models import CustomField, CustomFieldChoiceSet
@@ -90,6 +91,31 @@ class CustomFieldModelFormTest(TestCase):
self.assertIsNone(instance.custom_field_data[field_type]) 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): class SavedFilterFormTest(TestCase):
def test_basic_submit(self): def test_basic_submit(self):