Add search

This commit is contained in:
Gabriel Augendre 2021-03-02 20:42:54 +01:00
parent 726e342b88
commit 5722903301
5 changed files with 88 additions and 9 deletions

View file

@ -9,25 +9,28 @@
{% block content %} {% block content %}
<section> <section>
<h2 id="blog-posts">Blog posts</h2> <h2 id="blog-posts">{% block main_title %}Blog posts{% endblock %}</h2>
{% block search_bar %}{% endblock %}
{% for article in articles %} {% for article in articles %}
<p> <p>
{% include "articles/snippets/datetime.html" %}<br> {% include "articles/snippets/datetime.html" %}<br>
<a href="{% url 'article-detail' slug=article.slug %}">{{ article.title }}</a> <a href="{% url 'article-detail' slug=article.slug %}">{{ article.title }}</a>
</p> </p>
{% empty %} {% empty %}
{% block empty_results %}
<p>No article here. Come back later 🙂</p> <p>No article here. Come back later 🙂</p>
{% endblock %}
{% endfor %} {% endfor %}
</section> </section>
<nav class="pagination"> <nav class="pagination">
<div> <div>
{% if page_obj.has_next %} {% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}#blog-posts"><button>⇠ Older</button></a> <a href="?{{ next_page_querystring }}#blog-posts"><button>⇠ Older</button></a>
{% endif %} {% endif %}
</div> </div>
<div> <div>
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}#blog-posts"><button>Newer ⇢</button></a> <a href="?{{ previous_page_querystring }}#blog-posts"><button>Newer ⇢</button></a>
{% endif %} {% endif %}
</div> </div>
</nav> </nav>

View file

@ -0,0 +1,21 @@
{% extends 'articles/article_list.html' %}
{% block main_title %}Search{% if search_expression %} results{% endif %}{% endblock %}
{% block search_bar %}
<p>
This search form is pretty basic and will search for articles matching
ALL of your search terms in either the content, the title or the keywords.
It returns exact case-insensitive matches.
</p>
<form action="" method="get">
<input aria-label="Search" type="text" name="s" placeholder="Search..." value="{{ search_expression }}">
<button type="submit">Search</button>
</form>
{% endblock %}
{% block empty_results %}
{% if search_expression %}
No article found matching your criteria.
{% endif %}
{% endblock %}

View file

@ -1,5 +1,6 @@
<nav> <nav>
<a href="{% url 'articles-list' %}">Home</a> <a href="{% url 'articles-list' %}">Home</a>
<a href="{% url 'search' %}">Search</a>
<a href="{% url 'complete-feed' %}">RSS</a> <a href="{% url 'complete-feed' %}">RSS</a>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a href="{% url 'admin:articles_article_add' %}">Write</a> <a href="{% url 'admin:articles_article_add' %}">Write</a>

View file

@ -5,6 +5,7 @@ from articles.views import api, feeds, html
urlpatterns = [ urlpatterns = [
path("", html.ArticlesListView.as_view(), name="articles-list"), path("", html.ArticlesListView.as_view(), name="articles-list"),
path("drafts/", html.DraftsListView.as_view(), name="drafts-list"), path("drafts/", html.DraftsListView.as_view(), name="drafts-list"),
path("search/", html.SearchArticlesListView.as_view(), name="search"),
path("feed/", feeds.CompleteFeed(), name="complete-feed"), path("feed/", feeds.CompleteFeed(), name="complete-feed"),
path("api/render/<int:article_pk>/", api.render_article, name="api-render-article"), path("api/render/<int:article_pk>/", api.render_article, name="api-render-article"),
path("<slug:slug>/", html.ArticleDetailView.as_view(), name="article-detail"), path("<slug:slug>/", html.ArticleDetailView.as_view(), name="article-detail"),

View file

@ -1,24 +1,47 @@
import operator
from functools import reduce
from typing import Dict
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import F from django.db.models import F, Q
from django.views import generic from django.views import generic
from articles.models import Article from articles.models import Article
class BaseArticleListView(generic.ListView): class BaseArticleListView(generic.ListView):
model = Article
context_object_name = "articles"
paginate_by = 10 paginate_by = 10
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["blog_title"] = settings.BLOG["title"] context["blog_title"] = settings.BLOG["title"]
context["blog_description"] = settings.BLOG["description"] context["blog_description"] = settings.BLOG["description"]
page_obj = context["page_obj"]
if page_obj.has_next():
querystring = self.build_querystring({"page": page_obj.next_page_number()})
context["next_page_querystring"] = querystring
if page_obj.has_previous():
querystring = self.build_querystring(
{"page": page_obj.previous_page_number()}
)
context["previous_page_querystring"] = querystring
return context return context
def get_additional_querystring_params(self) -> Dict[str, str]:
return dict()
def build_querystring(self, initial_queryparams: Dict[str, str]) -> str:
querystring = {
**initial_queryparams,
**self.get_additional_querystring_params(),
}
return "&".join(map(lambda item: f"{item[0]}={item[1]}", querystring.items()))
class ArticlesListView(BaseArticleListView): class ArticlesListView(BaseArticleListView):
model = Article
context_object_name = "articles"
queryset = Article.objects.filter(status=Article.PUBLISHED) queryset = Article.objects.filter(status=Article.PUBLISHED)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -30,9 +53,39 @@ class ArticlesListView(BaseArticleListView):
return context return context
class SearchArticlesListView(BaseArticleListView):
queryset = Article.objects.filter(status=Article.PUBLISHED)
template_name = "articles/article_search.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["search_expression"] = self.request.GET.get("s") or ""
return context
def get_queryset(self):
queryset = super().get_queryset()
search_expression = self.request.GET.get("s")
if not search_expression:
return queryset.none()
search_terms = search_expression.split()
return queryset.filter(
reduce(operator.and_, (Q(title__icontains=term) for term in search_terms))
| reduce(
operator.and_, (Q(content__icontains=term) for term in search_terms)
)
| reduce(
operator.and_, (Q(keywords__icontains=term) for term in search_terms)
)
)
def get_additional_querystring_params(self) -> Dict[str, str]:
search_expression = self.request.GET.get("s")
if search_expression:
return {"s": search_expression}
return {}
class DraftsListView(LoginRequiredMixin, BaseArticleListView): class DraftsListView(LoginRequiredMixin, BaseArticleListView):
model = Article
context_object_name = "articles"
queryset = Article.objects.filter(status=Article.DRAFT) queryset = Article.objects.filter(status=Article.DRAFT)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):