From 744be59a4d3dfa530c480160403ffec7bf57ec77 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 28 Mar 2024 11:51:38 -0400 Subject: [PATCH] Closes #14736: Enable HTMX navigation globally (#15158) * Enable HTMX boosting * Refactor HTMX properties for tables * Fix dashboard object list widget * Disable scrolling to page content * Fix initialization of TomSelect dropdowns after HTMX loading * Replace formaction properties with hx-post * Fix quick search field on object list view * Reinitialize copy-to-clipboard buttons upon HTMX load * Disable scrolling effect for intra-page navigation * Introduce user preference for toggling HTMX navigation * Enable HTMX navigation only when selected by user * Pass htmx_navigation context * Fix display of confirmation form when deleting an object * Disable HTMX boosting for rack elevation SVG downloads * Fix dyanmic form rendering * Introduce htmx_boost template tag; enable HTMX for user menu * Use out-of-band sap to update footer stamp * Fix display of toasts after form submission * Fix user preference selection * Misc cleanup * Rename render_partial() to htmx_partial() * Add docstring to htmx_boost template tag * Disable HTMX for user preferences form to force a full page refresh on changes --- netbox/core/views.py | 7 +-- netbox/extras/views.py | 3 +- netbox/netbox/context_processors.py | 4 +- netbox/netbox/preferences.py | 8 +++ netbox/netbox/views/generic/bulk_views.py | 5 +- netbox/netbox/views/generic/object_views.py | 7 +-- netbox/netbox/views/misc.py | 3 +- netbox/project-static/dist/netbox.css | Bin 552517 -> 552553 bytes netbox/project-static/dist/netbox.js | Bin 375838 -> 375669 bytes netbox/project-static/dist/netbox.js.map | Bin 340628 -> 340501 bytes netbox/project-static/src/htmx.ts | 18 ++----- netbox/project-static/styles/netbox.scss | 1 + .../styles/overrides/_bootstrap.scss | 4 ++ netbox/templates/account/preferences.html | 2 +- netbox/templates/base/base.html | 1 + netbox/templates/base/layout.html | 47 +++++++++++++++++- netbox/templates/dcim/component_list.html | 2 +- .../dcim/device/components_base.html | 4 +- .../templates/dcim/device/consoleports.html | 2 +- .../dcim/device/consoleserverports.html | 2 +- netbox/templates/dcim/device/frontports.html | 2 +- netbox/templates/dcim/device/interfaces.html | 2 +- .../templates/dcim/device/poweroutlets.html | 2 +- netbox/templates/dcim/device/powerports.html | 2 +- netbox/templates/dcim/device/rearports.html | 2 +- netbox/templates/dcim/device_list.html | 22 ++++---- .../dcim/devicetype/component_templates.html | 4 +- netbox/templates/dcim/inc/rack_elevation.html | 2 +- .../dcim/moduletype/component_templates.html | 6 +-- netbox/templates/dcim/powerpanel.html | 6 +-- .../templates/extras/configcontext_list.html | 2 +- .../templates/extras/configtemplate_list.html | 2 +- .../extras/dashboard/widgets/objectlist.html | 2 +- .../templates/extras/exporttemplate_list.html | 2 +- netbox/templates/generic/object_children.html | 4 +- netbox/templates/generic/object_edit.html | 2 +- netbox/templates/inc/messages.html | 2 +- netbox/templates/inc/paginator.html | 35 ++++--------- netbox/templates/inc/table_controls_htmx.html | 2 +- netbox/templates/inc/table_htmx.html | 10 ++-- netbox/templates/inc/user_menu.html | 41 --------------- .../virtualization/cluster/devices.html | 2 +- .../virtualmachine/interfaces.html | 2 +- .../virtualmachine/virtual_disks.html | 2 +- .../virtualization/virtualmachine_list.html | 4 +- netbox/users/forms/model_forms.py | 3 +- netbox/utilities/htmx.py | 13 +++++ .../templates/builtins/htmx_table.html | 3 +- .../templates/buttons/bulk_delete.html | 2 +- .../templates/buttons/bulk_edit.html | 2 +- .../utilities/templates/buttons/delete.html | 2 + .../utilities/templates/navigation/menu.html | 3 +- .../utilities/templatetags/builtins/tags.py | 12 +++++ netbox/utilities/templatetags/navigation.py | 35 +++++++++++-- 54 files changed, 206 insertions(+), 153 deletions(-) create mode 100644 netbox/project-static/styles/overrides/_bootstrap.scss delete mode 100644 netbox/templates/inc/user_menu.html create mode 100644 netbox/utilities/htmx.py diff --git a/netbox/core/views.py b/netbox/core/views.py index 400b421d5..b19ab207b 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -25,6 +25,7 @@ from netbox.views import generic from netbox.views.generic.base import BaseObjectView from netbox.views.generic.mixins import TableMixin from utilities.forms import ConfirmationForm +from utilities.htmx import htmx_partial from utilities.query import count_related from utilities.views import ContentTypePermissionRequiredMixin, register_model_view from . import filtersets, forms, tables @@ -320,7 +321,7 @@ class BackgroundTaskListView(TableMixin, BaseRQView): table = self.get_table(data, request, False) # If this is an HTMX request, return only the rendered table HTML - if request.htmx: + if htmx_partial(request): return render(request, 'htmx/table.html', { 'table': table, }) @@ -489,8 +490,8 @@ class WorkerListView(TableMixin, BaseRQView): table = self.get_table(data, request, False) # If this is an HTMX request, return only the rendered table HTML - if request.htmx: - if request.htmx.target != 'object_list': + if htmx_partial(request): + if not request.htmx.target: table.embedded = True # Hide selection checkboxes if 'pk' in table.base_columns: diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 2468e9236..be3937512 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -20,6 +20,7 @@ from netbox.views import generic from netbox.views.generic.mixins import TableMixin from utilities.data import shallow_compare_dict from utilities.forms import ConfirmationForm, get_field_value +from utilities.htmx import htmx_partial from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.query import count_related from utilities.querydict import normalize_querydict @@ -1224,7 +1225,7 @@ class ScriptResultView(TableMixin, generic.ObjectView): } # If this is an HTMX request, return only the result HTML - if request.htmx: + if htmx_partial(request): response = render(request, 'extras/htmx/script_result.html', context) if job.completed or not job.started: response.status_code = 286 diff --git a/netbox/netbox/context_processors.py b/netbox/netbox/context_processors.py index 024ca85b5..ce4f8c45e 100644 --- a/netbox/netbox/context_processors.py +++ b/netbox/netbox/context_processors.py @@ -8,9 +8,11 @@ def settings_and_registry(request): """ Expose Django settings and NetBox registry stores in the template context. Example: {{ settings.DEBUG }} """ + user_preferences = request.user.config if request.user.is_authenticated else {} return { 'settings': django_settings, 'config': get_config(), 'registry': registry, - 'preferences': request.user.config if request.user.is_authenticated else {}, + 'preferences': user_preferences, + 'htmx_navigation': user_preferences.get('ui.htmx_navigation', False) == 'true' } diff --git a/netbox/netbox/preferences.py b/netbox/netbox/preferences.py index 9a6fe490c..1414ea850 100644 --- a/netbox/netbox/preferences.py +++ b/netbox/netbox/preferences.py @@ -23,6 +23,14 @@ PREFERENCES = { ), default='light', ), + 'ui.htmx_navigation': UserPreference( + label=_('HTMX Navigation'), + choices=( + ('', _('Disabled')), + ('true', _('Enabled')), + ), + default=False + ), 'locale.language': UserPreference( label=_('Language'), choices=( diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index ba4e585ad..d609f0a18 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -23,6 +23,7 @@ from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields from utilities.forms.bulk_import import BulkImportForm +from utilities.htmx import htmx_partial from utilities.permissions import get_permission_for_model from utilities.views import GetReturnURLMixin, get_viewname from .base import BaseMultiObjectView @@ -161,8 +162,8 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): table = self.get_table(self.queryset, request, has_bulk_actions) # If this is an HTMX request, return only the rendered table HTML - if request.htmx: - if request.htmx.target != 'object_list': + if htmx_partial(request): + if not request.htmx.target: table.embedded = True # Hide selection checkboxes if 'pk' in table.base_columns: diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 26bd7de65..616867603 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -17,6 +17,7 @@ from extras.signals import clear_events from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, PermissionsViolation from utilities.forms import ConfirmationForm, restrict_form_fields +from utilities.htmx import htmx_partial from utilities.permissions import get_permission_for_model from utilities.querydict import normalize_querydict, prepare_cloned_fields from utilities.views import GetReturnURLMixin, get_viewname @@ -138,7 +139,7 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin): table = self.get_table(table_data, request, has_bulk_actions) # If this is an HTMX request, return only the rendered table HTML - if request.htmx: + if htmx_partial(request): return render(request, 'htmx/table.html', { 'object': instance, 'table': table, @@ -226,7 +227,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): restrict_form_fields(form, request.user) # If this is an HTMX request, return only the rendered form HTML - if request.htmx: + if htmx_partial(request): return render(request, 'htmx/form.html', { 'form': form, }) @@ -482,7 +483,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView): instance = self.alter_object(self.queryset.model(), request) # If this is an HTMX request, return only the rendered form HTML - if request.htmx: + if htmx_partial(request): return render(request, 'htmx/form.html', { 'form': form, }) diff --git a/netbox/netbox/views/misc.py b/netbox/netbox/views/misc.py index fc6c18218..9678b71e3 100644 --- a/netbox/netbox/views/misc.py +++ b/netbox/netbox/views/misc.py @@ -17,6 +17,7 @@ from netbox.forms import SearchForm from netbox.search import LookupTypes from netbox.search.backends import search_backend from netbox.tables import SearchTable +from utilities.htmx import htmx_partial from utilities.paginator import EnhancedPaginator, get_paginate_count __all__ = ( @@ -104,7 +105,7 @@ class SearchView(View): }).configure(table) # If this is an HTMX request, return only the rendered table HTML - if request.htmx: + if htmx_partial(request): return render(request, 'htmx/table.html', { 'table': table, }) diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 95ac3c1b17f988b8b26dab2cf4a149a677d80c2f..501149f582db0a65a0f8fc99df6e284ed30554e4 100644 GIT binary patch delta 80 zcmX?lNAcwy#fBEf7N!>F7M2#)7Pc1lEgUj&Y8fTDIn~9F7M2#)7Pc1lEgUj&+a2OKGz9?5PYSI7 diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 07e7d30b4d515ad3e87bac6c08d820decd426916..97796e4fb1e44abb1ed4da75a89d4e52a2cb6b97 100644 GIT binary patch delta 17589 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 BC1(Ht delta 17694 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 delta 225 zcmbRGL}bcSk%kt=7N!>F7M3ln5A_)hr$5qXRj*Ifad&jgchd26bk1!c68Qv((!k64A=1m5uT2Ykvgf4j_F{j3>T0-Uyx{~6NniI(v}D$oK2lUd`}P& z3^FzXZb&G^5TF>)WT3RaPNsts*nnIh1$KmYjtg8l)XqXDpwqx+2I^#@DtC^^+U{w< HdWI1I%E&*h diff --git a/netbox/project-static/src/htmx.ts b/netbox/project-static/src/htmx.ts index 8d92b60c8..f4092036b 100644 --- a/netbox/project-static/src/htmx.ts +++ b/netbox/project-static/src/htmx.ts @@ -1,11 +1,12 @@ -import { getElements, isTruthy } from './util'; import { initButtons } from './buttons'; +import { initClipboard } from './clipboard' import { initSelects } from './select'; import { initObjectSelector } from './objectSelector'; import { initBootstrap } from './bs'; +import { initMessages } from './messages'; function initDepedencies(): void { - for (const init of [initButtons, initSelects, initObjectSelector, initBootstrap]) { + for (const init of [initButtons, initClipboard, initSelects, initObjectSelector, initBootstrap, initMessages]) { init(); } } @@ -15,16 +16,5 @@ function initDepedencies(): void { * elements. */ export function initHtmx(): void { - for (const element of getElements('[hx-target]')) { - const targetSelector = element.getAttribute('hx-target'); - if (isTruthy(targetSelector)) { - for (const target of getElements(targetSelector)) { - target.addEventListener('htmx:afterSettle', initDepedencies); - } - } - } - - for (const element of getElements('[hx-trigger=load]')) { - element.addEventListener('htmx:afterSettle', initDepedencies); - } + document.addEventListener('htmx:afterSettle', initDepedencies); } diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index 3afaaa9fc..aa7150be7 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -5,6 +5,7 @@ @import '../node_modules/@tabler/core/src/scss/vendor/tom-select'; // Overrides of external libraries +@import 'overrides/bootstrap'; @import 'overrides/tabler'; // Transitional styling to ease migration of templates from NetBox v3.x diff --git a/netbox/project-static/styles/overrides/_bootstrap.scss b/netbox/project-static/styles/overrides/_bootstrap.scss new file mode 100644 index 000000000..f24e18890 --- /dev/null +++ b/netbox/project-static/styles/overrides/_bootstrap.scss @@ -0,0 +1,4 @@ +// Disable smooth scrolling for intra-page links +html { + scroll-behavior: auto !important; +} diff --git a/netbox/templates/account/preferences.html b/netbox/templates/account/preferences.html index c5a93c162..51f807114 100644 --- a/netbox/templates/account/preferences.html +++ b/netbox/templates/account/preferences.html @@ -6,7 +6,7 @@ {% block title %}{% trans "User Preferences" %}{% endblock %} {% block content %} -
+ {% csrf_token %} {# Built-in preferences #} diff --git a/netbox/templates/base/base.html b/netbox/templates/base/base.html index bb35cd3bf..acaff4295 100644 --- a/netbox/templates/base/base.html +++ b/netbox/templates/base/base.html @@ -15,6 +15,7 @@ + {# Page title #} {% block title %}{% trans "Home" %}{% endblock %} | NetBox diff --git a/netbox/templates/base/layout.html b/netbox/templates/base/layout.html index 071396575..931d9c886 100644 --- a/netbox/templates/base/layout.html +++ b/netbox/templates/base/layout.html @@ -58,8 +58,48 @@ Blocks: + {# User menu #} - {% include 'inc/user_menu.html' %} + {% if request.user.is_authenticated %} + + {% else %} + + {% endif %} + {# /User menu #} {# Search box #} @@ -79,6 +119,7 @@ Blocks: {# Page content #}
+
{# Page header #} {% block header %} @@ -122,6 +163,8 @@ Blocks: {% endif %} {# /Bottom banner #} +
+ {# Page footer #}
@@ -173,7 +216,7 @@ Blocks: {# /Footer links #} {# Footer text #} -
    +
diff --git a/netbox/templates/dcim/moduletype/component_templates.html b/netbox/templates/dcim/moduletype/component_templates.html index cf862b2c6..d67c6e8fb 100644 --- a/netbox/templates/dcim/moduletype/component_templates.html +++ b/netbox/templates/dcim/moduletype/component_templates.html @@ -13,13 +13,13 @@