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 %}
|
||||
<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 %}
|
||||
<p>
|
||||
{% include "articles/snippets/datetime.html" %}<br>
|
||||
<a href="{% url 'article-detail' slug=article.slug %}">{{ article.title }}</a>
|
||||
</p>
|
||||
{% empty %}
|
||||
{% block empty_results %}
|
||||
<p>No article here. Come back later 🙂</p>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
<nav class="pagination">
|
||||
<div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
<div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</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>
|
||||
<a href="{% url 'articles-list' %}">Home</a>
|
||||
<a href="{% url 'search' %}">Search</a>
|
||||
<a href="{% url 'complete-feed' %}">RSS</a>
|
||||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'admin:articles_article_add' %}">Write</a>
|
||||
|
|
|
@ -5,6 +5,7 @@ from articles.views import api, feeds, html
|
|||
urlpatterns = [
|
||||
path("", html.ArticlesListView.as_view(), name="articles-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("api/render/<int:article_pk>/", api.render_article, name="api-render-article"),
|
||||
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.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import F
|
||||
from django.db.models import F, Q
|
||||
from django.views import generic
|
||||
|
||||
from articles.models import Article
|
||||
|
||||
|
||||
class BaseArticleListView(generic.ListView):
|
||||
model = Article
|
||||
context_object_name = "articles"
|
||||
paginate_by = 10
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["blog_title"] = settings.BLOG["title"]
|
||||
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
|
||||
|
||||
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):
|
||||
model = Article
|
||||
context_object_name = "articles"
|
||||
queryset = Article.objects.filter(status=Article.PUBLISHED)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -30,9 +53,39 @@ class ArticlesListView(BaseArticleListView):
|
|||
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):
|
||||
model = Article
|
||||
context_object_name = "articles"
|
||||
queryset = Article.objects.filter(status=Article.DRAFT)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
Reference in a new issue