Add opensearch description file
Not _that_ opensearch
This commit is contained in:
parent
10e8950aef
commit
5d50907ed2
7 changed files with 134 additions and 6 deletions
|
@ -14,6 +14,8 @@
|
|||
|
||||
{% block extra_head %}{% endblock %}
|
||||
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="{% url 'opensearch' %}" title="Orange search" />
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" href="{% url 'feed' %}" />
|
||||
|
||||
<link rel="me" href="https://{{ ACTIVITYPUB_HOST }}/@jake" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.core.paginator import EmptyPage, Paginator
|
||||
from django.db import models
|
||||
from django.http.request import HttpRequest
|
||||
from django.http.response import Http404, HttpResponse, HttpResponseBadRequest
|
||||
from django.template.response import TemplateResponse
|
||||
|
@ -43,6 +44,13 @@ class SearchPage(RoutablePageMixin, BaseContentPage):
|
|||
context["SEO_INDEX"] = False
|
||||
return context
|
||||
|
||||
def get_listing_pages(self) -> models.QuerySet:
|
||||
return (
|
||||
Page.objects.live()
|
||||
.public()
|
||||
.not_type(self.__class__, *self.EXCLUDED_PAGE_TYPES)
|
||||
)
|
||||
|
||||
@route(r"^results/$")
|
||||
@method_decorator(require_GET)
|
||||
def results(self, request: HttpRequest) -> HttpResponse:
|
||||
|
@ -68,12 +76,7 @@ class SearchPage(RoutablePageMixin, BaseContentPage):
|
|||
}
|
||||
|
||||
filters, query = parse_query_string(search_query)
|
||||
pages = (
|
||||
Page.objects.live()
|
||||
.public()
|
||||
.not_type(self.__class__, *self.EXCLUDED_PAGE_TYPES)
|
||||
.search(query, order_by_relevance=True)
|
||||
)
|
||||
pages = self.get_listing_pages().search(query, order_by_relevance=True)
|
||||
|
||||
paginator = Paginator(pages, self.PAGE_SIZE)
|
||||
context["paginator"] = paginator
|
||||
|
|
11
website/search/templates/search/opensearch.xml
Normal file
11
website/search/templates/search/opensearch.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>{{ site_title }}</ShortName>
|
||||
<Description>{{ site_title }}</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
{% if favicon_url %}
|
||||
<Image type="image/png">{{ favicon_url }}</Image>
|
||||
{% endif %}
|
||||
<Url type="text/html" template="{{ search_page_url }}?q={searchTerms}"/>
|
||||
<Url type="application/x-suggestions+json" template="{{ search_suggestions_url }}?q={searchTerms}"/>
|
||||
</OpenSearchDescription>
|
|
@ -1,5 +1,6 @@
|
|||
from bs4 import BeautifulSoup
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from website.common.factories import ContentPageFactory
|
||||
from website.home.models import HomePage
|
||||
|
@ -127,3 +128,30 @@ class SearchPageResultsTestCase(TestCase):
|
|||
with self.assertNumQueries(7):
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
|
||||
class OpenSearchTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
cls.home_page = HomePage.objects.get()
|
||||
cls.page = SearchPageFactory(parent=cls.home_page)
|
||||
|
||||
for i in range(6):
|
||||
ContentPageFactory(parent=cls.home_page, title=f"Post {i}")
|
||||
|
||||
def test_opensearch_description(self) -> None:
|
||||
with self.assertNumQueries(11):
|
||||
response = self.client.get(reverse("opensearch"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.assertContains(response, self.page.get_url())
|
||||
self.assertContains(response, reverse("opensearch-suggestions"))
|
||||
|
||||
def test_opensearch_suggestions(self) -> None:
|
||||
with self.assertNumQueries(4):
|
||||
response = self.client.get(reverse("opensearch-suggestions"), {"q": "post"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.json()
|
||||
self.assertEqual(data[0], "post")
|
||||
self.assertEqual(data[1], [f"Post {i}" for i in range(5)])
|
||||
|
|
12
website/search/urls.py
Normal file
12
website/search/urls.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("opensearch.xml", views.OpenSearchView.as_view(), name="opensearch"),
|
||||
path(
|
||||
"opensearch-suggestions/",
|
||||
views.OpenSearchSuggestionsView.as_view(),
|
||||
name="opensearch-suggestions",
|
||||
),
|
||||
]
|
71
website/search/views.py
Normal file
71
website/search/views.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
from django.http import HttpRequest, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.generic import TemplateView, View
|
||||
from wagtail.search.utils import parse_query_string
|
||||
from wagtail_favicon.models import FaviconSettings
|
||||
from wagtail_favicon.utils import get_rendition_url
|
||||
|
||||
from website.common.utils import get_site_title
|
||||
from website.contrib.singleton_page.utils import SingletonPageCache
|
||||
|
||||
from .models import SearchPage
|
||||
from .serializers import SearchParamsSerializer
|
||||
|
||||
|
||||
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
|
||||
class OpenSearchView(TemplateView):
|
||||
template_name = "search/opensearch.xml"
|
||||
content_type = "application/xml"
|
||||
|
||||
def get_context_data(self, **kwargs: dict) -> dict:
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
favicon_settings = FaviconSettings.for_request(self.request)
|
||||
|
||||
if favicon_settings.base_favicon_image_id:
|
||||
context["favicon_url"] = self.request.build_absolute_uri(
|
||||
get_rendition_url(
|
||||
favicon_settings.base_favicon_image, "fill-100|format-png"
|
||||
)
|
||||
)
|
||||
|
||||
context["search_page_url"] = self.request.build_absolute_uri(
|
||||
SingletonPageCache.get_url(SearchPage, self.request)
|
||||
)
|
||||
context["search_suggestions_url"] = self.request.build_absolute_uri(
|
||||
reverse("opensearch-suggestions")
|
||||
)
|
||||
|
||||
context["site_title"] = get_site_title()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
|
||||
class OpenSearchSuggestionsView(View):
|
||||
def get(self, request: HttpRequest) -> JsonResponse:
|
||||
serializer = SearchParamsSerializer(data=request.GET)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return JsonResponse(serializer.errors, status=400)
|
||||
|
||||
search_page = get_object_or_404(SearchPage)
|
||||
|
||||
filters, query = parse_query_string(serializer.validated_data["q"])
|
||||
|
||||
results = (
|
||||
search_page.get_listing_pages()
|
||||
.search(query, order_by_relevance=True)[:5]
|
||||
.get_queryset()
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
[
|
||||
serializer.validated_data["q"],
|
||||
list(results.values_list("title", flat=True)),
|
||||
],
|
||||
safe=False,
|
||||
)
|
|
@ -50,6 +50,7 @@ urlpatterns = [
|
|||
path("feed/", AllPagesFeed(), name="feed"),
|
||||
path(".health/", include("health_check.urls")),
|
||||
path("", include("website.legacy.urls")),
|
||||
path("", include("website.search.urls")),
|
||||
path("api/", include("website.api.urls", namespace="api")),
|
||||
path(
|
||||
"@jake",
|
||||
|
|
Loading…
Reference in a new issue