From c7ebad0fbbbc352f9dbe584d776461e18d55fe7b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 10 Aug 2021 13:04:50 -0400 Subject: [PATCH] Closes #6931: Include applied filters on object list view --- docs/release-notes/version-3.0.md | 1 + netbox/templates/generic/object_list.html | 9 ++++-- .../templatetags/applied_filters.html | 9 ++++++ netbox/utilities/forms/utils.py | 26 ++++++++++++++++ netbox/utilities/templatetags/helpers.py | 31 ++++++++++++++++++- 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 netbox/templates/utilities/templatetags/applied_filters.html diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 458d8a26c..e1eda12b8 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -5,6 +5,7 @@ ### Enhancements * [#6829](https://github.com/netbox-community/netbox/issues/6829) - Extend GraphQL API to support reverse generic relationships +* [#6931](https://github.com/netbox-community/netbox/issues/6931) - Include applied filters on object list view ### Bug Fixes diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index cbbdecd99..db41151ee 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -47,9 +47,14 @@ {# Object list #}
- {% if table.paginator.num_pages > 1 %} - {# "Select all" form #} + {# Applied filters #} + {% if filter_form %} + {% applied_filters filter_form request.GET %} + {% endif %} + + {# "Select all" form #} + {% if table.paginator.num_pages > 1 %} {% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
diff --git a/netbox/templates/utilities/templatetags/applied_filters.html b/netbox/templates/utilities/templatetags/applied_filters.html new file mode 100644 index 000000000..0be3aa23e --- /dev/null +++ b/netbox/templates/utilities/templatetags/applied_filters.html @@ -0,0 +1,9 @@ +{% if applied_filters %} +
+ {% for filter in applied_filters %} + + {{ filter.link_text }} + + {% endfor %} +
+{% endif %} diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index 90df55b9c..5a5f87ed8 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -3,6 +3,7 @@ import re from django import forms from django.forms.models import fields_for_model +from utilities.choices import unpack_grouped_choices from utilities.querysets import RestrictedQuerySet from .constants import * @@ -11,6 +12,7 @@ __all__ = ( 'expand_alphanumeric_pattern', 'expand_ipaddress_pattern', 'form_from_model', + 'get_selected_values', 'parse_alphanumeric_range', 'parse_numeric_range', 'restrict_form_fields', @@ -111,6 +113,30 @@ def expand_ipaddress_pattern(string, family): yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), remnant]) +def get_selected_values(form, field_name): + """ + Return the list of selected human-friendly values for a form field + """ + if not hasattr(form, 'cleaned_data'): + form.is_valid() + + # Selection field + if hasattr(form.fields[field_name], 'choices'): + try: + choices = dict(unpack_grouped_choices(form.fields[field_name].choices)) + return [ + label for value, label in choices.items() if value in form.cleaned_data[field_name] + ] + except TypeError: + # Field uses dynamic choices. Show all that have been populated. + return [ + subwidget.choice_label for subwidget in form[field_name].subwidgets + ] + + # Non-selection field + return [str(form.cleaned_data[field_name])] + + def add_blank_choice(choices): """ Add a blank choice to the beginning of a choices list. diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 700fb4369..4784fae30 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -13,7 +13,7 @@ from django.utils.html import strip_tags from django.utils.safestring import mark_safe from markdown import markdown -from utilities.forms import TableConfigForm +from utilities.forms import get_selected_values, TableConfigForm from utilities.utils import foreground_color register = template.Library() @@ -403,3 +403,32 @@ def table_config_form(table, table_name=None): 'table_name': table_name or table.__class__.__name__, 'table_config_form': TableConfigForm(table=table), } + + +@register.inclusion_tag('utilities/templatetags/applied_filters.html') +def applied_filters(form, query_params): + """ + Display the active filters for a given filter form. + """ + form.is_valid() + + applied_filters = [] + for filter_name in form.changed_data: + if filter_name not in query_params: + continue + + bound_field = form.fields[filter_name].get_bound_field(form, filter_name) + querydict = query_params.copy() + querydict.pop(filter_name) + display_value = ', '.join(get_selected_values(form, filter_name)) + + applied_filters.append({ + 'name': filter_name, + 'value': form.cleaned_data[filter_name], + 'link_url': f'?{querydict.urlencode()}', + 'link_text': f'{bound_field.label}: {display_value}', + }) + + return { + 'applied_filters': applied_filters, + }