mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#8057: Enable dynamic tables for object list views
This commit is contained in:
netbox
netbox/views
templates
utilities
@ -23,6 +23,7 @@ from utilities.exceptions import AbortTransaction, PermissionsViolation
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, ImportForm, restrict_form_fields,
|
||||
)
|
||||
from utilities.htmx import is_htmx
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.tables import paginate_table
|
||||
from utilities.utils import normalize_querydict, prepare_cloned_fields
|
||||
@ -185,6 +186,12 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
||||
table = self.get_table(request, permissions)
|
||||
paginate_table(table, request)
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if is_htmx(request):
|
||||
return render(request, 'htmx/table.html', {
|
||||
'table': table,
|
||||
})
|
||||
|
||||
context = {
|
||||
'content_type': content_type,
|
||||
'table': table,
|
||||
|
@ -160,6 +160,9 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# TODO: Package HTMX JS locally #}
|
||||
<script src="https://unpkg.com/htmx.org@1.6.1"></script>
|
||||
|
||||
{# Page layout #}
|
||||
{% block layout %}{% endblock %}
|
||||
|
||||
|
@ -95,10 +95,11 @@
|
||||
|
||||
{# Object table #}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-body" id="object_list">
|
||||
<div class="table-responsive">
|
||||
{% render_table table 'inc/table.html' %}
|
||||
{% render_table table 'inc/table_htmx.html' %}
|
||||
</div>
|
||||
{% include 'inc/paginator_htmx.html' with paginator=table.paginator page=table.page %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -125,8 +126,6 @@
|
||||
|
||||
</form>
|
||||
|
||||
{# Paginator #}
|
||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||
</div>
|
||||
|
||||
{# Filter form #}
|
||||
|
5
netbox/templates/htmx/table.html
Normal file
5
netbox/templates/htmx/table.html
Normal file
@ -0,0 +1,5 @@
|
||||
{# Render an HTML table element #}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% render_table table 'inc/table_htmx.html' %}
|
||||
{% include 'inc/paginator_htmx.html' with paginator=table.paginator page=table.page %}
|
43
netbox/templates/inc/paginator_htmx.html
Normal file
43
netbox/templates/inc/paginator_htmx.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% load helpers %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-6 mb-0">
|
||||
{% if paginator.num_pages > 1 %}
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Pages">
|
||||
{% if page.has_previous %}
|
||||
<a href="#" hx-get="{% querystring request page=page.previous_page_number %}" hx-target="#object_list" class="btn btn-outline-secondary">
|
||||
<i class="mdi mdi-chevron-double-left"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% for p in page.smart_pages %}
|
||||
{% if p %}
|
||||
<a href="#" hx-get="{% querystring request page=p %}" hx-target="#object_list" class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}">
|
||||
{{ p }}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||
<span>…</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if page.has_next %}
|
||||
<a href="#" hx-get="{% querystring request page=page.next_page_number %}" hx-target="#object_list" class="btn btn-outline-secondary">
|
||||
<i class="mdi mdi-chevron-double-right"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col col-md-6 mb-0 text-end">
|
||||
<div class="dropdown dropup">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
Per Page
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for n in page.paginator.get_page_lengths %}
|
||||
<li><a class="dropdown-item" href="#" hx-get="{% querystring request per_page=n %}" hx-target="#object_list">{{ n }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
41
netbox/templates/inc/table_htmx.html
Normal file
41
netbox/templates/inc/table_htmx.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% load django_tables2 %}
|
||||
|
||||
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
|
||||
{% if table.show_header %}
|
||||
<thead>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
{% if column.orderable %}
|
||||
<th {{ column.attrs.th.as_html }}><a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}" hx-get="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}" hx-target="#object_list">{{ column.header }}</a></th>
|
||||
{% else %}
|
||||
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
<tbody>
|
||||
{% for row in table.page.object_list|default:table.rows %}
|
||||
<tr {{ row.attrs.as_html }}>
|
||||
{% for column, cell in row.items %}
|
||||
<td {{ column.attrs.td.as_html }}>{{ cell }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
{% if table.empty_text %}
|
||||
<tr>
|
||||
<td colspan="{{ table.columns|length }}" class="text-center text-muted">— {{ table.empty_text }} —</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% if table.has_footer %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
<td>{{ column.footer }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
</table>
|
5
netbox/utilities/htmx.py
Normal file
5
netbox/utilities/htmx.py
Normal file
@ -0,0 +1,5 @@
|
||||
def is_htmx(request):
|
||||
"""
|
||||
Returns True if the request was made by HTMX; False otherwise.
|
||||
"""
|
||||
return 'Hx-Request' in request.headers
|
Reference in New Issue
Block a user