From 2b6a1805dd1847afc5ae1da61602d9843f9988d0 Mon Sep 17 00:00:00 2001 From: Stefan Wehrmeyer Date: Mon, 14 Mar 2022 13:42:08 +0100 Subject: [PATCH] Add basic search for govplans --- froide_govplan/models.py | 49 +++++++++++++++++++ .../froide_govplan/plugins/search.html | 17 +++++++ froide_govplan/urls.py | 3 +- froide_govplan/views.py | 18 ++++++- 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 froide_govplan/templates/froide_govplan/plugins/search.html diff --git a/froide_govplan/models.py b/froide_govplan/models.py index 67b45da..e0851f5 100644 --- a/froide_govplan/models.py +++ b/froide_govplan/models.py @@ -1,5 +1,9 @@ +import functools +import re + from django.conf import settings from django.contrib.auth.models import Group +from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector from django.db import models from django.urls import reverse from django.utils import timezone @@ -84,6 +88,48 @@ class CategorizedGovernmentPlan(TaggedItemBase): verbose_name_plural = _("Categorized Government Plans") +WORD_RE = re.compile(r"^\w+$", re.IGNORECASE) + + +class GovernmentPlanManager(models.Manager): + SEARCH_LANG = "german" + + def get_search_vector(self): + fields = [ + ("title", "A"), + ("description", "B"), + ("quote", "B"), + ] + return functools.reduce( + lambda a, b: a + b, + [SearchVector(f, weight=w, config=self.SEARCH_LANG) for f, w in fields], + ) + + def search(self, query, qs=None): + if not qs: + qs = self.get_queryset() + if not query: + return qs + search_queries = [] + for q in query.split(): + if WORD_RE.match(q): + sq = SearchQuery( + "{}:*".format(q), search_type="raw", config=self.SEARCH_LANG + ) + else: + sq = SearchQuery(q, search_type="plain", config=self.SEARCH_LANG) + search_queries.append(sq) + + search_query = functools.reduce(lambda a, b: a & b, search_queries) + search_vector = self.get_search_vector() + qs = ( + qs.annotate(rank=SearchRank(search_vector, search_query)) + .filter(rank__gte=0.3) + .order_by("-rank") + ) + return qs + + class GovernmentPlan(models.Model): government = models.ForeignKey( Government, on_delete=models.CASCADE, verbose_name=_("government") @@ -142,6 +188,8 @@ class GovernmentPlan(models.Model): Group, null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("group") ) + objects = GovernmentPlanManager() + class Meta: ordering = ("reference", "title") verbose_name = _("Government plan") @@ -313,6 +361,7 @@ if CMSPlugin: ("froide_govplan/plugins/default.html", _("Normal")), ("froide_govplan/plugins/progress.html", _("Progress")), ("froide_govplan/plugins/card_cols.html", _("Card columns")), + ("froide_govplan/plugins/search.html", _("Search")), ] class GovernmentPlansCMSPlugin(CMSPlugin): diff --git a/froide_govplan/templates/froide_govplan/plugins/search.html b/froide_govplan/templates/froide_govplan/plugins/search.html new file mode 100644 index 0000000..7595cdb --- /dev/null +++ b/froide_govplan/templates/froide_govplan/plugins/search.html @@ -0,0 +1,17 @@ +{% load i18n %} +
+
+ +
+ +
+
+ {% if instance.government_id %} + + {% endif %} +
+
+ +
diff --git a/froide_govplan/urls.py b/froide_govplan/urls.py index 9174f96..8c857c4 100644 --- a/froide_govplan/urls.py +++ b/froide_govplan/urls.py @@ -2,12 +2,13 @@ from django.urls import path from django.utils.translation import pgettext_lazy from .admin import govplan_admin_site -from .views import GovPlanDetailView, GovPlanSectionDetailView +from .views import GovPlanDetailView, GovPlanSectionDetailView, search app_name = "govplan" urlpatterns = [ path("admin/", govplan_admin_site.urls), + path("search/", search, name="search"), path( pgettext_lazy("url part", "/plan//"), GovPlanDetailView.as_view(), diff --git a/froide_govplan/views.py b/froide_govplan/views.py index bdb2650..9248187 100644 --- a/froide_govplan/views.py +++ b/froide_govplan/views.py @@ -1,4 +1,4 @@ -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, render from django.views.generic import DetailView from .models import Government, GovernmentPlan, GovernmentPlanSection @@ -53,3 +53,19 @@ class GovPlanDetailView(GovernmentMixin, DetailView): "-timestamp" ) return context + + +def search(request): + plans = GovernmentPlan.objects.search(request.GET.get("q", "")) + + if request.GET.get("government"): + try: + gov_id = int(request.GET["government"]) + plans = plans.filter(government_id=gov_id) + except ValueError: + pass + + plans = plans[:20] + return render( + request, "froide_govplan/plugins/card_cols.html", {"object_list": plans} + )