mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-27 15:47:46 -06:00
Compare commits
56 Commits
02496-max-
...
9583-add_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49bcf57834 | ||
|
|
01cb7beb68 | ||
|
|
762ebe23f9 | ||
|
|
714495486b | ||
|
|
e7649f0cc2 | ||
|
|
434cf37988 | ||
|
|
451a1f498d | ||
|
|
694fcd3a79 | ||
|
|
7e5d8eaf05 | ||
|
|
1958965c36 | ||
|
|
e12ae3acde | ||
|
|
a28871296c | ||
|
|
ee83895e2a | ||
|
|
764c676ffc | ||
|
|
92f6f426b3 | ||
|
|
4381ea9265 | ||
|
|
92c3bbc9db | ||
|
|
8d65973c82 | ||
|
|
882aa30043 | ||
|
|
f2c74db761 | ||
|
|
852535e3d4 | ||
|
|
012e815f3b | ||
|
|
d6ab7b7378 | ||
|
|
77cb8ac817 | ||
|
|
b12ce97474 | ||
|
|
0ad1db9912 | ||
|
|
f1306783dc | ||
|
|
30e653197e | ||
|
|
fd38255d31 | ||
|
|
5830ae9e9a | ||
|
|
479c69bd2d | ||
|
|
1c995fa5f9 | ||
|
|
35cff12974 | ||
|
|
a9aa0cbaa0 | ||
|
|
8ad79a64ae | ||
|
|
a422a3cd98 | ||
|
|
8a7df0b98d | ||
|
|
f257f4aad4 | ||
|
|
25a4e9448c | ||
|
|
77bfd620c3 | ||
|
|
84151cbc1a | ||
|
|
0309796bbb | ||
|
|
f81f76f862 | ||
|
|
c3f1a9601c | ||
|
|
4ae6683abc | ||
|
|
06c1aff04f | ||
|
|
5f69666b7b | ||
|
|
b54cfd6ba9 | ||
|
|
3243ebd1dd | ||
|
|
03f67f373f | ||
|
|
50557c0f9d | ||
|
|
cc423f5071 | ||
|
|
664a0eba6d | ||
|
|
f7294f7087 | ||
|
|
e762755e80 | ||
|
|
4c39516253 |
@@ -44,6 +44,7 @@ class BaseTable(tables.Table):
|
||||
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
||||
"""
|
||||
exempt_columns = ()
|
||||
filterset_form = None
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
|
||||
@@ -170,6 +170,20 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
||||
# Render the objects table
|
||||
table = self.get_table(self.queryset, request, has_bulk_actions)
|
||||
|
||||
# Check for filterset_form(s) on this view and/or the table, if a form exists:
|
||||
# * If both exist, initialize both
|
||||
# * If a filterset form for the table exists, only initialize the table filterset_form
|
||||
# * If a filterset form exists for the view, initialize the filterset form
|
||||
# * Apply to the table for use by the table and initialize a separate instance of the form for use by the table
|
||||
# column filters
|
||||
# * Otherwise set to None
|
||||
if self.filterset_form:
|
||||
filterset_form = self.filterset_form(request.GET)
|
||||
table.filterset_form = self.filterset_form(request.GET)
|
||||
else:
|
||||
filterset_form = None
|
||||
table.filterset_form = None
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if htmx_partial(request):
|
||||
if request.GET.get('embedded', False):
|
||||
@@ -179,15 +193,15 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
||||
table.columns.hide('pk')
|
||||
return render(request, 'htmx/table.html', {
|
||||
'table': table,
|
||||
'filter_form': filterset_form,
|
||||
'model': model,
|
||||
'actions': actions,
|
||||
})
|
||||
|
||||
context = {
|
||||
'model': model,
|
||||
'table': table,
|
||||
'actions': actions,
|
||||
'filter_form': self.filterset_form(request.GET) if self.filterset_form else None,
|
||||
'filter_form': filterset_form,
|
||||
'prerequisite_model': get_prerequisite_model(self.queryset),
|
||||
**self.get_extra_context(request),
|
||||
}
|
||||
|
||||
2
netbox/project-static/dist/netbox.css
vendored
2
netbox/project-static/dist/netbox.css
vendored
File diff suppressed because one or more lines are too long
@@ -29,6 +29,23 @@ span.color-label {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// Override bootstrap "dropdown" positioning and display for column filters
|
||||
.column-filter {
|
||||
position: static;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
// Override mdi font-size to adjust filter icon size
|
||||
.column-filter.dropdown > .dropdown-toggle > .mdi-filter-settings {
|
||||
font-size: .625rem;
|
||||
}
|
||||
|
||||
.column-filter.dropdown > .dropdown-menu {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.column-filter.dropdown-toggle:after { content: none }
|
||||
|
||||
// NetBox edition text
|
||||
.netbox-edition {
|
||||
letter-spacing: .15rem;
|
||||
|
||||
@@ -66,9 +66,8 @@ Context:
|
||||
|
||||
{# Object list tab #}
|
||||
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
||||
|
||||
{# Applied filters #}
|
||||
{% if filter_form %}
|
||||
{% if not request.htmx %}
|
||||
{# Applied filters #}
|
||||
{% applied_filters model filter_form request.GET %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
{% load buttons %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% if request.htmx %}
|
||||
{# OOB Swaps to update various components #}
|
||||
{% applied_filters model filter_form request.GET %}
|
||||
{% endif %}
|
||||
|
||||
<div class="htmx-container table-responsive">
|
||||
{% with preferences|get_key:"pagination.placement" as paginator_placement %}
|
||||
{% if paginator_placement == 'top' or paginator_placement == 'both' %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="col-auto d-print-none">
|
||||
<div class="input-group input-group-flat me-2 quicksearch" hx-disinherit="hx-select hx-swap">
|
||||
<input type="search" results="5" name="q" id="quicksearch" class="form-control" placeholder="{% trans "Quick search" %}"
|
||||
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
|
||||
hx-get="" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
|
||||
<span class="input-group-text py-1">
|
||||
<a href="#" id="quicksearch_clear" class="invisible text-secondary"><i class="mdi mdi-close-circle"></i></a>
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% load django_tables2 %}
|
||||
{% load form_helpers %}
|
||||
{% load i18n %}
|
||||
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %} hx-disinherit="hx-target hx-select" hx-swap="outerHTML">
|
||||
{% if table.show_header %}
|
||||
@@ -15,12 +16,15 @@
|
||||
<a href="#"
|
||||
hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field='' %}"
|
||||
class="text-danger"
|
||||
{% if not table.embedded %}hx-push-url="true"{% endif %}
|
||||
title="{% trans "Clear ordering" %}"
|
||||
><i class="mdi mdi-close"></i></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% render_table_filter_field column.name table request%}
|
||||
<a href="#"
|
||||
hx-get="{{ table.htmx_url }}{% 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 %}"
|
||||
{% if not table.embedded %}hx-push-url="true"{% endif %}
|
||||
>{{ column.header }}</a>
|
||||
</th>
|
||||
{% else %}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{% load form_helpers %}
|
||||
{% if field %}
|
||||
<div class="column-filter dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="outside"><i class="mdi mdi-filter-settings"></i></a>
|
||||
<div class="dropdown-menu">
|
||||
<div class="px-3 py-3">
|
||||
{% include "form_helpers/render_field.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,20 +1,22 @@
|
||||
{% load i18n %}
|
||||
{% if applied_filters %}
|
||||
<div class="mb-3">
|
||||
{% for filter in applied_filters %}
|
||||
<a href="{{ filter.link_url }}" class="badge rounded-pill text-bg-primary text-decoration-none me-1">
|
||||
<i class="mdi mdi-close"></i> {{ filter.link_text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if applied_filters|length > 1 %}
|
||||
<a href="?" class="badge rounded-pill text-bg-danger text-decoration-none me-1">
|
||||
<i class="mdi mdi-tag-off"></i> {% trans "Clear all" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if save_link %}
|
||||
<a href="{{ save_link }}" class="badge rounded-pill text-bg-success text-decoration-none me-1">
|
||||
<i class="mdi mdi-content-save"></i> {% trans "Save" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="applied_filters_pane" hx-swap-oob="true">
|
||||
{% if applied_filters %}
|
||||
<div class="mb-3">
|
||||
{% for filter in applied_filters %}
|
||||
<a href="{{ filter.link_url }}" class="badge rounded-pill text-bg-primary text-decoration-none me-1">
|
||||
<i class="mdi mdi-close"></i> {{ filter.link_text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if applied_filters|length > 1 %}
|
||||
<a href="?" class="badge rounded-pill text-bg-danger text-decoration-none me-1">
|
||||
<i class="mdi mdi-tag-off"></i> {% trans "Clear all" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if save_link %}
|
||||
<a href="{{ save_link }}" class="badge rounded-pill text-bg-success text-decoration-none me-1">
|
||||
<i class="mdi mdi-content-save"></i> {% trans "Save" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -7,10 +7,12 @@ __all__ = (
|
||||
'render_custom_fields',
|
||||
'render_errors',
|
||||
'render_field',
|
||||
'render_table_filter_field',
|
||||
'render_form',
|
||||
'widget_type',
|
||||
)
|
||||
|
||||
from utilities.templatetags.helpers import querystring
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -30,6 +32,11 @@ def getfield(form, fieldname):
|
||||
return None
|
||||
|
||||
|
||||
@register.filter()
|
||||
def get_filter_field(form, fieldname):
|
||||
return getfield(form, f'{fieldname}') or getfield(form, f'{fieldname}_id')
|
||||
|
||||
|
||||
@register.filter(name='widget_type')
|
||||
def widget_type(field):
|
||||
"""
|
||||
@@ -47,6 +54,7 @@ def widget_type(field):
|
||||
# Inclusion tags
|
||||
#
|
||||
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_fieldset.html')
|
||||
def render_fieldset(form, fieldset):
|
||||
"""
|
||||
@@ -109,6 +117,7 @@ def render_field(field, bulk_nullable=False, label=None):
|
||||
"""
|
||||
Render a single form field from template
|
||||
"""
|
||||
|
||||
return {
|
||||
'field': field,
|
||||
'label': label or field.label,
|
||||
@@ -116,6 +125,49 @@ def render_field(field, bulk_nullable=False, label=None):
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_table_filter_field.html')
|
||||
def render_table_filter_field(fieldname, table, request):
|
||||
"""
|
||||
Render a single form field for table column filters from template
|
||||
"""
|
||||
url = ""
|
||||
field = None
|
||||
|
||||
# Does this table have a filterset form?
|
||||
if hasattr(table, 'filterset_form') and table.filterset_form is not None:
|
||||
# Get the filterset field
|
||||
field = get_filter_field(table.filterset_form, fieldname)
|
||||
|
||||
# Return if no filterset field
|
||||
if field is None:
|
||||
return {}
|
||||
|
||||
# Handle filter forms
|
||||
if table:
|
||||
# Build kwargs for querystring function
|
||||
kwargs = {field.name: None}
|
||||
# Build request url
|
||||
if request and table.htmx_url:
|
||||
url = table.htmx_url + querystring(request, **kwargs)
|
||||
elif request:
|
||||
url = querystring(request, **kwargs)
|
||||
|
||||
# Set HTMX args
|
||||
if hasattr(field.field, 'widget'):
|
||||
field.field.widget.attrs.update({
|
||||
'id': f'table_filter_id_{field.name}',
|
||||
'hx-get': url if url else '#',
|
||||
'hx-push-url': "true",
|
||||
'hx-trigger': 'hidden.bs.dropdown from:closest .dropdown'
|
||||
})
|
||||
|
||||
return {
|
||||
'field': field,
|
||||
'label': None,
|
||||
'bulk_nullable': False,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_custom_fields.html')
|
||||
def render_custom_fields(form):
|
||||
"""
|
||||
|
||||
@@ -270,6 +270,8 @@ def applied_filters(context, model, form, query_params):
|
||||
Display the active filters for a given filter form.
|
||||
"""
|
||||
user = context['request'].user
|
||||
if not form:
|
||||
return
|
||||
form.is_valid() # Ensure cleaned_data has been set
|
||||
|
||||
applied_filters = []
|
||||
@@ -304,6 +306,7 @@ def applied_filters(context, model, form, query_params):
|
||||
save_link = f"{url}?object_types={object_type}¶meters={quote(parameters)}"
|
||||
|
||||
return {
|
||||
'request': context['request'],
|
||||
'applied_filters': applied_filters,
|
||||
'save_link': save_link,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user