Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes: #9583 - Add column specific search field to tables #15073

Open
wants to merge 52 commits into
base: feature
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4c39516
Preliminary work on 9583.
DanSheps Feb 2, 2024
e762755
HTMX work on 9583.
DanSheps Feb 7, 2024
f7294f7
Preliminary work on 9583.
DanSheps Feb 7, 2024
664a0eb
Preliminary work on 9583.
DanSheps Feb 7, 2024
cc423f5
Apply some quick fixes and add comments
DanSheps Feb 7, 2024
50557c0
Final work on #9583 for basic functionality
DanSheps Feb 9, 2024
03f67f3
CSS update for dropdown
DanSheps Feb 9, 2024
3243ebd
Merge remote-tracking branch 'origin/9583-add_column_specific_search_…
DanSheps Feb 9, 2024
b54cfd6
Update CSS
DanSheps Feb 12, 2024
5f69666
Change dropdown position and fix test failure
DanSheps Feb 12, 2024
06c1aff
Fix test failure
DanSheps Feb 12, 2024
4ae6683
Merge in recent feture changes
DanSheps Feb 12, 2024
c3f1a96
Update CSS after merge
DanSheps Feb 12, 2024
f81f76f
Optimizations
DanSheps Feb 12, 2024
0309796
Fix extraneous __all__ entry
DanSheps Feb 12, 2024
84151cb
Fix tom-select errors related to field id. Break out render_field fu…
DanSheps Feb 13, 2024
77bfd62
Merge branch 'feature' into 9583-add_column_specific_search_field_to_…
jeremystretch Mar 7, 2024
25a4e94
Merge branch 'feature' into 9583-add_column_specific_search_field_to_…
jeremystretch Mar 20, 2024
f257f4a
Apply suggestions from code review
DanSheps Mar 22, 2024
8a7df0b
Update netbox/utilities/templatetags/form_helpers.py
DanSheps Mar 22, 2024
a422a3c
Modify logic for table column filtering to further isolate the column…
DanSheps Mar 22, 2024
8ad79a6
Update CSS
DanSheps Mar 22, 2024
a9aa0cb
Merge in latest feature
DanSheps Apr 15, 2024
35cff12
Apply suggestions from Arthur
DanSheps Apr 15, 2024
1c995fa
Perform OOB swap for filter badges
DanSheps Apr 15, 2024
479c69b
Fix up duplication of template chits when rendering HTMX
DanSheps Apr 23, 2024
5830ae9
Rename variable for doing OOB swaps on the table
DanSheps Apr 25, 2024
fd38255
Rename swap variable back and remove join
DanSheps Apr 25, 2024
30e6531
Add a table form override and add a table column remap option
DanSheps Jun 13, 2024
f130678
Merge branch 'feature' of https://github.com/netbox-community/netbox …
DanSheps Jun 13, 2024
0ad1db9
Regnerate CSS
DanSheps Jun 13, 2024
b12ce97
Update to latest feature
DanSheps Jul 2, 2024
77cb8ac
Merge branch 'feature' into 9583-add_column_specific_search_field_to_…
DanSheps Oct 17, 2024
d6ab7b7
Update bundle
DanSheps Oct 17, 2024
012e815
Merge branch 'feature' into 9583-add_column_specific_search_field_to_…
jeremystretch Dec 6, 2024
852535e
Merge in updates from feature
DanSheps Jan 20, 2025
f2c74db
Update display of filtering chits
DanSheps Jan 20, 2025
882aa30
Simplify filter generation
DanSheps Jan 20, 2025
8d65973
Simplify table header filter rendering
DanSheps Jan 20, 2025
92c3bbc
Correct issue with filter_form not passing through render_table from …
DanSheps Jan 20, 2025
4381ea9
Merge Head
DanSheps Jan 20, 2025
92f6f42
Removed unused import
DanSheps Jan 20, 2025
764c676
Fix bundles
DanSheps Jan 20, 2025
ee83895
Fix bundles
DanSheps Jan 20, 2025
a288712
Fix assets
DanSheps Jan 20, 2025
e12ae3a
Figure out what is failing
DanSheps Jan 20, 2025
1958965
More bundle debugging
DanSheps Jan 20, 2025
7e5d8ea
Debugging
DanSheps Jan 20, 2025
694fcd3
Fix assets
DanSheps Jan 20, 2025
451a1f4
Remove debug
DanSheps Jan 20, 2025
434cf37
Fix test error and OOB-swap
DanSheps Jan 21, 2025
e7649f0
Fix test error
DanSheps Jan 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions netbox/netbox/tables/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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 = {
Expand Down
18 changes: 16 additions & 2 deletions netbox/netbox/views/generic/bulk_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ def get(self, request):
# Render the objects table
table = self.get_table(self.queryset, request, has_bulk_actions)

# Check for filterset_form(s) on this view and/or the table, if a form exists:
# * If both exist, initialize both
# * If a filterset form for the table exists, only initialize the table filterset_form
# * If a filterset form exists for the view, initialize the filterset form
# * Apply to the table for use by the table and initialize a separate instance of the form for use by the table
# column filters
# * Otherwise set to None
if self.filterset_form:
filterset_form = self.filterset_form(request.GET)
table.filterset_form = self.filterset_form(request.GET)
else:
filterset_form = None
table.filterset_form = None

# If this is an HTMX request, return only the rendered table HTML
if htmx_partial(request):
if request.GET.get('embedded', False):
Expand All @@ -179,15 +193,15 @@ def get(self, request):
table.columns.hide('pk')
return render(request, 'htmx/table.html', {
'table': table,
'filter_form': filterset_form,
'model': model,
'actions': actions,
})

context = {
'model': model,
'table': table,
'actions': actions,
'filter_form': self.filterset_form(request.GET) if self.filterset_form else None,
'filter_form': filterset_form,
'prerequisite_model': get_prerequisite_model(self.queryset),
**self.get_extra_context(request),
}
Expand Down
2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.css

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions netbox/project-static/styles/custom/_misc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ span.color-label {
opacity: 0;
}

// Override bootstrap "dropdown" positioning and display for column filters
.column-filter {
position: static;
display: inline;
}

// Override mdi font-size to adjust filter icon size
.column-filter.dropdown > .dropdown-toggle > .mdi-filter-settings {
font-size: .625rem;
}

.column-filter.dropdown > .dropdown-menu {
max-width: 300px;
}

.column-filter.dropdown-toggle:after { content: none }

// NetBox edition text
.netbox-edition {
letter-spacing: .15rem;
Expand Down
5 changes: 2 additions & 3 deletions netbox/templates/generic/object_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@

{# Object list tab #}
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">

{# Applied filters #}
{% if filter_form %}
{% if not request.htmx %}
{# Applied filters #}
{% applied_filters model filter_form request.GET %}
{% endif %}

Expand Down
5 changes: 5 additions & 0 deletions netbox/templates/htmx/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
{% load buttons %}
{% load render_table from django_tables2 %}

{% if request.htmx %}
{# OOB Swaps to update various components #}
{% applied_filters model filter_form request.GET %}
{% endif %}

<div class="htmx-container table-responsive">
{% with preferences|get_key:"pagination.placement" as paginator_placement %}
{% if paginator_placement == 'top' or paginator_placement == 'both' %}
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/inc/table_controls_htmx.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<div class="col-auto d-print-none">
<div class="input-group input-group-flat me-2 quicksearch" hx-disinherit="hx-select hx-swap">
<input type="search" results="5" name="q" id="quicksearch" class="form-control" placeholder="{% trans "Quick search" %}"
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
hx-get="" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
<span class="input-group-text py-1">
<a href="#" id="quicksearch_clear" class="invisible text-secondary"><i class="mdi mdi-close-circle"></i></a>
</span>
Expand Down
6 changes: 5 additions & 1 deletion netbox/templates/inc/table_htmx.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% load django_tables2 %}
{% load form_helpers %}
{% load i18n %}
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %} hx-disinherit="hx-target hx-select" hx-swap="outerHTML">
{% if table.show_header %}
Expand All @@ -15,12 +16,15 @@
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field='' %}"
class="text-danger"
{% if not table.embedded %}hx-push-url="true"{% endif %}
title="{% trans "Clear ordering" %}"
><i class="mdi mdi-close"></i></a>
</div>
{% endif %}
{% render_table_filter_field column.name table request%}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"
hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"
{% if not table.embedded %}hx-push-url="true"{% endif %}
>{{ column.header }}</a>
</th>
{% else %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% load form_helpers %}
{% if field %}
<div class="column-filter dropdown">
<a href="#" class="dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="outside"><i class="mdi mdi-filter-settings"></i></a>
<div class="dropdown-menu">
<div class="px-3 py-3">
{% include "form_helpers/render_field.html" %}
</div>
</div>
</div>
{% endif %}
40 changes: 21 additions & 19 deletions netbox/utilities/templates/helpers/applied_filters.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
{% load i18n %}
{% if applied_filters %}
<div class="mb-3">
{% for filter in applied_filters %}
<a href="{{ filter.link_url }}" class="badge rounded-pill text-bg-primary text-decoration-none me-1">
<i class="mdi mdi-close"></i> {{ filter.link_text }}
</a>
{% endfor %}
{% if applied_filters|length > 1 %}
<a href="?" class="badge rounded-pill text-bg-danger text-decoration-none me-1">
<i class="mdi mdi-tag-off"></i> {% trans "Clear all" %}
</a>
{% endif %}
{% if save_link %}
<a href="{{ save_link }}" class="badge rounded-pill text-bg-success text-decoration-none me-1">
<i class="mdi mdi-content-save"></i> {% trans "Save" %}
</a>
{% endif %}
</div>
{% endif %}
<div id="applied_filters_pane" hx-swap-oob="true">
{% if applied_filters %}
<div class="mb-3">
{% for filter in applied_filters %}
<a href="{{ filter.link_url }}" class="badge rounded-pill text-bg-primary text-decoration-none me-1">
<i class="mdi mdi-close"></i> {{ filter.link_text }}
</a>
{% endfor %}
{% if applied_filters|length > 1 %}
<a href="?" class="badge rounded-pill text-bg-danger text-decoration-none me-1">
<i class="mdi mdi-tag-off"></i> {% trans "Clear all" %}
</a>
{% endif %}
{% if save_link %}
<a href="{{ save_link }}" class="badge rounded-pill text-bg-success text-decoration-none me-1">
<i class="mdi mdi-content-save"></i> {% trans "Save" %}
</a>
{% endif %}
</div>
{% endif %}
</div>
52 changes: 52 additions & 0 deletions netbox/utilities/templatetags/form_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
'render_custom_fields',
'render_errors',
'render_field',
'render_table_filter_field',
'render_form',
DanSheps marked this conversation as resolved.
Show resolved Hide resolved
'widget_type',
)

from utilities.templatetags.helpers import querystring

register = template.Library()

Expand All @@ -30,6 +32,11 @@ def getfield(form, fieldname):
return None


@register.filter()
def get_filter_field(form, fieldname):
return getfield(form, f'{fieldname}') or getfield(form, f'{fieldname}_id')


@register.filter(name='widget_type')
def widget_type(field):
"""
Expand All @@ -47,6 +54,7 @@ def widget_type(field):
# Inclusion tags
#


@register.inclusion_tag('form_helpers/render_fieldset.html')
def render_fieldset(form, fieldset):
"""
Expand Down Expand Up @@ -109,13 +117,57 @@ def render_field(field, bulk_nullable=False, label=None):
"""
Render a single form field from template
"""

return {
'field': field,
'label': label or field.label,
'bulk_nullable': bulk_nullable or getattr(field, '_nullable', False),
}


@register.inclusion_tag('form_helpers/render_table_filter_field.html')
def render_table_filter_field(fieldname, table, request):
"""
Render a single form field for table column filters from template
"""
url = ""
field = None

# Does this table have a filterset form?
if hasattr(table, 'filterset_form') and table.filterset_form is not None:
# Get the filterset field
field = get_filter_field(table.filterset_form, fieldname)

# Return if no filterset field
if field is None:
return {}

# Handle filter forms
if table:
# Build kwargs for querystring function
kwargs = {field.name: None}
# Build request url
if request and table.htmx_url:
url = table.htmx_url + querystring(request, **kwargs)
elif request:
url = querystring(request, **kwargs)

# Set HTMX args
if hasattr(field.field, 'widget'):
field.field.widget.attrs.update({
'id': f'table_filter_id_{field.name}',
'hx-get': url if url else '#',
'hx-push-url': "true",
'hx-trigger': 'hidden.bs.dropdown from:closest .dropdown'
})

return {
'field': field,
'label': None,
'bulk_nullable': False,
}


@register.inclusion_tag('form_helpers/render_custom_fields.html')
def render_custom_fields(form):
"""
Expand Down
3 changes: 3 additions & 0 deletions netbox/utilities/templatetags/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ def applied_filters(context, model, form, query_params):
Display the active filters for a given filter form.
"""
user = context['request'].user
if not form:
return
form.is_valid() # Ensure cleaned_data has been set

applied_filters = []
Expand Down Expand Up @@ -304,6 +306,7 @@ def applied_filters(context, model, form, query_params):
save_link = f"{url}?object_types={object_type}&parameters={quote(parameters)}"

return {
'request': context['request'],
'applied_filters': applied_filters,
'save_link': save_link,
}
Loading