Add inventory search with haystack
Also give a coherent look to all inventory lists
This commit is contained in:
parent
68dea4002c
commit
1850746975
19 changed files with 217 additions and 45 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -63,6 +63,7 @@ venv
|
|||
|
||||
# Project specific
|
||||
db.sqlite3
|
||||
whoosh_index/
|
||||
public/
|
||||
|
||||
# Vim recover files
|
||||
|
|
|
@ -23,6 +23,15 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
# Search engine
|
||||
# https://django-haystack.readthedocs.io/en/latest/tutorial.html#configuration
|
||||
|
||||
HAYSTACK_CONNECTIONS = {
|
||||
"default": {
|
||||
"ENGINE": "haystack.backends.whoosh_backend.WhooshEngine",
|
||||
"PATH": os.path.join(BASE_DIR, "whoosh_index"),
|
||||
},
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.0/topics/i18n/
|
||||
|
|
|
@ -25,6 +25,7 @@ INSTALLED_APPS = [
|
|||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"haystack",
|
||||
"mainsite",
|
||||
"inventory",
|
||||
"django_cas_ng",
|
||||
|
@ -79,6 +80,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||
]
|
||||
|
||||
# Update the search database on save
|
||||
HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||
|
||||
|
|
27
inventory/search_indexes.py
Normal file
27
inventory/search_indexes.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from haystack import indexes
|
||||
from .models import Category, Tag, Game
|
||||
|
||||
|
||||
class CategoryIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, model_attr="name")
|
||||
|
||||
def get_model(self):
|
||||
return Category
|
||||
|
||||
|
||||
class TagIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, model_attr="name")
|
||||
|
||||
def get_model(self):
|
||||
return Tag
|
||||
|
||||
|
||||
class GameIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(
|
||||
document=True,
|
||||
use_template=True,
|
||||
template_name="inventory/search_indexes/game.txt",
|
||||
)
|
||||
|
||||
def get_model(self):
|
||||
return Game
|
|
@ -7,7 +7,7 @@
|
|||
Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} dans cette catégorie :
|
||||
<ul>
|
||||
{% for game in game_list %}
|
||||
<li><a href="{{ game.get_absolute_url }}">{{ game.title }}</a></li>
|
||||
{% include "./partials/game_item.html" %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1><i class="fa fa-bookmark" aria-hidden="true"></i> Liste des catégories</h1>
|
||||
<h1>Liste des catégories</h1>
|
||||
|
||||
Il y a {{ category_list|length }} catégorie{{ category_list|pluralize }} de jeux :
|
||||
<ul>
|
||||
{% for category in category_list %}
|
||||
<li><a href="{{ category.get_absolute_url }}">{{ category.name }}</a></li>
|
||||
<li><a href="{{ category.get_absolute_url }}"><i class="fa fa-bookmark" aria-hidden="true"></i> {{ category.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} en salle jeux :
|
||||
<ul>
|
||||
{% for game in game_list %}
|
||||
<li><a href="{{ game.get_absolute_url }}">{{game.title}}</a></li>
|
||||
{% include "./partials/game_item.html" %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,7 +3,16 @@
|
|||
{% block "content" %}
|
||||
<h1>Inventaire du club Jeux</h1>
|
||||
|
||||
Rechercher dans la ludothèque:
|
||||
<form class="search" method="get" action="{% url "inventory:search" %}">
|
||||
<input type="search" name="q" />
|
||||
<button type="submit"><i class="fa fa-fw fa-search" aria-hidden="true"></i></button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="btn_row">
|
||||
|
||||
<a href="{% url "inventory:category_list" %}">
|
||||
Liste des catégories
|
||||
<p class="helptext">
|
||||
|
|
19
inventory/templates/inventory/partials/game_item.html
Normal file
19
inventory/templates/inventory/partials/game_item.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<li>
|
||||
<a href="{{ game.get_absolute_url }}">
|
||||
{{game.title}}
|
||||
<div class="details">
|
||||
<p><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ game.player_range }}</p>
|
||||
<p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ game.duration }}
|
||||
<p><i class="fa fa-fw fa-bookmark"></i> {{ game.category }}</p>
|
||||
{% for tag in game.tags.all %}
|
||||
<p><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</p>
|
||||
{% endfor %}
|
||||
{% if game.game_designer %}
|
||||
<p><i class="fa fa-fw fa-pencil" aria-hidden="true"></i> {{ game.game_designer }}
|
||||
{% endif %}
|
||||
{% if game.editor %}
|
||||
<p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ game.editor }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
39
inventory/templates/inventory/search.html
Normal file
39
inventory/templates/inventory/search.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1>Recherche</h1>
|
||||
<form class="search" method="get" action=".">
|
||||
{{ form.q }}
|
||||
<button type="submit"><i class="fa fa-fw fa-search" aria-hidden="true"></i></button>
|
||||
</form>
|
||||
|
||||
{% if query %}
|
||||
<hr/>
|
||||
|
||||
<ul>
|
||||
{% for result in page_obj.object_list %}
|
||||
{% if result.model_name == "game" %}
|
||||
{% include "./partials/game_item.html" with game=result.object %}
|
||||
{% elif result.model_name == "category" %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}"><i class="fa fa-bookmark" aria-hidden="true"></i> {{ result.object.name }}</a>
|
||||
</li>
|
||||
{% elif result.model_name == "tag" %}
|
||||
<li>
|
||||
<a href="{{ result.object.get_absolute_url }}"><i class="fa fa-tag" aria-hidden="true"></i> {{ result.object.name }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<li>Aucun résultat trouvé</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if page_obj.has_previous or page_obj.has_next %}
|
||||
<div>
|
||||
{% if page_obj.has_previous %}<a href="?q={{ query }}&page={{ page_obj.previous_page_number }}">{% endif %}« Précédent{% if page_obj.has_previous %}</a>{% endif %}
|
||||
|
|
||||
{% if page_obj.has_next %}<a href="?q={{ query }}&page={{ page_obj.next_page_number }}">{% endif %}Suivant »{% if page_obj.has_next %}</a>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
4
inventory/templates/inventory/search_indexes/game.txt
Normal file
4
inventory/templates/inventory/search_indexes/game.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
{{ object.title }}
|
||||
{{ object.editor }}
|
||||
{{ object.game_designer }}
|
||||
{{ object.description }}
|
|
@ -7,7 +7,7 @@
|
|||
Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} marqué{{ game_list|pluralize }} avec ce tag :
|
||||
<ul>
|
||||
{% for game in game_list %}
|
||||
<li><a href="{{ game.get_absolute_url }}">{{ game.title }}</a></li>
|
||||
{% include "./partials/game_item.html" %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1><i class="fa fa-tags" aria-hidden="true"></i> Liste des tags</h1>
|
||||
<h1>Liste des tags</h1>
|
||||
|
||||
Il y a {{ tag_list|length }} tag{{ tag_list|pluralize }} dans la ludothèque :
|
||||
<ul>
|
||||
{% for tag in tag_list %}
|
||||
<li><a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a></li>
|
||||
<li><a href="{{ tag.get_absolute_url }}"><i class="fa fa-tag" aria-hidden="true"></i> {{ tag.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,6 +7,7 @@ from .views import (
|
|||
TagView,
|
||||
GameListView,
|
||||
GameView,
|
||||
InventorySearchView,
|
||||
)
|
||||
|
||||
app_name = "inventory"
|
||||
|
@ -19,4 +20,5 @@ urlpatterns = [
|
|||
path("tag/<slug>/", TagView.as_view(), name="tag"),
|
||||
path("game/", GameListView.as_view(), name="game_list"),
|
||||
path("game/<slug>/", GameView.as_view(), name="game"),
|
||||
path("search/", InventorySearchView.as_view(), name="search"),
|
||||
]
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
from django.views.generic import TemplateView, ListView, DetailView
|
||||
from haystack.generic_views import SearchView
|
||||
from haystack.forms import SearchForm
|
||||
from haystack.query import SearchQuerySet
|
||||
from .models import Category, Tag, Game
|
||||
|
||||
|
||||
|
@ -34,3 +37,11 @@ class GameListView(ListView):
|
|||
class GameView(DetailView):
|
||||
model = Game
|
||||
template_name = "inventory/game.html"
|
||||
|
||||
|
||||
class InventorySearchView(SearchView):
|
||||
form_class = SearchForm
|
||||
template_name = "inventory/search.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return SearchQuerySet().models(Category, Tag, Game)
|
||||
|
|
|
@ -97,20 +97,11 @@ select {
|
|||
background-color: rgba($error_box_color, .4);
|
||||
}
|
||||
|
||||
.checkbox_input {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
form.search {
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
|
||||
.label_line {
|
||||
order: 1;
|
||||
flex: 1 1 500px;
|
||||
}
|
||||
input {
|
||||
flex: 0 1 50px;
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fieldgroup {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
|
|
@ -276,3 +276,38 @@ iframe {
|
|||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0 20px;
|
||||
list-style-type: none;
|
||||
|
||||
li>a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 15px;
|
||||
margin: 10px 5px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
border-color: $header_border_color;
|
||||
background-color: lighten($header_border_color, 70%);
|
||||
}
|
||||
&:focus {
|
||||
border-color: $header_border_color;
|
||||
background-color: lighten($header_border_color, 70%);
|
||||
box-shadow: 0 0 1.5px 1px $header_bg_color;
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: 0.7em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 5px;
|
||||
gap: 5px 20px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,18 +151,11 @@ select {
|
|||
border-radius: 10px;
|
||||
background-color: rgba(255, 205, 221, 0.4); }
|
||||
|
||||
.checkbox_input {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center; }
|
||||
.checkbox_input .label_line {
|
||||
order: 1;
|
||||
flex: 1 1 500px; }
|
||||
.checkbox_input input {
|
||||
flex: 0 1 50px; }
|
||||
|
||||
.fieldgroup {
|
||||
margin: 15px 0; }
|
||||
form.search {
|
||||
flex-direction: row;
|
||||
gap: 10px; }
|
||||
form.search button {
|
||||
margin: 0; }
|
||||
|
||||
html {
|
||||
box-sizing: border-box; }
|
||||
|
@ -413,3 +406,29 @@ iframe {
|
|||
margin: 1ex; }
|
||||
#game_infos #details hr {
|
||||
margin: 1ex; }
|
||||
|
||||
ul {
|
||||
padding: 0 20px;
|
||||
list-style-type: none; }
|
||||
ul li > a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 15px;
|
||||
margin: 10px 5px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent; }
|
||||
ul li > a:hover {
|
||||
border-color: #51808c;
|
||||
background-color: white; }
|
||||
ul li > a:focus {
|
||||
border-color: #51808c;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 1.5px 1px #6bb8c4; }
|
||||
ul li > a .details {
|
||||
font-size: 0.7em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 5px;
|
||||
gap: 5px 20px; }
|
||||
ul li > a .details p {
|
||||
margin: 0; }
|
||||
|
|
|
@ -2,3 +2,5 @@ Django
|
|||
django-autoslug
|
||||
Pillow
|
||||
django-cas-ng
|
||||
django-haystack
|
||||
Whoosh
|
||||
|
|
Loading…
Reference in a new issue