diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index fdb9228f1..5c304d84f 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -723,6 +723,15 @@ def prepare_script_form(script_instance, data, files=None): """ data = data.copy() if data is not None else {} for name, var in script_instance._get_vars().items(): - if name not in data and (initial := var.field_attrs.get('initial')) is not None: + if name in data: + continue + if (initial := var.field_attrs.get('initial')) is None: + continue + if isinstance(initial, (list, tuple)) and hasattr(data, 'setlist'): + # Assigning a list to a QueryDict stores it as a single nested value, which a + # multi-select widget reads back as one bogus choice. Set the values individually + # so a MultiChoiceVar/MultiObjectVar default binds as it does for a plain dict. + data.setlist(name, list(initial)) + else: data[name] = initial return script_instance.as_form(data=data, files=files) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index e6e353c19..7a9468bec 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -14,7 +14,7 @@ from core.models import Job, ObjectType from dcim.models import DeviceType, Manufacturer, Site from extras.choices import * from extras.models import * -from extras.scripts import BooleanVar, IntegerVar, StringVar +from extras.scripts import BooleanVar, IntegerVar, MultiChoiceVar, StringVar from extras.scripts import Script as PythonClass from users.models import Group, ObjectPermission, User from utilities.testing import TestCase, ViewTestCases @@ -1294,6 +1294,7 @@ class ScriptDefaultBackfillTestCase(TestCase): label = StringVar(default='hello') flag = BooleanVar(default=True) + picks = MultiChoiceVar(choices=(('a', 'A'), ('b', 'B'), ('c', 'C')), default=['a', 'b']) def run(self, data, commit): return 'Complete' @@ -1328,6 +1329,10 @@ class ScriptDefaultBackfillTestCase(TestCase): data = mock_enqueue.call_args.kwargs['data'] self.assertEqual(data['label'], 'hello') self.assertIs(data['flag'], True) + # A multi-value default must be set on the QueryDict with setlist(): a plain + # assignment stores the list as one nested value, which the multi-select widget + # then rejects as a single invalid choice. + self.assertEqual(data['picks'], ['a', 'b']) class ScriptValidationErrorTestCase(TestCase):