diff --git a/inventory/static/inventory/css/main.css b/inventory/static/inventory/css/main.css index bbaf413..c8e4bb8 100644 --- a/inventory/static/inventory/css/main.css +++ b/inventory/static/inventory/css/main.css @@ -207,7 +207,6 @@ ul.tag-list { display: flex; flex-wrap: wrap; gap: 6px; - max-width: 500px; text-indent: 0; padding-left: 0; } @@ -291,4 +290,23 @@ table.list tbody td { .right { text-align: right; -} \ No newline at end of file +} + +#search-container { + display: none; + position: absolute; + background-color: #292981; + padding: 10px; + border: 1px solid white; + box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.8); +} + +.search-result em { + color: #c00000; +} + +.search-result .small { + font-size: 9pt; + font-weight: normal; + color: #808080; +} diff --git a/inventory/static/inventory/img/search.svg b/inventory/static/inventory/img/search.svg new file mode 100644 index 0000000..97cb287 --- /dev/null +++ b/inventory/static/inventory/img/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/inventory/templates/base.html b/inventory/templates/base.html index 0d507b9..2f65625 100644 --- a/inventory/templates/base.html +++ b/inventory/templates/base.html @@ -23,6 +23,29 @@ {% endblock %} diff --git a/inventory/templates/inventory/search.html b/inventory/templates/inventory/search.html new file mode 100644 index 0000000..53a0af3 --- /dev/null +++ b/inventory/templates/inventory/search.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Search{% endblock %} + +{% block header_bar %} + Search +{% endblock %} + +{% block content %} +
+ + +
+{% endblock %} \ No newline at end of file diff --git a/inventory/templates/inventory/search_result.html b/inventory/templates/inventory/search_result.html new file mode 100644 index 0000000..ee74ecd --- /dev/null +++ b/inventory/templates/inventory/search_result.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} +{% load static %} +{% load hilight %} + +{% block title %}Search{% endblock %} + +{% block header_bar %} + Search +{% endblock %} + +{% block content %} +
+ + +
+ +

Search result for '{{ q }}'

+ + {% for result in results %} +
+

{{ result.title | hilight:tokens }}

+ {{ result.text | safe }} +
+
+ {% empty %} +

Noting found

+ {% endfor %} + + {% if pages > 1 %} + + {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/inventory/templates/inventory/search_result_item.html b/inventory/templates/inventory/search_result_item.html new file mode 100644 index 0000000..ea8c9e3 --- /dev/null +++ b/inventory/templates/inventory/search_result_item.html @@ -0,0 +1,17 @@ +{% load static %} +{% load hilight %} +

+{% if item.documentation.all %} + +{% endif %} +{{ item | hilight:tokens }} +{{item.form_factor.name}} +

+

Contained in: {{ item.container.display_name }}

+ diff --git a/inventory/urls.py b/inventory/urls.py index 6576519..fcc3f68 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -33,7 +33,8 @@ from .views import ( DistributorView, ManufacturerView, IndexView, - TagView + TagView, + SearchView ) urlpatterns = [ @@ -52,5 +53,6 @@ urlpatterns = [ path('distributor/', DistributorView.as_view(), name='distributor-detail'), path('tags', TagListView.as_view(), name='tag-list'), path('tag/', TagView.as_view(), name='tag-detail'), + path('search', SearchView.as_view(), name='search'), path('', IndexView.as_view(), name='index') ] diff --git a/inventory/views/__init__.py b/inventory/views/__init__.py index 36ff231..a5ea7b5 100644 --- a/inventory/views/__init__.py +++ b/inventory/views/__init__.py @@ -6,6 +6,7 @@ from .manufacturer import ManufacturerView, ManufacturerListView from .workshop import WorkshopView, WorkshopListView from .index import IndexView from .tag import TagListView, TagView +from .search import SearchView __all__ = [ AreaView, AreaListView, @@ -15,5 +16,6 @@ __all__ = [ ManufacturerView, ManufacturerListView, WorkshopView, WorkshopListView, IndexView, - TagView, TagListView + TagView, TagListView, + SearchView ] \ No newline at end of file diff --git a/inventory/views/search.py b/inventory/views/search.py new file mode 100644 index 0000000..cc60b3e --- /dev/null +++ b/inventory/views/search.py @@ -0,0 +1,84 @@ +from django.contrib.auth.decorators import login_required +from django.utils.decorators import method_decorator +from django.views.generic import View +from django.shortcuts import render +from django.urls import reverse +from django.template.loader import get_template +from django.db.models import Q +from django.core.paginator import Paginator +from django.conf import settings + +from re import finditer + +from inventory.models import Item + +@method_decorator(login_required, name='dispatch') +class SearchView(View): + + def get(self, request): + page = int(request.GET.get('page', "1")) + query = request.GET.get('q', None) + if not query: + return render(request, "inventory/search.html") + + results: list[dict[str, str]] = [] + + tokens = query.split(" ") + tokens = map(str.lower, map(str.strip, tokens)) + + q: Q = None + item_template = get_template("inventory/search_result_item.html") + t: list[str] = [] + for token in tokens: + combiner = 'or' + if token.startswith("+"): + token = token[1:] + combiner = 'and' + elif token.startswith("-"): + token = token[1:] + combiner = 'and not' + + t.append(token) + + q1 = Q(name__icontains=f'{token}') + q2 = Q(description__icontains=f'{token}') + q3 = Q(tags__name__icontains=f'{token}') + q4 = Q(tags__description__icontains=f'{token}') + q5 = Q(form_factor__tags__name__icontains=f'{token}') + q6 = Q(form_factor__tags__description__icontains=f'{token}') + + qx = q1 | q2 | q3 | q4 | q5 | q6 + + if q == None: + q = qx + elif combiner == 'or': + q = q | qx + elif combiner == 'and': + q = q & qx + elif combiner == 'and not': + q = q & ~qx + items = Item.objects.filter(q).select_related("container", "form_factor").prefetch_related("tags", "documentation").distinct() + + # FIXME: Rank search results + + paginator = Paginator(items, getattr(settings, "PAGE_SIZE", 10)) + + for item in paginator.get_page(page): + text = item_template.render({ + "item": item, + "tokens": t + }) + result = { + "title": item.name, + "text": text, + "url": reverse('item-detail', kwargs={"pk": item.pk}) + } + results.append(result) + + return render(request, "inventory/search_result.html", { + "q": query, + "results": results, + "tokens": t, + "page": page, + "pages": paginator.num_pages + }) \ No newline at end of file