Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #14736: Enable HTMX navigation globally #15158

Merged
merged 26 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6d06152
Enable HTMX boosting
jeremystretch Feb 2, 2024
a742f2e
Refactor HTMX properties for tables
jeremystretch Feb 2, 2024
4f3f214
Fix dashboard object list widget
jeremystretch Feb 14, 2024
fa4f489
Disable scrolling to page content
jeremystretch Feb 14, 2024
a78fb80
Fix initialization of TomSelect dropdowns after HTMX loading
jeremystretch Feb 14, 2024
881b994
Replace formaction properties with hx-post
jeremystretch Feb 16, 2024
d224f73
Fix quick search field on object list view
jeremystretch Feb 16, 2024
648a14a
Reinitialize copy-to-clipboard buttons upon HTMX load
jeremystretch Feb 16, 2024
c377d83
Merge branch 'feature' into 14736-htmx
jeremystretch Feb 29, 2024
236c561
Disable scrolling effect for intra-page navigation
jeremystretch Feb 29, 2024
e735be7
Merge branch 'feature' into 14736-htmx
jeremystretch Mar 18, 2024
7d86d09
Introduce user preference for toggling HTMX navigation
jeremystretch Mar 18, 2024
4d85e9c
Enable HTMX navigation only when selected by user
jeremystretch Mar 18, 2024
8ba5963
Pass htmx_navigation context
jeremystretch Mar 18, 2024
3061e16
Merge branch 'feature' into 14736-htmx
jeremystretch Mar 25, 2024
a36467c
Fix display of confirmation form when deleting an object
jeremystretch Mar 25, 2024
ebab17d
Disable HTMX boosting for rack elevation SVG downloads
jeremystretch Mar 25, 2024
16ceaef
Fix dyanmic form rendering
jeremystretch Mar 25, 2024
e37c943
Introduce htmx_boost template tag; enable HTMX for user menu
jeremystretch Mar 25, 2024
1af5799
Use out-of-band sap to update footer stamp
jeremystretch Mar 25, 2024
4c92397
Fix display of toasts after form submission
jeremystretch Mar 25, 2024
ec364c6
Fix user preference selection
jeremystretch Mar 25, 2024
5fcc0fc
Misc cleanup
jeremystretch Mar 25, 2024
6c540e5
Rename render_partial() to htmx_partial()
jeremystretch Mar 27, 2024
41f21d3
Add docstring to htmx_boost template tag
jeremystretch Mar 27, 2024
c7a63e0
Disable HTMX for user preferences form to force a full page refresh o…
jeremystretch Mar 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions netbox/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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
Expand Down Expand Up @@ -320,7 +321,7 @@ def get(self, request, queue_index, status):
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,
})
Expand Down Expand Up @@ -489,8 +490,8 @@ def get(self, request, queue_index):
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:
Expand Down
3 changes: 2 additions & 1 deletion netbox/extras/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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
Expand Down Expand Up @@ -1224,7 +1225,7 @@ def get(self, request, **kwargs):
}

# 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
Expand Down
4 changes: 3 additions & 1 deletion netbox/netbox/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
8 changes: 8 additions & 0 deletions netbox/netbox/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ def get_page_lengths():
),
default='light',
),
'ui.htmx_navigation': UserPreference(
label=_('HTMX Navigation'),
choices=(
('', _('Disabled')),
('true', _('Enabled')),
),
default=False
),
'locale.language': UserPreference(
label=_('Language'),
choices=(
Expand Down
5 changes: 3 additions & 2 deletions netbox/netbox/views/generic/bulk_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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
Expand Down Expand Up @@ -161,8 +162,8 @@ def get(self, request):
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:
Expand Down
7 changes: 4 additions & 3 deletions netbox/netbox/views/generic/object_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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
Expand Down Expand Up @@ -138,7 +139,7 @@ def get(self, request, *args, **kwargs):
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,
Expand Down Expand Up @@ -226,7 +227,7 @@ def get(self, request, *args, **kwargs):
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,
})
Expand Down Expand Up @@ -482,7 +483,7 @@ def get(self, request):
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,
})
Expand Down
3 changes: 2 additions & 1 deletion netbox/netbox/views/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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__ = (
Expand Down Expand Up @@ -104,7 +105,7 @@ def get(self, request):
}).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,
})
Expand Down
2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.css

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions netbox/project-static/dist/netbox.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.js.map

Large diffs are not rendered by default.

18 changes: 4 additions & 14 deletions netbox/project-static/src/htmx.ts
Original file line number Diff line number Diff line change
@@ -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();
jeremystretch marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand All @@ -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);
}
1 change: 1 addition & 0 deletions netbox/project-static/styles/netbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions netbox/project-static/styles/overrides/_bootstrap.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Disable smooth scrolling for intra-page links
html {
scroll-behavior: auto !important;
}
2 changes: 1 addition & 1 deletion netbox/templates/account/preferences.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% block title %}{% trans "User Preferences" %}{% endblock %}

