diff --git a/server.py b/server.py index 1b087d10a84e7900492300bf15f27ba06f6ba64e..57bfc4ff335f998198e76eff54cc832f4c9ce90c 100755 --- a/server.py +++ b/server.py @@ -8,6 +8,7 @@ from flask_script import Manager, prompt from flask_migrate import Migrate, MigrateCommand #from flask_socketio import SocketIO from celery import Celery +from sqlalchemy import or_, and_ from io import StringIO, BytesIO import os from datetime import datetime @@ -297,24 +298,74 @@ def list_protocols(): if search_term is not None: search_form.search.data = search_term protocol_query = Protocol.query - if search_term is not None: - match_target = Protocol.content_public - if protocoltype is not None and protocoltype.has_private_view_right(user): - match_target = Protocol.content_private - for term in split_terms(search_term): - protocol_query = protocol_query.filter(match_target.match("%{}%".format(term))) + shall_search = search_term is not None and len(search_term.strip()) > 0 + search_terms = [] + if shall_search: + search_terms = list(map(str.lower, split_terms(search_term))) + for term in search_terms: + protocol_query = protocol_query.filter(or_( + Protocol.content_public.ilike("%{}%".format(term)), + Protocol.content_private.ilike("%{}%".format(term)) + )) protocols = [ protocol for protocol in protocol_query.all() if (not is_logged_in and protocol.protocoltype.is_public) or (is_logged_in and ( protocol.protocoltype.public_group in user.groups or protocol.protocoltype.private_group in user.groups))] + def _matches_search(content): + content = content.lower() + for search_term in search_terms: + if search_term.lower() not in content: + return False + return True + def _matches_search_lazy(content): + content = content.lower() + for search_term in search_terms: + if search_term.lower() in content: + return True + return False + search_results = {} if shall_search else None if protocoltype_id is not None and protocoltype_id != -1: protocols = [ protocol for protocol in protocols if protocol.protocoltype.id == protocoltype_id ] - protocols = sorted(protocols, key=lambda protocol: protocol.date, reverse=True) + if shall_search: + protocols = [ + protocol for protocol in protocols + if (protocol.protocoltype.has_private_view_right(user) + and _matches_search(protocol.content_private)) + or (protocol.protocoltype.has_public_view_right(user) + and _matches_search(protocol.content_public)) + ] + for protocol in protocols: + content = protocol.content_private if protocol.protocoltype.has_private_view_right(user) else protocol.content_public + lines = content.splitlines() + matches = [line for line in lines if _matches_search_lazy(line)] + formatted_lines = [] + for line in matches: + parts = [] + lower_line = line.lower() + last_index = 0 + while last_index < len(line): + index_candidates = list(filter(lambda t: t[0] != -1, + [(lower_line.find(term, last_index), term) for term in search_terms])) + if len(index_candidates) == 0: + parts.append((line[last_index:], False)) + break + else: + new_index, term = min(index_candidates, key=lambda t: t[0]) + new_end_index = new_index + len(term) + parts.append((line[last_index:new_index], False)) + parts.append((line[new_index:new_end_index], True)) + last_index = new_end_index + formatted_lines.append("".join([ + "<b>{}</b>".format(text) if matched else text + for text, matched in parts + ])) + search_results[protocol] = "<br />\n".join(formatted_lines) + protocols = sorted(protocols, key=lambda protocol: protocol.date, reverse=True) page = _get_page() page_count = int(math.ceil(len(protocols)) / config.PAGE_LENGTH) if page >= page_count: @@ -322,7 +373,7 @@ def list_protocols(): begin_index = page * config.PAGE_LENGTH end_index = (page + 1) * config.PAGE_LENGTH protocols = protocols[begin_index:end_index] - protocols_table = ProtocolsTable(protocols) + protocols_table = ProtocolsTable(protocols, search_results=search_results) return render_template("protocols-list.html", protocols=protocols, protocols_table=protocols_table, search_form=search_form, page=page, page_count=page_count, page_diff=config.PAGE_DIFF, protocoltype_id=protocoltype_id, search_term=search_term) @app.route("/protocol/new", methods=["GET", "POST"]) diff --git a/views/tables.py b/views/tables.py index 778e4235e3c9ae73ab4748a366e6e8e47c869bab..4a44cbb589844a58b7658979426f340b64eab987 100644 --- a/views/tables.py +++ b/views/tables.py @@ -48,14 +48,21 @@ class SingleValueTable: return [self.row()] class ProtocolsTable(Table): - def __init__(self, protocols): + def __init__(self, protocols, search_results=None): super().__init__("Protokolle", protocols, newlink=url_for("new_protocol")) + self.search_results = search_results def headers(self): - result = ["ID", "Sitzung", "Datum", "Status"] - optional_part = ["Typ", "Löschen"] + result = ["ID", "Sitzung", "Datum"] + state_part = ["Status"] + search_part = ["Suchergebnis"] + login_part = ["Typ", "Löschen"] + if self.search_results is None: + result.extend(state_part) + else: + result.extend(search_part) if check_login(): - result += optional_part + result.extend(login_part) return result def row(self, protocol): @@ -64,8 +71,11 @@ class ProtocolsTable(Table): Table.link(url_for("show_protocol", protocol_id=protocol.id), str(protocol.id)), Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.protocoltype.name), date_filter(protocol.date), - "Fertig" if protocol.is_done() else "Geplant" ] + if self.search_results is None: + result.append("Fertig" if protocol.is_done() else "Geplant") + elif protocol in self.search_results: + result.append(Markup(self.search_results[protocol])) if user is not None and protocol.protocoltype.has_private_view_right(user): result.append(Table.link(url_for("show_type", type_id=protocol.protocoltype.id), protocol.protocoltype.short_name)) result.append(Table.link(url_for("delete_protocol", protocol_id=protocol.id), "Löschen", confirm="Bist du dir sicher, dass du das Protokoll {} löschen möchtest?".format(protocol.get_identifier())))