diff --git a/Readme.md b/Readme.md index 9badaf58..92524d3d 100644 --- a/Readme.md +++ b/Readme.md @@ -31,4 +31,14 @@ docker-compose up Mittels diesen Command können die Thumbs für die Alben in der Galerie erstellt werden: python3 fet2020/manage.py create_thumbs - \ No newline at end of file + + +Erstellt alle Searchindexes neu. + +python3 fet2020/manage.py rebuild_index + + +Aktualisiert alle Searchindexes, die in den letzten 24 Stunden geändert wurden. + +python3 fet2020/manage.py update_index --age=24 + diff --git a/fet2020/fet2020/settings.py b/fet2020/fet2020/settings.py index 7a661a56..d36cb3bc 100644 --- a/fet2020/fet2020/settings.py +++ b/fet2020/fet2020/settings.py @@ -66,7 +66,7 @@ INSTALLED_APPS = [ "django.contrib.sites", "django.contrib.sitemaps", "django.contrib.flatpages", - + "taggit", "ckeditor", "ckeditor_uploader", @@ -77,7 +77,9 @@ INSTALLED_APPS = [ "django_filters", "django_static_jquery_ui", "fontawesomefree", - + "whoosh", + "haystack", + "core.apps.CoreConfig", "posts.apps.PostsConfig", "members.apps.MembersConfig", @@ -344,3 +346,12 @@ GALLERY = { CSRF_TRUSTED_ORIGINS = [ "https://" + env("HOST_NAME"), ] + + +# DJANGO HAYSTACK +HAYSTACK_CONNECTIONS = { + "default": { + "ENGINE": "haystack.backends.whoosh_backend.WhooshEngine", + "PATH": os.path.join(BASE_DIR, "whoosh_index"), + }, +} diff --git a/fet2020/fet2020/urls.py b/fet2020/fet2020/urls.py index cdc4516e..8e865e75 100644 --- a/fet2020/fet2020/urls.py +++ b/fet2020/fet2020/urls.py @@ -41,6 +41,7 @@ urlpatterns = [ path("members/", include("members.urls"), name="members"), path("member/", include(member_urlpatterns), name="member"), path("posts/", include("posts.urls")), + path("search/", include("search.urls")), path("tasks/", include("tasks.urls"), name="tasks"), path("intern/", include("intern.urls"), name="intern"), path( diff --git a/fet2020/intern/models.py b/fet2020/intern/models.py index e9cd223e..6cffd8a0 100644 --- a/fet2020/intern/models.py +++ b/fet2020/intern/models.py @@ -9,7 +9,8 @@ from django.utils import timezone from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ -from documents import create_pad +from documents import create_pad, get_pad_html +from documents.api import get_pad_link from tasks.models import TaskList logger = logging.getLogger(__name__) @@ -139,6 +140,12 @@ class Etherpad(models.Model): ), ] + def __str__(self): + return self.title + + def get_absolute_url(self): + return get_pad_link(self.__get_pad_name()) + def __get_pad_name(self): return ( slugify(self.date) @@ -170,8 +177,12 @@ class Etherpad(models.Model): _(f"Etherpad '{pad_name}' konnte nicht erstellt werden."), ) - def __str__(self): - return self.title + @property + def etherpad_html(self): + if not self.__get_pad_name(): + return None + + return get_pad_html(self.__get_pad_name()) class FileUpload(models.Model): diff --git a/fet2020/intern/search_indexes.py b/fet2020/intern/search_indexes.py new file mode 100644 index 00000000..4d4cb5a7 --- /dev/null +++ b/fet2020/intern/search_indexes.py @@ -0,0 +1,17 @@ +from haystack import indexes +from html2text import html2text + +from .models import Etherpad + + +class EtherpadIndex(indexes.SearchIndex, indexes.Indexable): + text = indexes.CharField(document=True, use_template=True) + title = indexes.CharField(model_attr="title") + date = indexes.DateField(model_attr="date") + etherpad = indexes.EdgeNgramField(null=True) + + def get_model(self): + return Etherpad + + def prepare_etherpad(self, obj): + return html2text(obj.etherpad_html) diff --git a/fet2020/posts/search_indexes.py b/fet2020/posts/search_indexes.py new file mode 100644 index 00000000..596aede7 --- /dev/null +++ b/fet2020/posts/search_indexes.py @@ -0,0 +1,38 @@ +from haystack import indexes +from html2text import html2text + +from .models import Post + + +class PostIndex(indexes.SearchIndex, indexes.Indexable): + text = indexes.CharField(document=True, use_template=True) + title = indexes.EdgeNgramField(model_attr="title") + body = indexes.EdgeNgramField(model_attr="body", null=True) + status = indexes.EdgeNgramField(model_attr="status") + date = indexes.DateField() + agenda = indexes.EdgeNgramField(null=True) + protocol = indexes.EdgeNgramField(null=True) + + def get_model(self): + return Post + + def index_queryset(self, using=None): + return self.get_model().objects.date_sorted_list(public=False) + + def prepare_date(self, obj): + if obj.post_type == "N": + return obj.public_date + elif obj.post_type == "E": + return obj.event_start.date() + elif obj.post_type == "F": + return obj.event_start.date() + + def prepare_agenda(self, obj): + if obj.has_agenda: + return html2text(obj.agenda_html) + return None + + def prepare_protocol(self, obj): + if obj.has_protocol: + return html2text(obj.protocol_html) + return None diff --git a/fet2020/requirements.txt b/fet2020/requirements.txt index 7c9dfc76..f35498e9 100644 --- a/fet2020/requirements.txt +++ b/fet2020/requirements.txt @@ -3,6 +3,7 @@ django-ckeditor==6.2.0 django-crontab==0.7.1 django-environ==0.8.1 django-filter==21.1 +django-haystack==3.2.dev0 django-static-jquery-ui==1.12.1.1 django-softhyphen==1.1.0 django-taggit==2.1.0 diff --git a/fet2020/search/__init__.py b/fet2020/search/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fet2020/search/forms.py b/fet2020/search/forms.py new file mode 100644 index 00000000..045b0199 --- /dev/null +++ b/fet2020/search/forms.py @@ -0,0 +1,60 @@ +from collections import deque + +from haystack.forms import SearchForm +from haystack.query import SQ + +from intern.models import Etherpad +from posts.models import Post + + +class FetUserSearchForm(SearchForm): + def search(self): + if not self.is_valid(): + return self.no_query_found() + + if not self.cleaned_data.get("q"): + return self.no_query_found() + + sqs_post = self.searchqueryset.models(Post) + sqs_post = sqs_post.filter( + SQ(title__icontains=self.cleaned_data["q"]) + | SQ(body__icontains=self.cleaned_data["q"]) + | SQ(agenda__icontains=self.cleaned_data["q"]) + | SQ(protocol__icontains=self.cleaned_data["q"]) + ) + + sqs_intern = self.searchqueryset.models(Etherpad) + sqs_intern = sqs_intern.filter(etherpad__icontains=self.cleaned_data["q"]) + + results = deque([]) + + for elem in sqs_post.order_by("-date"): + results.append(elem.object) + + for elem in sqs_intern.order_by("-date"): + results.append(elem.object) + + return sorted(results, key=lambda elem: elem.date, reverse=True) + + +class NonUserSearchForm(SearchForm): + def search(self): + if not self.is_valid(): + return self.no_query_found() + + if not self.cleaned_data.get("q"): + return self.no_query_found() + + sqs_post = self.searchqueryset.models(Post).filter(status="20") + sqs_post = sqs_post.filter( + SQ(title__icontains=self.cleaned_data["q"]) + | SQ(body__icontains=self.cleaned_data["q"]) + | SQ(agenda__icontains=self.cleaned_data["q"]) + ) + + results = deque([]) + + for elem in sqs_post.order_by("-date"): + results.append(elem.object) + + return sorted(results, key=lambda elem: elem.date, reverse=True) diff --git a/fet2020/search/urls.py b/fet2020/search/urls.py new file mode 100644 index 00000000..68e46d2e --- /dev/null +++ b/fet2020/search/urls.py @@ -0,0 +1,7 @@ +from django.urls import path, re_path + +from . import views + +urlpatterns = [ + path("", views.index, name="index"), +] diff --git a/fet2020/search/views.py b/fet2020/search/views.py new file mode 100644 index 00000000..de5c67fd --- /dev/null +++ b/fet2020/search/views.py @@ -0,0 +1,26 @@ +from haystack.generic_views import SearchView +from haystack.query import SearchQuerySet + +from django.contrib.auth.mixins import LoginRequiredMixin +from django.shortcuts import render + +from .forms import FetUserSearchForm, NonUserSearchForm + + +class FetUserSearchView(LoginRequiredMixin, SearchView): + template_name = "search/index.html" + queryset = SearchQuerySet() + form_class = FetUserSearchForm + + +class NonUserSearchView(SearchView): + template_name = "search/index.html" + queryset = SearchQuerySet() + form_class = NonUserSearchForm + + +def index(request): + if request.user.is_authenticated: + return FetUserSearchView.as_view()(request) + + return NonUserSearchView.as_view()(request) diff --git a/fet2020/templates/search/index.html b/fet2020/templates/search/index.html new file mode 100644 index 00000000..e2b4d349 --- /dev/null +++ b/fet2020/templates/search/index.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Suchen

+
+ +
+ + {{ form.as_table }} + + + + +
  + +
+ + {% if object_list %} +

Results

+ + {% for result in object_list %} +

+ {{ result.date|date }}: {{ result.title }} +

+ {% empty %} +

No results found.

+ {% endfor %} + {% endif %} +
+
+
+{% endblock %} diff --git a/fet2020/templates/search/indexes/intern/etherpad_text.txt b/fet2020/templates/search/indexes/intern/etherpad_text.txt new file mode 100644 index 00000000..7bac4979 --- /dev/null +++ b/fet2020/templates/search/indexes/intern/etherpad_text.txt @@ -0,0 +1,3 @@ +{{ object.title }} +{{ object.date }} +{{ object.etherpad|safe }} diff --git a/fet2020/templates/search/indexes/posts/post_text.txt b/fet2020/templates/search/indexes/posts/post_text.txt new file mode 100644 index 00000000..e9be43b3 --- /dev/null +++ b/fet2020/templates/search/indexes/posts/post_text.txt @@ -0,0 +1,4 @@ +{{ object.title }} +{{ object.body|safe }} +{{ object.agenda|safe }} +{{ object.protocol|safe }}