Add search
This commit is contained in:
parent
726e342b88
commit
5722903301
5 changed files with 88 additions and 9 deletions
|
@ -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>
|
||||||
|
|
21
articles/templates/articles/article_search.html
Normal file
21
articles/templates/articles/article_search.html
Normal 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 %}
|
|
@ -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>
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Reference in a new issue