From 08f57b0f8029ee5f45adba91ceb31e07a7bd65fd Mon Sep 17 00:00:00 2001 From: Bobby Date: Thu, 27 Apr 2023 03:05:18 -0400 Subject: Inworks Search using `haystack` --- blog/views.py | 47 ++++++--------- requirements.txt | 1 + templates/blog/partials/sidebar.html | 8 +-- templates/blog/search.html | 79 ++++++++++++++------------ thatcomputerscientist/search_indexes.py | 30 ++++++++++ thatcomputerscientist/settings.py | 6 ++ thatcomputerscientist/templatetags/get_list.py | 7 +++ thatcomputerscientist/urls.py | 3 +- 8 files changed, 109 insertions(+), 72 deletions(-) create mode 100644 thatcomputerscientist/search_indexes.py create mode 100644 thatcomputerscientist/templatetags/get_list.py diff --git a/blog/views.py b/blog/views.py index 01e0379b..acab2d74 100644 --- a/blog/views.py +++ b/blog/views.py @@ -13,6 +13,7 @@ from users.forms import RegisterForm, UpdateUserDetailsForm from users.tokens import CaptchaTokenGenerator from django.contrib import messages from bs4 import BeautifulSoup +from haystack.query import SearchQuerySet import re import os from dotenv import load_dotenv @@ -195,40 +196,26 @@ def delete_comment(request, slug, comment_id): def search(request): - categories = Category.objects.all() - tags = request.GET.get('tags') - category = request.GET.get('category') - query = request.GET.get('query') - search_in_body = False - - # First check for query constraints - if len(query) == 0: - return render(request, 'blog/search.html', {'title': 'Search', 'posts': [], 'categories': categories, 'tags': tags, 'cate': category, 'query': query}) - - if len(query) < 3: - return render(request, 'blog/search.html', {'title': 'Search', 'posts': [], 'categories': categories, 'tags': tags, 'cate': category, 'query': query, 'error': 'Query must be at least 3 characters long'}) - - if len(query) > 100: - search_in_body = True - - # public posts which contain the query in the title or body - posts = Post.objects.filter(is_public=True, title__icontains=query) if not search_in_body else Post.objects.filter(is_public=True, body__icontains=query) | Post.objects.filter(is_public=True, title__icontains=query) + query = request.GET.get('q') + search_in = request.GET.getlist('search_in') if request.GET.get('search_in') else ['posts'] + sort_by = request.GET.get('sort_by') if request.GET.get('sort_by') else 'relevance' + order = request.GET.get('order') if request.GET.get('order') else 'ascending' + date_range = request.GET.get('date_range') if request.GET.get('date_range') else 'any' + search_model_map = { + 'posts': Post, + 'users': User, + } - # filter by category slug - if category: - posts = posts.filter(category__slug=category) - else: - category = '' + if query: + search_results = SearchQuerySet().filter(content=query) + if search_in: + search_results = search_results.models(*[search_model_map[model] for model in search_in]) - # filter by tags - if tags: - posts = posts.filter(tags__name__in=tags.split(',')) + search_results = [result.object for result in search_results] else: - tags = '' + search_results = None - # order by date - posts = posts.order_by('-date') - return render(request, 'blog/search.html', {'title': 'Search', 'posts': posts, 'categories': categories, 'tags': tags, 'cate': category, 'query': query}) + return render(request, 'blog/search.html', {'title': f"Search results for '{query}'", 'query': query, 'search_results': search_results, 'search_in': search_in, 'sort_by': sort_by, 'order': order, 'date_range': date_range}) def articles(request, date=None, cg=None): type = 'articles' diff --git a/requirements.txt b/requirements.txt index afac18fe..a160015e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ jellyfish fuzzywuzzy python-Levenshtein selenium +django-haystack diff --git a/templates/blog/partials/sidebar.html b/templates/blog/partials/sidebar.html index b47b3657..3e0ea796 100644 --- a/templates/blog/partials/sidebar.html +++ b/templates/blog/partials/sidebar.html @@ -51,11 +51,11 @@
- -
- + + - + +
diff --git a/templates/blog/search.html b/templates/blog/search.html index f9eaa71e..9fde878c 100644 --- a/templates/blog/search.html +++ b/templates/blog/search.html @@ -1,40 +1,45 @@ {% extends 'blog/partials/base.html' %} {% block content %} +{% load get_list %} + -{% if posts %} -
-

