mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 09:16:10 -06:00
Enable HTMX rendering for embedded tables
This commit is contained in:
parent
ff4eb41c09
commit
807753aa35
@ -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(
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user