mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 12:12:53 -06:00
* Install django-htmx * Replace is_htmx() function with request.htmx * Remove is_embedded() HTMX utility * Include django-htmx debug error handler
This commit is contained in:
parent
da085e60c2
commit
1d41a8ace5
@ -18,6 +18,10 @@ django-filter
|
|||||||
# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
|
# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
|
||||||
django-graphiql-debug-toolbar
|
django-graphiql-debug-toolbar
|
||||||
|
|
||||||
|
# HTMX utilities for Django
|
||||||
|
# https://django-htmx.readthedocs.io/en/latest/changelog.html
|
||||||
|
django-htmx
|
||||||
|
|
||||||
# Modified Preorder Tree Traversal (recursive nesting of objects)
|
# Modified Preorder Tree Traversal (recursive nesting of objects)
|
||||||
# Pinned to 0.14.0; 0.15.0 requires Python 3.9+
|
# Pinned to 0.14.0; 0.15.0 requires Python 3.9+
|
||||||
# https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst
|
# https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from django.apps import apps
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -20,7 +18,6 @@ from extras.dashboard.utils import get_widget_class
|
|||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm, get_field_value
|
from utilities.forms import ConfirmationForm, get_field_value
|
||||||
from utilities.htmx import is_htmx
|
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.rqworker import get_workers_for_queue
|
from utilities.rqworker import get_workers_for_queue
|
||||||
from utilities.templatetags.builtins.filters import render_markdown
|
from utilities.templatetags.builtins.filters import render_markdown
|
||||||
@ -892,7 +889,7 @@ class DashboardWidgetAddView(LoginRequiredMixin, View):
|
|||||||
template_name = 'extras/dashboard/widget_add.html'
|
template_name = 'extras/dashboard/widget_add.html'
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if not is_htmx(request):
|
if not request.htmx:
|
||||||
return redirect('home')
|
return redirect('home')
|
||||||
|
|
||||||
initial = request.GET or {
|
initial = request.GET or {
|
||||||
@ -942,7 +939,7 @@ class DashboardWidgetConfigView(LoginRequiredMixin, View):
|
|||||||
template_name = 'extras/dashboard/widget_config.html'
|
template_name = 'extras/dashboard/widget_config.html'
|
||||||
|
|
||||||
def get(self, request, id):
|
def get(self, request, id):
|
||||||
if not is_htmx(request):
|
if not request.htmx:
|
||||||
return redirect('home')
|
return redirect('home')
|
||||||
|
|
||||||
widget = request.user.dashboard.get_widget(id)
|
widget = request.user.dashboard.get_widget(id)
|
||||||
@ -983,7 +980,7 @@ class DashboardWidgetDeleteView(LoginRequiredMixin, View):
|
|||||||
template_name = 'generic/object_delete.html'
|
template_name = 'generic/object_delete.html'
|
||||||
|
|
||||||
def get(self, request, id):
|
def get(self, request, id):
|
||||||
if not is_htmx(request):
|
if not request.htmx:
|
||||||
return redirect('home')
|
return redirect('home')
|
||||||
|
|
||||||
widget = request.user.dashboard.get_widget(id)
|
widget = request.user.dashboard.get_widget(id)
|
||||||
@ -1173,7 +1170,7 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
report = module.reports[job.name]
|
report = module.reports[job.name]
|
||||||
|
|
||||||
# If this is an HTMX request, return only the result HTML
|
# If this is an HTMX request, return only the result HTML
|
||||||
if is_htmx(request):
|
if request.htmx:
|
||||||
response = render(request, 'extras/htmx/report_result.html', {
|
response = render(request, 'extras/htmx/report_result.html', {
|
||||||
'report': report,
|
'report': report,
|
||||||
'job': job,
|
'job': job,
|
||||||
@ -1347,7 +1344,7 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
script = module.scripts[job.name]()
|
script = module.scripts[job.name]()
|
||||||
|
|
||||||
# If this is an HTMX request, return only the result HTML
|
# If this is an HTMX request, return only the result HTML
|
||||||
if is_htmx(request):
|
if request.htmx:
|
||||||
response = render(request, 'extras/htmx/script_result.html', {
|
response = render(request, 'extras/htmx/script_result.html', {
|
||||||
'script': script,
|
'script': script,
|
||||||
'job': job,
|
'job': job,
|
||||||
|
@ -367,6 +367,7 @@ INSTALLED_APPS = [
|
|||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
'graphiql_debug_toolbar',
|
'graphiql_debug_toolbar',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
|
'django_htmx',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
'django_prometheus',
|
'django_prometheus',
|
||||||
'graphene_django',
|
'graphene_django',
|
||||||
@ -405,6 +406,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django_htmx.middleware.HtmxMiddleware',
|
||||||
'netbox.middleware.RemoteUserMiddleware',
|
'netbox.middleware.RemoteUserMiddleware',
|
||||||
'netbox.middleware.CoreMiddleware',
|
'netbox.middleware.CoreMiddleware',
|
||||||
'netbox.middleware.MaintenanceModeMiddleware',
|
'netbox.middleware.MaintenanceModeMiddleware',
|
||||||
|
@ -22,7 +22,6 @@ 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, restrict_form_fields
|
from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields
|
||||||
from utilities.forms.bulk_import import BulkImportForm
|
from utilities.forms.bulk_import import BulkImportForm
|
||||||
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.utils import get_viewname
|
from utilities.utils import get_viewname
|
||||||
from utilities.views import GetReturnURLMixin
|
from utilities.views import GetReturnURLMixin
|
||||||
@ -162,8 +161,8 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
|||||||
table = self.get_table(self.queryset, request, has_bulk_actions)
|
table = self.get_table(self.queryset, request, has_bulk_actions)
|
||||||
|
|
||||||
# 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 request.htmx:
|
||||||
if is_embedded(request):
|
if request.htmx.target != 'object_list':
|
||||||
table.embedded = True
|
table.embedded = True
|
||||||
# Hide selection checkboxes
|
# Hide selection checkboxes
|
||||||
if 'pk' in table.base_columns:
|
if 'pk' in table.base_columns:
|
||||||
|
@ -16,7 +16,6 @@ from extras.signals import clear_events
|
|||||||
from utilities.error_handlers import handle_protectederror
|
from utilities.error_handlers import handle_protectederror
|
||||||
from utilities.exceptions import AbortRequest, PermissionsViolation
|
from utilities.exceptions import AbortRequest, PermissionsViolation
|
||||||
from utilities.forms import ConfirmationForm, restrict_form_fields
|
from utilities.forms import ConfirmationForm, restrict_form_fields
|
||||||
from utilities.htmx import is_htmx
|
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fields
|
from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fields
|
||||||
from utilities.views import GetReturnURLMixin
|
from utilities.views import GetReturnURLMixin
|
||||||
@ -136,7 +135,7 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
|||||||
table = self.get_table(table_data, request, has_bulk_actions)
|
table = self.get_table(table_data, request, has_bulk_actions)
|
||||||
|
|
||||||
# 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 request.htmx:
|
||||||
return render(request, 'htmx/table.html', {
|
return render(request, 'htmx/table.html', {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'table': table,
|
'table': table,
|
||||||
@ -224,7 +223,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
|||||||
restrict_form_fields(form, request.user)
|
restrict_form_fields(form, request.user)
|
||||||
|
|
||||||
# If this is an HTMX request, return only the rendered form HTML
|
# If this is an HTMX request, return only the rendered form HTML
|
||||||
if is_htmx(request):
|
if request.htmx:
|
||||||
return render(request, 'htmx/form.html', {
|
return render(request, 'htmx/form.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
})
|
})
|
||||||
@ -349,7 +348,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
|
|||||||
"""
|
"""
|
||||||
handle_protectederror(protected_objects, request, exc)
|
handle_protectederror(protected_objects, request, exc)
|
||||||
|
|
||||||
if is_htmx(request):
|
if request.htmx:
|
||||||
return HttpResponse(headers={
|
return HttpResponse(headers={
|
||||||
'HX-Redirect': obj.get_absolute_url(),
|
'HX-Redirect': obj.get_absolute_url(),
|
||||||
})
|
})
|
||||||
@ -378,7 +377,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
|
|||||||
return self._handle_protected_objects(obj, e.restricted_objects, request, e)
|
return self._handle_protected_objects(obj, e.restricted_objects, request, e)
|
||||||
|
|
||||||
# If this is an HTMX request, return only the rendered deletion form as modal content
|
# If this is an HTMX request, return only the rendered deletion form as modal content
|
||||||
if is_htmx(request):
|
if request.htmx:
|
||||||
viewname = get_viewname(self.queryset.model, action='delete')
|
viewname = get_viewname(self.queryset.model, action='delete')
|
||||||
form_url = reverse(viewname, kwargs={'pk': obj.pk})
|
form_url = reverse(viewname, kwargs={'pk': obj.pk})
|
||||||
return render(request, 'htmx/delete_form.html', {
|
return render(request, 'htmx/delete_form.html', {
|
||||||
@ -480,7 +479,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
instance = self.alter_object(self.queryset.model(), request)
|
instance = self.alter_object(self.queryset.model(), request)
|
||||||
|
|
||||||
# If this is an HTMX request, return only the rendered form HTML
|
# If this is an HTMX request, return only the rendered form HTML
|
||||||
if is_htmx(request):
|
if request.htmx:
|
||||||
return render(request, 'htmx/form.html', {
|
return render(request, 'htmx/form.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,6 @@ from netbox.forms import SearchForm
|
|||||||
from netbox.search import LookupTypes
|
from netbox.search import LookupTypes
|
||||||
from netbox.search.backends import search_backend
|
from netbox.search.backends import search_backend
|
||||||
from netbox.tables import SearchTable
|
from netbox.tables import SearchTable
|
||||||
from utilities.htmx import is_htmx
|
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -96,7 +95,7 @@ class SearchView(View):
|
|||||||
}).configure(table)
|
}).configure(table)
|
||||||
|
|
||||||
# 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 request.htmx:
|
||||||
return render(request, 'htmx/table.html', {
|
return render(request, 'htmx/table.html', {
|
||||||
'table': table,
|
'table': table,
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load django_htmx %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="en"
|
||||||
@ -48,6 +49,7 @@
|
|||||||
src="{% static 'netbox.js' %}?v={{ settings.VERSION }}"
|
src="{% static 'netbox.js' %}?v={{ settings.VERSION }}"
|
||||||
onerror="window.location='{% url 'media_failure' %}?filename=netbox.js'">
|
onerror="window.location='{% url 'media_failure' %}?filename=netbox.js'">
|
||||||
</script>
|
</script>
|
||||||
|
{% django_htmx_script %}
|
||||||
|
|
||||||
{# Additional <head> content #}
|
{# Additional <head> content #}
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'is_embedded',
|
|
||||||
'is_htmx',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_htmx(request):
|
|
||||||
"""
|
|
||||||
Returns True if the request was made by HTMX; False otherwise.
|
|
||||||
"""
|
|
||||||
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
|
|
@ -3,6 +3,7 @@ django-cors-headers==4.3.1
|
|||||||
django-debug-toolbar==4.2.0
|
django-debug-toolbar==4.2.0
|
||||||
django-filter==23.5
|
django-filter==23.5
|
||||||
django-graphiql-debug-toolbar==0.2.0
|
django-graphiql-debug-toolbar==0.2.0
|
||||||
|
django-htmx==1.17.2
|
||||||
django-mptt==0.14.0
|
django-mptt==0.14.0
|
||||||
django-pglocks==1.0.4
|
django-pglocks==1.0.4
|
||||||
django-prometheus==2.3.1
|
django-prometheus==2.3.1
|
||||||
|
Loading…
Reference in New Issue
Block a user