Search results for "{{ query }}"

-
    - {% for post in posts %} -
  • - {{ post.title }} -

    {{ post.excerpt }}

    -
  • - {% endfor %} -
-
-{% else %} -
-

No results found for "{{ query }}"

-
-{% endif %} - - -{% endblock %} \ No newline at end of file + + + + + + +{% endblock %} diff --git a/thatcomputerscientist/search_indexes.py b/thatcomputerscientist/search_indexes.py new file mode 100644 index 00000000..db8761b5 --- /dev/null +++ b/thatcomputerscientist/search_indexes.py @@ -0,0 +1,30 @@ +from haystack import indexes +from blog.models import Post +from django.contrib.auth.models import User + +class PostIndex(indexes.SearchIndex, indexes.Indexable): + text = indexes.CharField(document=True, use_template=True) + title = indexes.CharField(model_attr='title') + body = indexes.CharField(model_attr='body') + date = indexes.DateTimeField(model_attr='date') + + def get_model(self): + return Post + + def index_queryset(self, using=None): + """Return all published posts.""" + return self.get_model().objects.filter(is_public=True) + +class UserIndex(indexes.SearchIndex, indexes.Indexable): + text = indexes.CharField(document=True, use_template=True) + username = indexes.CharField(model_attr='username') + first_name = indexes.CharField(model_attr='first_name') + last_name = indexes.CharField(model_attr='last_name') + email = indexes.CharField(model_attr='email') + + def get_model(self): + return User + + def index_queryset(self, using=None): + """Return all user profiles without filtering.""" + return self.get_model().objects.all() \ No newline at end of file diff --git a/thatcomputerscientist/settings.py b/thatcomputerscientist/settings.py index 33c2186d..514adb32 100644 --- a/thatcomputerscientist/settings.py +++ b/thatcomputerscientist/settings.py @@ -47,6 +47,7 @@ INSTALLED_APPS = [ 'django.contrib.sites', 'django.contrib.sitemaps', 'thatcomputerscientist', + 'haystack', 'blog', 'users', 'userpages', @@ -58,6 +59,11 @@ INSTALLED_APPS = [ SITE_ID = 1 APPEND_SLASH = False +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine', + }, +} MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', diff --git a/thatcomputerscientist/templatetags/get_list.py b/thatcomputerscientist/templatetags/get_list.py new file mode 100644 index 00000000..7e77689c --- /dev/null +++ b/thatcomputerscientist/templatetags/get_list.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def get_list(dictionary, key): + return dictionary.getlist(key) \ No newline at end of file diff --git a/thatcomputerscientist/urls.py b/thatcomputerscientist/urls.py index 25fb077e..96cc8a9b 100644 --- a/thatcomputerscientist/urls.py +++ b/thatcomputerscientist/urls.py @@ -19,6 +19,7 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib.sitemaps.views import sitemap from .sitemaps import PostSitemap, CategorySitemap, TagSitemap, StaticViewSitemap, GithubSitemap +from haystack.views import search_view_factory sitemaps = { 'posts': PostSitemap, @@ -31,7 +32,7 @@ sitemaps = { handler404 = 'thatcomputerscientist.error_handler.custom_404' urlpatterns = [ - path('admin/', admin.site.urls), + path('admin', admin.site.urls), path('', include('blog.urls', namespace='blog')), path('users', include('users.urls', namespace='users')), path('blog-admin', include('blog_admin.urls', namespace='blog-admin')), -- cgit v1.2.3