Repository: nautobot/nautobot

Issue:
Dynamic filter form population can alter presentation of ChoiceFields elsewhere in the UI
### Environment
* Nautobot version (Docker tag too if applicable): 1.6.0rc1

### Steps to Reproduce
1. Log in to Nautobot
2. In one tab, open the `/dcim/devices/` list view, open the `Filter` modal, and switch to the `Advanced` tab.
3. In another browser tab, open the "run" view for a Job that defines a `ChoiceVar`. Observe that the ChoiceVar is correctly represented as a single-value Select2 widget in the UI.
4. In the first tab, in the Filter modal, select "Rack Face" in the first column, then "exact" in the second column.
5. Refresh the second tab.

<!-- What did you expect to happen? -->
### Expected Behavior

No change on page refresh

<!-- What happened instead? -->
### Observed Behavior

The `ChoiceVar` now gets incorrectly rendered as a multi-value Select2 widget.

This is due to incorrect code in `get_filterset_parameter_form_field()` which is inadvertently modifying the `forms.ChoiceField` class directly instead of only changing a single instance of that class.


Discussion:
Thank you to the many folks who helped identify, reproduce, and troubleshoot this issue, including @Dav-C, @jtdub, @joewesch. @matt852.

Resolving patch:
diff --git a/nautobot/utilities/utils.py b/nautobot/utilities/utils.py
--- a/nautobot/utilities/utils.py
+++ b/nautobot/utilities/utils.py
@@ -915,17 +915,9 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
                 choices_as_strings=True, queryset=queryset_map[plural_name]().as_queryset()
             )
     elif isinstance(field, (filters.MultipleChoiceFilter, filters.ChoiceFilter)) and "choices" in field.extra:
-        form_field_class = forms.ChoiceField
-        form_field_class.widget = StaticSelect2Multiple()
-        form_attr = {"choices": field.extra.get("choices")}
-
-        form_field = form_field_class(**form_attr)
+        form_field = forms.MultipleChoiceField(choices=field.extra.get("choices"), widget=StaticSelect2Multiple)
     elif isinstance(field, (BooleanFilter,)):  # Yes / No choice
-        form_field_class = forms.ChoiceField
-        form_field_class.widget = StaticSelect2()
-        form_attr = {"choices": BOOLEAN_CHOICES}
-
-        form_field = form_field_class(**form_attr)
+        form_field = forms.ChoiceField(choices=BOOLEAN_CHOICES, widget=StaticSelect2)
     elif isinstance(field, DateTimeFilter):
         form_field.widget = DateTimePicker()
     elif isinstance(field, DateFilter):


Test patch:
diff --git a/nautobot/utilities/tests/test_utils.py b/nautobot/utilities/tests/test_utils.py
--- a/nautobot/utilities/tests/test_utils.py
+++ b/nautobot/utilities/tests/test_utils.py
@@ -490,11 +490,15 @@ def test_get_filterset_parameter_form_field(self):
                 form_field = get_filterset_parameter_form_field(Site, field_name)
                 self.assertIsInstance(form_field, forms.ChoiceField)
 
-            device_fields = ["has_console_ports", "has_interfaces", "face"]
+            device_fields = ["has_console_ports", "has_interfaces"]
             for field_name in device_fields:
                 form_field = get_filterset_parameter_form_field(Device, field_name)
                 self.assertIsInstance(form_field, forms.ChoiceField)
 
+        with self.subTest("Test MultipleChoiceField"):
+            form_field = get_filterset_parameter_form_field(Device, "face")
+            self.assertIsInstance(form_field, forms.MultipleChoiceField)
+
         with self.subTest("Test DateTimePicker"):
             form_field = get_filterset_parameter_form_field(Site, "last_updated")
             self.assertIsInstance(form_field.widget, DateTimePicker)