blog-browse #40
|
@ -32,11 +32,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<div class="container-fluid w-100 h-100 p-2">
|
<div class="container-fluid w-100 h-100 p-2">
|
||||||
<form class="w-100 h-100" role="search" action="" method="GET" novalidate>
|
<form class="w-100 h-100"
|
||||||
|
role="search"
|
||||||
|
action=""
|
||||||
|
method="GET"
|
||||||
|
novalidate
|
||||||
|
id="filter-form">
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<input type="search"
|
<input type="search"
|
||||||
name="search"
|
name="search"
|
||||||
class="form-control flex-fill py-2"
|
class="form-control flex-fill py-2"
|
||||||
|
defaultValue=""
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
placeholder="{% trans "Search" %}"
|
placeholder="{% trans "Search" %}"
|
||||||
required=""
|
required=""
|
||||||
|
@ -46,6 +52,7 @@
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<select class="form-select py-2"
|
<select class="form-select py-2"
|
||||||
aria-label="Large select example"
|
aria-label="Large select example"
|
||||||
|
defaultValue=""
|
||||||
name="category">
|
name="category">
|
||||||
<option value="">{% trans "select category" %}</option>
|
<option value="">{% trans "select category" %}</option>
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
|
@ -57,6 +64,7 @@
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<input class="tagify"
|
<input class="tagify"
|
||||||
name="keywords"
|
name="keywords"
|
||||||
|
defaultValue=""
|
||||||
placeholder="{% trans "Keywords" %}"
|
placeholder="{% trans "Keywords" %}"
|
||||||
{% if filters.keywords %} value="{% for keyword in filters.keywords %}{{ keyword }} {% endfor %}
|
{% if filters.keywords %} value="{% for keyword in filters.keywords %}{{ keyword }} {% endfor %}
|
||||||
"
|
"
|
||||||
|
@ -65,9 +73,11 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<input id="clear-button"
|
<!-- will reset to the filters encoded in
|
||||||
type="reset"
|
the url by GET attributes, not to the empty
|
||||||
class="btn bg-primary fw-bold py-2 float-end w-100">
|
form. -->
|
||||||
|
<input type="reset"
|
||||||
|
class="btn bg-primary fw-bold py-2 float-end w-100 reset-empty-button">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -91,8 +101,15 @@
|
||||||
<div class="row mb-5">
|
<div class="row mb-5">
|
||||||
<div class="container-fluid p-0 w-100 h-100">
|
<div class="container-fluid p-0 w-100 h-100">
|
||||||
<div class="row gap-3">
|
<div class="row gap-3">
|
||||||
{# TODO: display some info if the queryset is empty #}
|
{% if is_empty %}
|
||||||
{# TODO: pagination #}
|
<div class="text-center">
|
||||||
|
<img src="/media/img/http/404.svg"
|
||||||
|
class="img-fluid pb-5 pt-2 darkmode-invert"
|
||||||
|
style="max-height: 650px;"
|
||||||
|
alt="404" />
|
||||||
|
<h2 class="display-5">{% trans "No posts found for your filters." %}</h2>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<div class="card col mx-auto my-2 py-2" style="min-width: 400px;">
|
<div class="card col mx-auto my-2 py-2" style="min-width: 400px;">
|
||||||
<a class="text-reset link-offset-2 link-underline link-underline-opacity-0"
|
<a class="text-reset link-offset-2 link-underline link-underline-opacity-0"
|
||||||
|
@ -146,6 +163,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
from django.shortcuts import Http404, HttpResponse, get_object_or_404, render
|
import json
|
||||||
|
import ast
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.http.response import Http404, HttpResponse
|
||||||
|
from django.http.request import HttpRequest
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
from django.views.generic import TemplateView, DetailView, ListView, View
|
from django.views.generic import TemplateView, DetailView, ListView, View
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
@ -9,8 +13,6 @@ from start.views import SearchableView
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
import ast
|
|
||||||
import json
|
|
||||||
|
|
||||||
class Index(TemplateView, SearchableView):
|
class Index(TemplateView, SearchableView):
|
||||||
"""
|
"""
|
||||||
|
@ -55,6 +57,7 @@ class Browse(ListView):
|
||||||
model = BlogPost
|
model = BlogPost
|
||||||
template_name = "blog/browse.html"
|
template_name = "blog/browse.html"
|
||||||
context_object_name = "posts"
|
context_object_name = "posts"
|
||||||
|
allow_empty = False # but we have a special get method
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
objects = BlogPost.objects.all()
|
objects = BlogPost.objects.all()
|
||||||
|
@ -123,3 +126,27 @@ class Browse(ListView):
|
||||||
keywords = keywords.split('+')
|
keywords = keywords.split('+')
|
||||||
context["filters"]["keywords"] = keywords
|
context["filters"]["keywords"] = keywords
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
self.object_list = self.get_queryset()
|
||||||
|
allow_empty = self.get_allow_empty()
|
||||||
|
|
||||||
|
if not allow_empty:
|
||||||
|
# When pagination is enabled and object_list is a queryset,
|
||||||
|
# it's better to do a cheap query than to load the unpaginated
|
||||||
|
# queryset in memory.
|
||||||
|
if self.get_paginate_by(self.object_list) is not None and hasattr(
|
||||||
|
self.object_list, 'exists'):
|
||||||
|
is_empty = not self.object_list.exists()
|
||||||
|
else:
|
||||||
|
is_empty = not self.object_list
|
||||||
|
else:
|
||||||
|
is_empty = False
|
||||||
|
context = self.get_context_data()
|
||||||
|
|
||||||
|
context["is_empty"] = is_empty
|
||||||
|
|
||||||
|
response = self.render_to_response(context)
|
||||||
|
if is_empty:
|
||||||
|
response.status_code = 404
|
||||||
|
return response
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="557.15094"
|
||||||
|
height="210.8916"
|
||||||
|
viewBox="0 0 557.15094 210.8916"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:export-filename="404.svg"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<rect
|
||||||
|
x="292.2478"
|
||||||
|
y="154.0531"
|
||||||
|
width="462.1593"
|
||||||
|
height="240.14159"
|
||||||
|
id="rect1" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-239.50485,-280.19422)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text1"
|
||||||
|
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:24.5524px;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro, Bold Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:pre;shape-inside:url(#rect1);fill:#3b3b3b;fill-opacity:1"
|
||||||
|
transform="matrix(13.034066,0,0,13.034066,-3571.9156,-1800.7033)"><tspan
|
||||||
|
x="292.24805"
|
||||||
|
y="175.53608"
|
||||||
|
id="tspan2">404</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -2,6 +2,18 @@
|
||||||
// dark theme stuff
|
// dark theme stuff
|
||||||
const setTheme = (theme) => {
|
const setTheme = (theme) => {
|
||||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||||
|
if (theme === "dark") {
|
||||||
|
var to_invert = document.querySelectorAll('.darkmode-invert');
|
||||||
|
to_invert.forEach(element => {
|
||||||
|
element.style.filter = "invert(100%)";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (theme === "light") {
|
||||||
|
var to_invert = document.querySelectorAll('.darkmode-invert');
|
||||||
|
to_invert.forEach(element => {
|
||||||
|
element.style.filter = "invert(0%)";
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
setTheme(localStorage.getItem("theme"));
|
setTheme(localStorage.getItem("theme"));
|
||||||
document.getElementById("toggleThemeButton").onclick = function () {
|
document.getElementById("toggleThemeButton").onclick = function () {
|
||||||
|
|
Loading…
Reference in New Issue