diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 495e56991..e82f61c6b 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -38,6 +38,7 @@ class BaseTable(tables.Table): :param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed. """ exempt_columns = () + filterset_form = None class Meta: attrs = { diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index f5b605ccd..406ff049d 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -160,6 +160,11 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): # Render the objects table table = self.get_table(self.queryset, request, has_bulk_actions) + filterset_form = self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None + + if hasattr(self, 'filterset_form'): + table.filterset_form = filterset_form + # If this is an HTMX request, return only the rendered table HTML if request.htmx: if request.htmx.target != 'object_list': @@ -175,7 +180,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): 'model': model, 'table': table, 'actions': actions, - 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, + 'filter_form': filterset_form, 'prerequisite_model': get_prerequisite_model(self.queryset), **self.get_extra_context(request), } diff --git a/netbox/templates/inc/table_header_filter_dropdown.html b/netbox/templates/inc/table_header_filter_dropdown.html new file mode 100644 index 000000000..4d134c9ec --- /dev/null +++ b/netbox/templates/inc/table_header_filter_dropdown.html @@ -0,0 +1,11 @@ +{% load form_helpers %} +{% if form_field %} + +{% endif %} \ No newline at end of file diff --git a/netbox/templates/inc/table_htmx.html b/netbox/templates/inc/table_htmx.html index 4eedce72d..6e9ebe874 100644 --- a/netbox/templates/inc/table_htmx.html +++ b/netbox/templates/inc/table_htmx.html @@ -1,4 +1,5 @@ {% load django_tables2 %} +{% load form_helpers %} {% if table.show_header %} @@ -21,9 +22,13 @@ hx-target="closest .htmx-container" {% if not table.embedded %}hx-push-url="true"{% endif %} >{{ column.header }} + {% include 'inc/table_header_filter_dropdown.html' with form_field=table.filterset_form|getfilterfield:column.name %} {% else %} - {{ column.header }} + + {{ column.header }} + {% include 'inc/table_header_filter_dropdown.html' with form_field=table.filterset_form|getfilterfield:column.name %} + {% endif %} {% endfor %} diff --git a/netbox/utilities/templatetags/form_helpers.py b/netbox/utilities/templatetags/form_helpers.py index f4fd8b819..8b3979d37 100644 --- a/netbox/utilities/templatetags/form_helpers.py +++ b/netbox/utilities/templatetags/form_helpers.py @@ -2,6 +2,7 @@ from django import template __all__ = ( 'getfield', + 'getfilterfield', 'render_custom_fields', 'render_errors', 'render_field', @@ -28,6 +29,15 @@ def getfield(form, fieldname): return None +@register.filter() +def getfilterfield(form, fieldname): + field = getfield(form, f'{fieldname}') + if field is not None: + return field + else: + return getfield(form, f'{fieldname}_id') + + @register.filter(name='widget_type') def widget_type(field): """ @@ -57,6 +67,24 @@ def render_field(field, bulk_nullable=False, label=None): } +@register.inclusion_tag('form_helpers/render_field.html') +def render_filter_field(field, bulk_nullable=False, label=None): + """ + Render a single form field from template + """ + if hasattr(field.field, 'widget'): + field.field.widget.attrs.update({ + 'hx-get': None, + 'hx-target': '#object_list', + 'hx-trigger': 'hidden.bs.dropdown from:closest .dropdown' + }) + return { + 'field': field, + 'label': None, + 'bulk_nullable': bulk_nullable, + } + + @register.inclusion_tag('form_helpers/render_custom_fields.html') def render_custom_fields(form): """