Skip to content

Commit 5a64cb7

Browse files
jnovingerjeremystretch
authored andcommitted
Fixes #21064: Ensures that extra choices preserve nested colons
1 parent 4d90d55 commit 5a64cb7

File tree

2 files changed

+33
-7
lines changed

2 files changed

+33
-7
lines changed

netbox/extras/forms/model_forms.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,22 +189,22 @@ def __init__(self, *args, initial=None, **kwargs):
189189
# if standardize these, we can simplify this code
190190

191191
# Convert extra_choices Array Field from model to CharField for form
192-
if 'extra_choices' in self.initial and self.initial['extra_choices']:
193-
extra_choices = self.initial['extra_choices']
192+
if extra_choices := self.initial.get('extra_choices', None):
194193
if isinstance(extra_choices, str):
195194
extra_choices = [extra_choices]
196-
choices = ""
195+
choices = []
197196
for choice in extra_choices:
198197
# Setup choices in Add Another use case
199198
if isinstance(choice, str):
200199
choice_str = ":".join(choice.replace("'", "").replace(" ", "")[1:-1].split(","))
201-
choices += choice_str + "\n"
200+
choices.append(choice_str)
202201
# Setup choices in Edit use case
203202
elif isinstance(choice, list):
204-
choice_str = ":".join(choice)
205-
choices += choice_str + "\n"
203+
value = choice[0].replace(':', '\\:')
204+
label = choice[1].replace(':', '\\:')
205+
choices.append(f'{value}:{label}')
206206

207-
self.initial['extra_choices'] = choices
207+
self.initial['extra_choices'] = '\n'.join(choices)
208208

209209
def clean_extra_choices(self):
210210
data = []

netbox/extras/tests/test_forms.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from dcim.models import Site
66
from extras.choices import CustomFieldTypeChoices
77
from extras.forms import SavedFilterForm
8+
from extras.forms.model_forms import CustomFieldChoiceSetForm
89
from extras.models import CustomField, CustomFieldChoiceSet
910

1011

@@ -90,6 +91,31 @@ def test_empty_values(self):
9091
self.assertIsNone(instance.custom_field_data[field_type])
9192

9293

94+
class CustomFieldChoiceSetFormTest(TestCase):
95+
96+
def test_escaped_colons_preserved_on_edit(self):
97+
choice_set = CustomFieldChoiceSet.objects.create(
98+
name='Test Choice Set',
99+
extra_choices=[['foo:bar', 'label'], ['value', 'label:with:colons']]
100+
)
101+
102+
form = CustomFieldChoiceSetForm(instance=choice_set)
103+
initial_choices = form.initial['extra_choices']
104+
105+
# colons are re-escaped
106+
self.assertEqual(initial_choices, 'foo\\:bar:label\nvalue:label\\:with\\:colons')
107+
108+
form = CustomFieldChoiceSetForm(
109+
{'name': choice_set.name, 'extra_choices': initial_choices},
110+
instance=choice_set
111+
)
112+
self.assertTrue(form.is_valid())
113+
updated = form.save()
114+
115+
# cleaned extra choices are correct, which does actually mean a list of tuples
116+
self.assertEqual(updated.extra_choices, [('foo:bar', 'label'), ('value', 'label:with:colons')])
117+
118+
93119
class SavedFilterFormTest(TestCase):
94120

95121
def test_basic_submit(self):

0 commit comments

Comments
 (0)