{% block content %}
<form method="post" action="" id="preferences-update">
<form method="post" action="" hx-disable="true" id="preferences-update">
{% csrf_token %}

{# Built-in preferences #}
Expand Down
1 change: 1 addition & 0 deletions netbox/templates/base/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, viewport-fit=cover" />
<meta name="htmx-config" content='{"scrollBehavior": "auto"}'>

{# Page title #}
<title>{% block title %}{% trans "Home" %}{% endblock %} | NetBox</title>
Expand Down
47 changes: 45 additions & 2 deletions netbox/templates/base/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,48 @@ <h1 class="navbar-brand navbar-brand-autodark">
<i class="mdi mdi-lightbulb-on"></i>
</button>
</div>

{# User menu #}
{% include 'inc/user_menu.html' %}
{% if request.user.is_authenticated %}
<div class="nav-item dropdown">
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
<div class="d-xl-block ps-2">
<div>{{ request.user }}</div>
<div class="mt-1 small text-secondary">{% if request.user.is_staff %}Staff{% else %}User{% endif %}</div>
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow" {% htmx_boost %}>
{% if config.DJANGO_ADMIN_ENABLED and request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> {% trans "Django Admin" %}
</a>
{% endif %}
<a href="{% url 'account:profile' %}" class="dropdown-item">
<i class="mdi mdi-account"></i> {% trans "Profile" %}
</a>
<a href="{% url 'account:bookmarks' %}" class="dropdown-item">
<i class="mdi mdi-bookmark"></i> {% trans "Bookmarks" %}
</a>
<a href="{% url 'account:preferences' %}" class="dropdown-item">
<i class="mdi mdi-wrench"></i> {% trans "Preferences" %}
</a>
<a href="{% url 'account:usertoken_list' %}" class="dropdown-item">
<i class="mdi mdi-key"></i> {% trans "API Tokens" %}
</a>
<div class="dropdown-divider"></div>
<a href="{% url 'logout' %}" class="dropdown-item">
<i class="mdi mdi-logout-variant"></i> {% trans "Log Out" %}
</a>
</div>
</div>
{% else %}
<div class="btn-group ps-2">
<a class="btn btn-primary" type="button" href="{% url 'login' %}?next={{ request.path }}">
<i class="mdi mdi-login-variant"></i> {% trans "Log In" %}
</a>
</div>
{% endif %}
{# /User menu #}
</div>

{# Search box #}
Expand All @@ -79,6 +119,7 @@ <h1 class="navbar-brand navbar-brand-autodark">

{# Page content #}
<div class="page-wrapper">
<div id="page-content" {% htmx_boost %}>

{# Page header #}
{% block header %}
Expand Down Expand Up @@ -122,6 +163,8 @@ <h1 class="navbar-brand navbar-brand-autodark">
{% endif %}
{# /Bottom banner #}

</div>

{# Page footer #}
<footer class="footer footer-transparent d-print-none py-2">
<div class="container-fluid d-flex justify-content-between align-items-center">
Expand Down Expand Up @@ -173,7 +216,7 @@ <h1 class="navbar-brand navbar-brand-autodark">
{# /Footer links #}

{# Footer text #}
<ul class="list-inline list-inline-dots mb-0">
<ul class="list-inline list-inline-dots mb-0" id="footer-stamp" hx-swap-oob="true">
<li class="list-inline-item">
{% annotated_now %} {% now 'T' %}
</li>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/component_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{% endif %}
{% if 'bulk_rename' in actions %}
{% with bulk_rename_view=model|validated_viewname:"bulk_rename" %}
<button type="submit" name="_rename" formaction="{% url bulk_rename_view %}" class="btn btn-outline-warning">
<button type="submit" name="_rename" {% formaction %}="{% url bulk_rename_view %}" class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename Selected" %}
</button>
{% endwith %}
Expand Down
4 changes: 2 additions & 2 deletions netbox/templates/dcim/device/components_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
{% if 'bulk_edit' in actions and bulk_edit_view %}
<button type="submit" name="_edit"
formaction="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
{% formaction %}="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
class="btn btn-warning">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
</button>
Expand All @@ -14,7 +14,7 @@
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
{% if 'bulk_rename' in actions and bulk_rename_view %}
<button type="submit" name="_rename"
formaction="{% url bulk_rename_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/consoleports.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/consoleserverports.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/frontports.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/interfaces.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/poweroutlets.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/powerports.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device/rearports.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
Expand Down
Loading
Loading