diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index ad209c516..bb69a4218 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1419,7 +1419,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): self.initial['rack'] = self.instance.parent_bay.device.rack_id -class BaseDeviceCSVForm(forms.ModelForm): +class BaseDeviceCSVForm(CustomFieldForm): device_role = forms.ModelChoiceField( queryset=DeviceRole.objects.all(), to_field_name='name', diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index b48482c93..07af96afd 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -12,7 +12,7 @@ from dcim.models import DeviceRole, Platform, Region, Site from tenancy.models import Tenant, TenantGroup from utilities.forms import ( add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect, - FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, + CSVChoiceFieldCustom, FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, ) from .constants import ( CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, @@ -75,7 +75,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F default_choice = cf.choices.get(value=initial).pk except ObjectDoesNotExist: pass - field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice) + field = CSVChoiceFieldCustom(choices=choices, coerce=int, required=cf.required, initial=default_choice) # URL elif cf.type == CF_TYPE_URL: diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 1d4671bf6..42db79d40 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -436,6 +436,26 @@ class CSVChoiceField(forms.ChoiceField): return self.choice_values[value] +class CSVChoiceFieldCustom(forms.TypedChoiceField): + """ + For custom fields, invert the provided set of choices to take the human-friendly label as input, and return the database value. + """ + + def __init__(self, choices=(), coerce=lambda val: val, empty_value='', *args, **kwargs): + super().__init__(choices=choices, coerce=coerce, empty_value=empty_value, *args, **kwargs) + self.choice_values = {label: value for value, label in unpack_grouped_choices(choices)} + + def clean(self, value): + if not value: + return None + + for choice in self.choice_values: + if str(choice) == value: + return self.choice_values[choice] + + return super().clean(value) + + class ExpandableNameField(forms.CharField): """ A field which allows for numeric range expansion diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 2b465d54a..0e893536a 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -143,6 +143,9 @@ def example_choices(field, arg=3): break if not value or not label: continue + if hasattr(label, 'value'): + # Handling for custom field choice labels + label = label.value examples.append(label) return ', '.join(examples) or 'None'