Introduce SearchTable and enable HTMX for results

This commit is contained in:
jeremystretch 2022-10-18 08:49:08 -04:00
parent f69a13e05b
commit 7cc85a609d
5 changed files with 56 additions and 49 deletions

View File

@ -17,7 +17,14 @@ LOOKUP_CHOICES = (
class SearchForm(BootstrapMixin, forms.Form): class SearchForm(BootstrapMixin, forms.Form):
q = forms.CharField( q = forms.CharField(
label='Search' label='Search',
widget=forms.TextInput(
attrs={
'hx-get': '',
'hx-target': '#search_results',
'hx-trigger': 'keyup[target.value.length >= 3] changed delay:500ms',
}
)
) )
obj_types = forms.MultipleChoiceField( obj_types = forms.MultipleChoiceField(
choices=[], choices=[],

View File

@ -192,7 +192,7 @@ class CachedValueSearchBackend(SearchBackend):
# Wrap the base query to return only the lowest-weight result for each object # Wrap the base query to return only the lowest-weight result for each object
# Hat-tip to https://blog.oyam.dev/django-filter-by-window-function/ for the solution # Hat-tip to https://blog.oyam.dev/django-filter-by-window-function/ for the solution
sql, params = queryset.query.sql_with_params() sql, params = queryset.query.sql_with_params()
results = CachedValue.objects.prefetch_related(prefetch).raw( results = CachedValue.objects.prefetch_related(prefetch, 'object_type').raw(
f"SELECT * FROM ({sql}) t WHERE row_number = 1", f"SELECT * FROM ({sql}) t WHERE row_number = 1",
params params
) )

View File

@ -4,6 +4,7 @@ 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.utils.translation import gettext as _
from django_tables2.data import TableQuerysetData from django_tables2.data import TableQuerysetData
from extras.models import CustomField, CustomLink from extras.models import CustomField, CustomLink
@ -14,6 +15,7 @@ from utilities.paginator import EnhancedPaginator, get_paginate_count
__all__ = ( __all__ = (
'BaseTable', 'BaseTable',
'NetBoxTable', 'NetBoxTable',
'SearchTable',
) )
@ -192,3 +194,18 @@ class NetBoxTable(BaseTable):
]) ])
super().__init__(*args, extra_columns=extra_columns, **kwargs) super().__init__(*args, extra_columns=extra_columns, **kwargs)
class SearchTable(tables.Table):
object_type = columns.ContentTypeColumn()
object = tables.Column(
linkify=True
)
field = tables.Column()
value = tables.Column()
class Meta:
attrs = {
'class': 'table table-hover object-list',
}
empty_text = _('No results found')

View File

@ -8,7 +8,6 @@ from django.http import HttpResponseServerError
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.template import loader from django.template import loader
from django.template.exceptions import TemplateDoesNotExist from django.template.exceptions import TemplateDoesNotExist
from django.urls import reverse
from django.views.decorators.csrf import requires_csrf_token from django.views.decorators.csrf import requires_csrf_token
from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found
from django.views.generic import View from django.views.generic import View
@ -23,9 +22,10 @@ from extras.models import ObjectChange
from extras.tables import ObjectChangeTable from extras.tables import ObjectChangeTable
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF
from netbox.forms import SearchForm from netbox.forms import SearchForm
from netbox.search import get_registry
from netbox.search.backends import search_backend from netbox.search.backends import search_backend
from netbox.tables import SearchTable
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.htmx import is_htmx
from virtualization.models import Cluster, VirtualMachine from virtualization.models import Cluster, VirtualMachine
from wireless.models import WirelessLAN, WirelessLink from wireless.models import WirelessLAN, WirelessLink
@ -170,9 +170,17 @@ class SearchView(View):
lookup=form.cleaned_data['lookup'] lookup=form.cleaned_data['lookup']
) )
table = SearchTable(results)
# If this is an HTMX request, return only the rendered table HTML
if is_htmx(request):
return render(request, 'htmx/table.html', {
'table': table,
})
return render(request, 'search.html', { return render(request, 'search.html', {
'form': form, 'form': form,
'results': results, 'table': table,
}) })

View File

@ -15,49 +15,24 @@
</ul> </ul>
{% endblock tabs %} {% endblock tabs %}
{% block content-wrapper %} {% block content %}
<div class="row px-3"> <div class="row px-3">
<div class="col col-6 offset-3 py-3"> <div class="col col-6 offset-3 py-3">
<form action="{% url 'search' %}" method="get" class="form form-horizontal"> <form action="{% url 'search' %}" method="get" class="form form-horizontal">
{% render_form form %} {% render_form form %}
<div class="text-end"> <div class="text-end">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<span class="mdi mdi-magnify" aria-hidden="true"></span> Search <span class="mdi mdi-magnify" aria-hidden="true"></span> Search
</button> </button>
</div> </div>
</form> </form>
</div>
</div>
<div class="row px-3">
<div class="card">
<div class="card-body" id="search_results">
{% include 'htmx/table.html' %}
</div> </div>
</div> </div>
{% if request.GET.q %} </div>
{% if results %} {% endblock content %}
<div class="row px-3">
<div class="col">
<div class="card">
<div class="card-body table-responsive">
<table class="table table-hover">
<tr>
<th>Type</th>
<th>Object</th>
<th>Field</th>
<th>Value</th>
</tr>
{% for result in results %}
<tr>
<td>{{ result.object|meta:"verbose_name"|bettertitle }}</td>
<td>
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a>
</td>
<td>{{ result.field|placeholder }}</td>
<td>{{ result.value|placeholder }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{% else %}
<h3 class="text-muted text-center">No results found</h3>
{% endif %}
{% endif %}
{% endblock content-wrapper %}