aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2023-04-27 03:05:18 -0400
committerBobby <[email protected]>2023-04-27 03:05:18 -0400
commit08f57b0f8029ee5f45adba91ceb31e07a7bd65fd (patch)
tree56557ff613b48b93b0ffb8c565fe6ac3d4e10efe
parent6e3748deb07a4916c516a6b38197a29b91f35c8a (diff)
downloadthatcomputerscientist-08f57b0f8029ee5f45adba91ceb31e07a7bd65fd.tar.xz
thatcomputerscientist-08f57b0f8029ee5f45adba91ceb31e07a7bd65fd.zip
Inworks Search using `haystack`
-rw-r--r--blog/views.py47
-rw-r--r--requirements.txt1
-rw-r--r--templates/blog/partials/sidebar.html8
-rw-r--r--templates/blog/search.html79
-rw-r--r--thatcomputerscientist/search_indexes.py30
-rw-r--r--thatcomputerscientist/settings.py6
-rw-r--r--thatcomputerscientist/templatetags/get_list.py7
-rw-r--r--thatcomputerscientist/urls.py3
8 files changed, 109 insertions, 72 deletions
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 @@
<form action="{% url 'blog:search' %}" method="get">
<table style="width: 250px; border-spacing: 0; border-collapse: separate;">
<tr>
- <td style="width: 150px; padding-right: 10px;">
- <input type="text" name="query" placeholder="Search..." autocomplete="off" style="width: 100%; box-sizing: border-box;" value="{{ request.GET.query }}">
+ <td style="width: 170px; padding-right: 10px;">
+ <input type="text" name="q" placeholder="Search..." autocomplete="off" style="width: 100%; box-sizing: border-box;" value="{{ request.GET.query }}">
</td>
- <td style="width: 80px;">
- <input type="submit" align="center" class="button button-special" value="Search" style="width: 100%;">
+ <td style="width: 60px;">
+ <input type="submit" align="center" class="button button-special" value="Search" style="max-width: 60px;">
</td>
</tr>
</table>
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 %}
+ <style>
+ #search-area {
+ display: none !important; /* Hide search area for this page */
+ }
-<div id="search-banner">
- <h1>Combine your search terms:</h1>
- <form action="{% url 'blog:search' %}" method="get">
- <label for="query">Search for:</label>
- <input type="text" name="query" value="{{ query }}" />
- <label for="tags">With tags (separate with commas):</label>
- <input type="text" name="tags" value="{{ tags }}" />
- <label for="category">In category:</label>
- <select name="category">
- <option value="" {% if cate == '' %}selected{% endif %}>All categories</option>
- {% for category in categories %}
- <option value="{{ category.slug }}" {% if category.slug == cate %}selected{% endif %}>{{ category.name }}</option>
- {% endfor %}
- </select>
- <input type="submit" value="Search" />
- </form>
-</div>
+ #user-area {
+ margin-top: 0px;
+ }
+ </style>
-{% if posts %}
- <div id="search-results">
- <h2>Search results for "{{ query }}"</h2>
- <ul>
- {% for post in posts %}
- <li>
- <a href="{% url 'blog:post' post.slug %}">{{ post.title }}</a>
- <p>{{ post.excerpt }}</p>
- </li>
- {% endfor %}
- </ul>
- </div>
-{% else %}
- <div id="search-results">
- <h2>No results found for "{{ query }}"</h2>
- </div>
-{% endif %}
-
-
-{% endblock %} \ No newline at end of file
+ <table id="search" cellpadding="0" cellspacing="0">
+ <tr>
+ <td id="search_sidebar" style="width: 200px; vertical-align: top;">
+ <form method="get" url="{% url 'blog:search' %}">
+ <h2>Search</h2>
+ <input type="text" name="q" value="{{ request.GET.q }}" placeholder="Query" style="width: 180px; display: block; margin: 10px 0;" required/>
+ <h2 class="mtsbitem">Search In</h2>
+ <input type="checkbox" name="search_in" value="posts" {% if 'posts' in search_in %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="search_in" style="vertical-align: middle;">Posts</label><br>
+ <input type="checkbox" name="search_in" value="users" {% if 'users' in search_in %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="search_in" style="vertical-align: middle;">Users</label><br>
+ <input type="checkbox" name="search_in" value="comments" {% if 'comments' in search_in %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="search_in" style="vertical-align: middle;">Comments</label><br>
+ <h2 class="mtsbitem">Sort By</h2>
+ <input type="radio" name="sort_by" value="relevance" {% if sort_by == 'relevance' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="sort_by" style="vertical-align: middle;">Relevance</label><br>
+ <input type="radio" name="sort_by" value="date" {% if sort_by == 'date' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="sort_by" style="vertical-align: middle;">Date</label><br>
+ <input type="radio" name="sort_by" value="popularity" {% if sort_by == 'popularity' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="sort_by" style="vertical-align: middle;">Popularity</label><br>
+ <h2 class="mtsbitem">Order</h2>
+ <input type="radio" name="order" value="ascending" {% if order == 'ascending' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="order" style="vertical-align: middle;">Ascending</label><br>
+ <input type="radio" name="order" value="descending" {% if order == 'descending' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="order" style="vertical-align: middle;">Descending</label><br>
+ <h2 class="mtsbitem">Date Range</h2>
+ <input type="radio" name="date_range" value="any" {% if date_range == 'any' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="date_range" style="vertical-align: middle;">Any</label><br>
+ <input type="radio" name="date_range" value="past_day" {% if date_range == 'past_day' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="date_range" style="vertical-align: middle;">Past Day</label><br>
+ <input type="radio" name="date_range" value="past_week" {% if date_range == 'past_week' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="date_range" style="vertical-align: middle;">Past Week</label><br>
+ <input type="radio" name="date_range" value="past_month" {% if date_range == 'past_month' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="date_range" style="vertical-align: middle;">Past Month</label><br>
+ <input type="radio" name="date_range" value="past_year" {% if date_range == 'past_year' %}checked{% endif %} style="margin: 5px 10px 5px 0px; vertical-align: middle;"/><label for="date_range" style="vertical-align: middle;">Past Year</label><br>
+ <input type="submit" value="Search" class="button button-special" style="margin: 10px 0;"/>
+ </form>
+ </td>
+ <td id="search_results" style="width: 530px; vertical-align: top; padding-left: 20px;">
+ <h2>Search Results</h2>
+ <p>Coming Soon!</p>
+ </td>
+ </tr>
+ </table>
+{% 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()
+
+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')),