From 49617a595de54f9407253d3c1689d196b2998149 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Tue, 7 Sep 2021 18:28:49 -0700 Subject: [PATCH 1/5] #7205: Handle `null_option` in dynamic multi-select choices field --- netbox/utilities/forms/fields.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 05eaa9515..6684eb908 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -477,3 +477,13 @@ class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultip """ filter = django_filters.ModelMultipleChoiceFilter widget = widgets.APISelectMultiple + + def clean(self, value: list[str]): + """ + When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the + string 'null'. This will check for that condition and gracefully handle the conversion to a NoneType. + """ + if self.null_option is not None and settings.FILTERS_NULL_CHOICE_VALUE in value: + value = [v for v in value if v != settings.FILTERS_NULL_CHOICE_VALUE] + return [self.null_option, *value] + return super().clean(value) From 752de0d9c0e358c018ee5a9009567243a9d64831 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Tue, 7 Sep 2021 18:30:45 -0700 Subject: [PATCH 2/5] Fixes #7205: Handle `null_option` when getting selected form values in `applied_filters` template tag --- docs/release-notes/version-3.0.md | 1 + netbox/utilities/forms/utils.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index e1bb57d72..f1b96d8b3 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -10,6 +10,7 @@ * [#7176](https://github.com/netbox-community/netbox/issues/7176) - Fix issue where query parameters were duplicated across different forms of the same type * [#7188](https://github.com/netbox-community/netbox/issues/7188) - Fix issue where select fields with `null_option` did not render or send the null option * [#7193](https://github.com/netbox-community/netbox/issues/7193) - Fix prefix (flat) template issue when viewing child prefixes with prefixes available +* [#7205](https://github.com/netbox-community/netbox/issues/7205) - Fix issue where selected fields with `null_option` set were not added to applied filters --- diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index 0121b250c..c0c14c80d 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -120,11 +120,20 @@ def get_selected_values(form, field_name): if not hasattr(form, 'cleaned_data'): form.is_valid() filter_data = form.cleaned_data.get(field_name) + field = form.fields[field_name] # Selection field - if hasattr(form.fields[field_name], 'choices'): + if hasattr(field, 'choices'): try: - choices = dict(unpack_grouped_choices(form.fields[field_name].choices)) + grouped_choices = [(k, v) for k, v in field.choices] + + if hasattr(field, 'null_option'): + # If the field has a `null_option` attribute set and it is selected, + # add it to the field's grouped choices. + if field.null_option is not None and field.null_option in filter_data: + grouped_choices.append((field.null_option, field.null_option)) + + choices = dict(unpack_grouped_choices(grouped_choices)) return [ label for value, label in choices.items() if str(value) in filter_data ] From 6a15c2ae8611c4ed9b4fa14fdf7e41ba489887e2 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Tue, 7 Sep 2021 18:37:55 -0700 Subject: [PATCH 3/5] Remove invalid (for Python <3.9) type annotation --- netbox/utilities/forms/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 6684eb908..cf7bff14f 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -478,7 +478,7 @@ class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultip filter = django_filters.ModelMultipleChoiceFilter widget = widgets.APISelectMultiple - def clean(self, value: list[str]): + def clean(self, value): """ When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the string 'null'. This will check for that condition and gracefully handle the conversion to a NoneType. From a9c1c8968e21787ba4f2c9f7abb273b8746e2560 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Tue, 7 Sep 2021 18:43:36 -0700 Subject: [PATCH 4/5] Return cleaned `null_option` value as `None` in dynamic multi-select field --- netbox/utilities/forms/fields.py | 2 +- netbox/utilities/forms/utils.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index cf7bff14f..2561c2e22 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -485,5 +485,5 @@ class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultip """ if self.null_option is not None and settings.FILTERS_NULL_CHOICE_VALUE in value: value = [v for v in value if v != settings.FILTERS_NULL_CHOICE_VALUE] - return [self.null_option, *value] + return [None, *value] return super().clean(value) diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index c0c14c80d..3297f7955 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -121,7 +121,6 @@ def get_selected_values(form, field_name): form.is_valid() filter_data = form.cleaned_data.get(field_name) field = form.fields[field_name] - # Selection field if hasattr(field, 'choices'): try: @@ -130,12 +129,12 @@ def get_selected_values(form, field_name): if hasattr(field, 'null_option'): # If the field has a `null_option` attribute set and it is selected, # add it to the field's grouped choices. - if field.null_option is not None and field.null_option in filter_data: + if field.null_option is not None and None in filter_data: grouped_choices.append((field.null_option, field.null_option)) choices = dict(unpack_grouped_choices(grouped_choices)) return [ - label for value, label in choices.items() if str(value) in filter_data + label for value, label in choices.items() if str(value) in filter_data or None in filter_data ] except TypeError: # Field uses dynamic choices. Show all that have been populated. From 45988b9818e52da75e5199a2b1291012220a1a18 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 8 Sep 2021 11:11:52 -0400 Subject: [PATCH 5/5] Minor cleanup for get_selected_values() --- netbox/utilities/forms/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index 3297f7955..bb1f56c4d 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -1,6 +1,7 @@ import re from django import forms +from django.conf import settings from django.forms.models import fields_for_model from utilities.choices import unpack_grouped_choices @@ -124,17 +125,16 @@ def get_selected_values(form, field_name): # Selection field if hasattr(field, 'choices'): try: - grouped_choices = [(k, v) for k, v in field.choices] + choices = unpack_grouped_choices(field.choices) if hasattr(field, 'null_option'): # If the field has a `null_option` attribute set and it is selected, # add it to the field's grouped choices. if field.null_option is not None and None in filter_data: - grouped_choices.append((field.null_option, field.null_option)) + choices.append((settings.FILTERS_NULL_CHOICE_VALUE, field.null_option)) - choices = dict(unpack_grouped_choices(grouped_choices)) return [ - label for value, label in choices.items() if str(value) in filter_data or None in filter_data + label for value, label in choices if str(value) in filter_data or None in filter_data ] except TypeError: # Field uses dynamic choices. Show all that have been populated.