Use HTMX for search page

This commit is contained in:
Jake Howard 2022-07-31 15:28:48 +01:00
parent 53123ad93e
commit fb78f3f993
Signed by: jake
GPG key ID: 57AFB45680EDD477
4 changed files with 63 additions and 52 deletions

View file

@ -40,6 +40,7 @@
<script async defer type="text/javascript" src="{% static 'js/base.js' %}"></script> <script async defer type="text/javascript" src="{% static 'js/base.js' %}"></script>
<script async defer type="text/javascript" src="{% static 'contrib/htmx/htmx.min.js' %}"></script>
{% block darkmode %} {% block darkmode %}
<script type="text/javascript" src="{% static 'js/darkreader.js' %}"></script> <script type="text/javascript" src="{% static 'js/darkreader.js' %}"></script>

View file

@ -1,7 +1,10 @@
from django.core.paginator import EmptyPage, Paginator from django.core.paginator import EmptyPage, Paginator
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
from django.utils.functional import cached_property from django.utils.functional import cached_property
from rest_framework import serializers from rest_framework import serializers
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.models import Page from wagtail.models import Page
from wagtail.query import PageQuerySet from wagtail.query import PageQuerySet
from wagtail.search.models import Query from wagtail.search.models import Query
@ -12,7 +15,7 @@ from website.common.utils import TocEntry
from website.home.models import HomePage from website.home.models import HomePage
class SearchPage(BaseContentMixin, BasePage): # type: ignore[misc] class SearchPage(BaseContentMixin, RoutablePageMixin, BasePage): # type: ignore[misc]
max_count = 1 max_count = 1
subpage_types: list = [] subpage_types: list = []
parent_page_types = ["home.HomePage"] parent_page_types = ["home.HomePage"]
@ -40,40 +43,49 @@ class SearchPage(BaseContentMixin, BasePage): # type: ignore[misc]
def get_context(self, request: HttpRequest) -> dict: def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request) context = super().get_context(request)
context["search_url"] = self.reverse_subpage("results")
return context
@route(r"^results/$")
def results(self, request: HttpRequest) -> HttpResponse:
if not request.GET.get("q", None):
return HttpResponse()
serializer = self.SearchParamsSerializer(data=request.GET) serializer = self.SearchParamsSerializer(data=request.GET)
if serializer.is_valid(): if not serializer.is_valid():
search_query = serializer.validated_data["q"] return HttpResponseBadRequest(serializer.errors)
context["search_query"] = search_query
filters, query = parse_query_string(search_query)
Query.get(search_query).add_hit()
pages = self.get_search_pages().search(query)
paginator = Paginator(pages, self.PAGE_SIZE) search_query = serializer.validated_data["q"]
context["paginator"] = paginator page_num = serializer.validated_data["page"]
page_num = serializer.validated_data["page"]
context["page_num"] = page_num
try:
results = paginator.page(page_num)
# HACK: Search results aren't a queryset, so we can't call `.specific` on it. This forces it to one as efficiently as possible context = {
if not isinstance(results.object_list, PageQuerySet): **self.get_context(request),
results.object_list = Page.objects.filter( "search_query": search_query,
id__in=list( "page_num": page_num,
results.object_list.get_queryset().values_list( }
"id", flat=True
)
)
).specific()
except EmptyPage:
results = []
context["results"] = results filters, query = parse_query_string(search_query)
else: Query.get(search_query).add_hit()
if "q" in request.GET: pages = self.get_search_pages().search(query)
context["invalid_search"] = True
else:
context["initial"] = True
return context paginator = Paginator(pages, self.PAGE_SIZE)
context["paginator"] = paginator
try:
results = paginator.page(page_num)
# HACK: Search results aren't a queryset, so we can't call `.specific` on it. This forces it to one as efficiently as possible
if not isinstance(results.object_list, PageQuerySet):
results.object_list = Page.objects.filter(
id__in=list(
results.object_list.get_queryset().values_list("id", flat=True)
)
).specific()
except EmptyPage:
results = []
context["results"] = results
return render(request, "search/search_results.html", context)

View file

@ -11,28 +11,17 @@
{% endif %} {% endif %}
<section class="container"> <section class="container">
<form> <input
<input type="search" name="q" placeholder="Search" value="{{ search_query }}"/> type="search"
</form> name="q"
</section> placeholder="Search"
hx-get="{{ search_url }}"
<section class="container"> hx-trigger="keyup changed delay:300ms, search"
{% if initial %} hx-target="#search-results"
<p>Enter search terms</p> autocomplete="off"
{% elif invalid_search %} >
<p>Invalid search</p>
{% elif results|length == 0 %}
{% if page_num > paginator.num_pages %}
<p>There aren't {{ page_num }} page - only {{ paginator.num_pages }}.</p>
{% else %}
<p>No results</p>
{% endif %}
{% else %}
{% for page in results %}
{% include "common/listing-item.html" %}
{% endfor %}
{% endif %}
</section> </section>
<section class="container" id="search-results"></section>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,9 @@
{% load wagtailadmin_tags %}
{% for page in results %}
{% include "common/listing-item.html" %}
{% endfor %}
{% if results.has_next %}
<span hx-get="{{ search_url }}{% querystring page=results.next_page_number %}" hx-trigger="revealed" hx-swap="outerHTML"></span>
{% endif %}