diff options
| author | Bobby <[email protected]> | 2023-04-27 03:05:18 -0400 |
|---|---|---|
| committer | Bobby <[email protected]> | 2023-04-27 03:05:18 -0400 |
| commit | 08f57b0f8029ee5f45adba91ceb31e07a7bd65fd (patch) | |
| tree | 56557ff613b48b93b0ffb8c565fe6ac3d4e10efe | |
| parent | 6e3748deb07a4916c516a6b38197a29b91f35c8a (diff) | |
| download | thatcomputerscientist-08f57b0f8029ee5f45adba91ceb31e07a7bd65fd.tar.xz thatcomputerscientist-08f57b0f8029ee5f45adba91ceb31e07a7bd65fd.zip | |
Inworks Search using `haystack`
| -rw-r--r-- | blog/views.py | 47 | ||||
| -rw-r--r-- | requirements.txt | 1 | ||||
| -rw-r--r-- | templates/blog/partials/sidebar.html | 8 | ||||
| -rw-r--r-- | templates/blog/search.html | 79 | ||||
| -rw-r--r-- | thatcomputerscientist/search_indexes.py | 30 | ||||
| -rw-r--r-- | thatcomputerscientist/settings.py | 6 | ||||
| -rw-r--r-- | thatcomputerscientist/templatetags/get_list.py | 7 | ||||
| -rw-r--r-- | thatcomputerscientist/urls.py | 3 |
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')), |
