Use HTMX for search page
This commit is contained in:
parent
53123ad93e
commit
fb78f3f993
4 changed files with 63 additions and 52 deletions
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
9
website/search/templates/search/search_results.html
Normal file
9
website/search/templates/search/search_results.html
Normal 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 %}
|
Loading…
Reference in a new issue