Enable HTMX rendering for embedded tables

This commit is contained in:
jeremystretch 2023-01-13 16:16:32 -05:00
parent ff4eb41c09
commit 807753aa35
30 changed files with 112 additions and 89 deletions

View File

@ -4,6 +4,8 @@ from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import RelatedField from django.db.models.fields.related import RelatedField
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_tables2.data import TableQuerysetData from django_tables2.data import TableQuerysetData
@ -12,7 +14,7 @@ from extras.models import CustomField, CustomLink
from extras.choices import CustomFieldVisibilityChoices from extras.choices import CustomFieldVisibilityChoices
from netbox.tables import columns from netbox.tables import columns
from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.utils import highlight_string, title from utilities.utils import get_viewname, highlight_string, title
__all__ = ( __all__ = (
'BaseTable', 'BaseTable',
@ -197,6 +199,15 @@ class NetBoxTable(BaseTable):
super().__init__(*args, extra_columns=extra_columns, **kwargs) super().__init__(*args, extra_columns=extra_columns, **kwargs)
@property
def htmx_url(self):
viewname = get_viewname(self._meta.model, action='list')
try:
return reverse(viewname)
except NoReverseMatch:
pass
return ''
class SearchTable(tables.Table): class SearchTable(tables.Table):
object_type = columns.ContentTypeColumn( object_type = columns.ContentTypeColumn(

View File

@ -20,7 +20,7 @@ from utilities.choices import ImportFormatChoices
from utilities.error_handlers import handle_protectederror from utilities.error_handlers import handle_protectederror
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
from utilities.forms import BulkRenameForm, ConfirmationForm, ImportForm, restrict_form_fields from utilities.forms import BulkRenameForm, ConfirmationForm, ImportForm, restrict_form_fields
from utilities.htmx import is_htmx from utilities.htmx import is_embedded, is_htmx
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
from utilities.views import GetReturnURLMixin from utilities.views import GetReturnURLMixin
from .base import BaseMultiObjectView from .base import BaseMultiObjectView
@ -161,6 +161,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
# If this is an HTMX request, return only the rendered table HTML # If this is an HTMX request, return only the rendered table HTML
if is_htmx(request): if is_htmx(request):
table.embedded = is_embedded(request)
return render(request, 'htmx/table.html', { return render(request, 'htmx/table.html', {
'table': table, 'table': table,
}) })

View File

@ -12,7 +12,7 @@
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab"> <div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
{% include 'inc/table_controls_htmx.html' %} {% include 'inc/table_controls_htmx.html' %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<h5 class="card-header">{{ title }}</h5> <h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
<div class="card-footer noprint"> <div class="card-footer noprint">
@ -36,7 +36,7 @@
{% else %} {% else %}
<div class="card"> <div class="card">
<h5 class="card-header">{{ title }}</h5> <h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<h5 class="card-header">{{ title }}</h5> <h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
<div class="card-footer noprint"> <div class="card-footer noprint">
@ -36,7 +36,7 @@
{% else %} {% else %}
<div class="card"> <div class="card">
<h5 class="card-header">{{ title }}</h5> <h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -110,7 +110,7 @@ Context:
{% endif %} {% endif %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
{% if page.has_previous %} {% if page.has_previous %}
<a href="#" <a href="#"
hx-get="{% querystring request page=page.previous_page_number %}" hx-get="{% querystring request page=page.previous_page_number %}"
hx-target="#object_list" hx-target="closest .htmx-container"
hx-push-url="true" hx-push-url="true"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
> >
@ -19,7 +19,7 @@
{% if p %} {% if p %}
<a href="#" <a href="#"
hx-get="{% querystring request page=p %}" hx-get="{% querystring request page=p %}"
hx-target="#object_list" hx-target="closest .htmx-container"
hx-push-url="true" hx-push-url="true"
class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}" class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}"
> >
@ -34,7 +34,7 @@
{% if page.has_next %} {% if page.has_next %}
<a href="#" <a href="#"
hx-get="{% querystring request page=page.next_page_number %}" hx-get="{% querystring request page=page.next_page_number %}"
hx-target="#object_list" hx-target="closest .htmx-container"
hx-push-url="true" hx-push-url="true"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
> >
@ -56,7 +56,7 @@
<li> <li>
<a href="#" <a href="#"
hx-get="{% querystring request per_page=n %}" hx-get="{% querystring request per_page=n %}"
hx-target="#object_list" hx-target="closest .htmx-container"
hx-push-url="true" hx-push-url="true"
class="dropdown-item" class="dropdown-item"
>{{ n }}</a> >{{ n }}</a>

View File

@ -1,6 +1,4 @@
{% load django_tables2 %} {% load django_tables2 %}
<div class="table-responsive">
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}> <table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
{% if table.show_header %} {% if table.show_header %}
<thead> <thead>
@ -11,17 +9,17 @@
{% if column.is_ordered %} {% if column.is_ordered %}
<div class="float-end"> <div class="float-end">
<a href="#" <a href="#"
hx-get="{% querystring table.prefixed_order_by_field='' %}" hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field='' %}"
hx-target="#object_list" hx-target="closest .htmx-container"
hx-push-url="true" {% if not table.embedded %}hx-push-url="true"{% endif %}
class="text-danger" class="text-danger"
><i class="mdi mdi-close"></i></a> ><i class="mdi mdi-close"></i></a>
</div> </div>
{% endif %} {% endif %}
<a href="#" <a href="#"
hx-get="{% 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 %}"
hx-target="#object_list" hx-target="closest .htmx-container"
hx-push-url="true" {% if not table.embedded %}hx-push-url="true"{% endif %}
>{{ column.header }}</a> >{{ column.header }}</a>
</th> </th>
{% else %} {% else %}
@ -56,4 +54,3 @@
</tfoot> </tfoot>
{% endif %} {% endif %}
</table> </table>
</div>

View File

@ -18,7 +18,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -30,7 +30,7 @@
</div> </div>
<div class="row px-3"> <div class="row px-3">
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post"> <form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
</div> </div>
</div> </div>

View File

@ -1,5 +1,19 @@
from urllib.parse import urlparse
def is_htmx(request): def is_htmx(request):
""" """
Returns True if the request was made by HTMX; False otherwise. Returns True if the request was made by HTMX; False otherwise.
""" """
return 'Hx-Request' in request.headers return 'Hx-Request' in request.headers
def is_embedded(request):
"""
Returns True if the request indicates that it originates from a URL different from
the path being requested.
"""
hx_current_url = request.headers.get('HX-Current-URL', None)
if not hx_current_url:
return False
return request.path != urlparse(hx_current_url).path