This commit is contained in:
Daniel Sheppard 2024-04-15 12:05:59 -05:00 committed by GitHub
commit 9834bd4f55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 106 additions and 7 deletions

View File

@ -40,6 +40,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 = {

View File

@ -161,6 +161,18 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
# Render the objects table
table = self.get_table(self.queryset, request, has_bulk_actions)
# Check for filterset_form on this view, if a form exists
# * Apply to context for use by the filter form tab and initialize the 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 not request.htmx.target:
@ -169,14 +181,16 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
if 'pk' in table.base_columns:
table.columns.hide('pk')
return render(request, 'htmx/table.html', {
'model': model,
'table': table,
'filter_form': filterset_form,
})
context = {
'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),
}

File diff suppressed because one or more lines are too long

View File

@ -28,3 +28,20 @@ span.color-label {
visibility: hidden;
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 }

View File

@ -63,9 +63,7 @@ Context:
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
{# Applied filters #}
{% if filter_form %}
{% applied_filters model filter_form request.GET %}
{% endif %}
{% include 'inc/applied_filters_pane.html' %}
{# Object table controls #}
{% include 'inc/table_controls_htmx.html' with table_modal="ObjectTable_config" %}

View File

@ -2,6 +2,9 @@
{% load helpers %}
{% load render_table from django_tables2 %}
{# Applied filters #}
{% include 'inc/applied_filters_pane.html' %}
{% with preferences|get_key:"pagination.placement" as paginator_placement %}
{% if paginator_placement == 'top' or paginator_placement == 'both' %}
{% include 'inc/paginator.html' with htmx=True table=table paginator=table.paginator page=table.page %}

View File

@ -0,0 +1,8 @@
{% load helpers %}
<div id="applied_filters_pane" hx-swap-oob="true">
{# Applied filters #}
{% if filter_form %}
{% applied_filters model filter_form request.GET %}
{% endif %}
</div>

View File

@ -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 px-2 py-1" placeholder="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="d-none text-secondary"><i class="mdi mdi-close-circle"></i></a>
</span>

View File

@ -0,0 +1,11 @@
{% load form_helpers %}
{% if form_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">
{% render_table_filter_field form_field table=table request=request %}
</div>
</div>
</div>
{% endif %}

View File

@ -1,4 +1,5 @@
{% load django_tables2 %}
{% load form_helpers %}
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %} hx-disinherit="hx-target hx-select hx-swap">
{% if table.show_header %}
<thead
@ -14,11 +15,16 @@
<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 %}
><i class="mdi mdi-close"></i></a>
</div>
{% endif %}
{% if table.filterset_form %}
{% include 'inc/table_header_filter_dropdown.html' with form_field=table.filterset_form|get_filter_field:column.name %}
{% endif %}
<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 %}

View File

@ -6,6 +6,7 @@ from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, T
__all__ = (
'getfield',
'get_filter_field',
'render_custom_fields',
'render_errors',
'render_field',
@ -13,6 +14,7 @@ __all__ = (
'widget_type',
)
from utilities.templatetags.helpers import querystring
register = template.Library()
@ -32,6 +34,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):
"""
@ -120,6 +127,7 @@ def render_field(field, bulk_nullable=False, label=None):
"""
Render a single form field from template
"""
return {
'field': field,
'label': label or field.label,
@ -127,6 +135,39 @@ def render_field(field, bulk_nullable=False, label=None):
}
@register.inclusion_tag('form_helpers/render_field.html')
def render_table_filter_field(field, table, request):
"""
Render a single form field for table column filters from template
"""
url = ""
# 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):
"""