implement timerange
This commit is contained in:
parent
88c4eecb3b
commit
41e5f692f6
@ -39,41 +39,12 @@ def init_db():
|
||||
''')
|
||||
search_db.commit()
|
||||
# If the table already existed, try to add the new columns.
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN hitcount INTEGER DEFAULT 0")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN basefolder TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN category TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN titel TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN name TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN performance_date TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
try:
|
||||
cursor.execute("ALTER TABLE files ADD COLUMN site TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
# Likely the column already exists, so we ignore this error.
|
||||
pass
|
||||
# try:
|
||||
# cursor.execute("ALTER TABLE files ADD COLUMN category TEXT")
|
||||
# except sqlite3.OperationalError:
|
||||
# # Likely the column already exists, so we ignore this error.
|
||||
# pass
|
||||
|
||||
search_db.commit()
|
||||
|
||||
def scan_dir(directory):
|
||||
@ -200,16 +171,15 @@ def updatefileindex():
|
||||
date_match = re.search(r'(\d{1,2}\.\d{1,2}\.\d{2,4})', relative_path)
|
||||
if date_match:
|
||||
date_str = date_match.group(1)
|
||||
# Convert to YYYY-MM-DD format
|
||||
try:
|
||||
date_obj = datetime.strptime(date_str, '%d.%m.%Y')
|
||||
performance_date = date_obj.strftime('%d.%m.%Y')
|
||||
except ValueError:
|
||||
performance_date = None
|
||||
for fmt in ('%d.%m.%Y', '%d.%m.%y', '%Y-%m-%d'):
|
||||
try:
|
||||
date_obj = datetime.strptime(date_str, '%d.%m.%y')
|
||||
performance_date = date_obj.strftime('%d.%m.%Y')
|
||||
date_obj = datetime.strptime(date_str, fmt)
|
||||
# Convert to ISO format YYYY-MM-DD
|
||||
performance_date = date_obj.strftime('%Y-%m-%d')
|
||||
break
|
||||
except ValueError:
|
||||
performance_date = None
|
||||
continue
|
||||
else:
|
||||
performance_date = None
|
||||
|
||||
@ -236,7 +206,52 @@ def updatefileindex():
|
||||
|
||||
return "File index updated successfully"
|
||||
|
||||
def convert_dates(search_db,
|
||||
date_formats=('%d.%m.%Y', '%d.%m.%y')):
|
||||
"""
|
||||
Connects to the SQLite database at search_db, and for every row in table 'files':
|
||||
- Reads the date from performance_date (expects 'dd.mm.yyyy' or 'dd.mm.yy').
|
||||
- Parses it and reformats to ISO 'YYYY-MM-DD'.
|
||||
- Updates the row (using id as primary key).
|
||||
|
||||
Only counts rows where the conversion was successful.
|
||||
"""
|
||||
# Regex to quickly filter out non-matching strings
|
||||
date_regex = re.compile(r'^\d{1,2}\.\d{1,2}\.\d{2,4}$')
|
||||
|
||||
cur = search_db.cursor()
|
||||
|
||||
# Fetch all rows with a non-null date
|
||||
cur.execute("SELECT id, performance_date FROM files")
|
||||
rows = cur.fetchall()
|
||||
|
||||
converted_count = 0
|
||||
|
||||
for pk, raw_date in rows:
|
||||
if not raw_date or not date_regex.match(raw_date):
|
||||
continue
|
||||
|
||||
for fmt in date_formats:
|
||||
try:
|
||||
dt = datetime.strptime(raw_date, fmt)
|
||||
new_date = dt.strftime('%Y-%m-%d')
|
||||
# Only update if the reformatted date is different
|
||||
if new_date != raw_date:
|
||||
cur.execute(
|
||||
"UPDATE files SET performance_date = ? WHERE id = ?",
|
||||
(new_date, pk)
|
||||
)
|
||||
converted_count += 1
|
||||
break # stop trying other formats
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
search_db.commit()
|
||||
search_db.close()
|
||||
print(f"Converted {converted_count} rows to ISO format.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
convert_dates(search_db)
|
||||
init_db() # Initialize the database schema if it doesn't exist
|
||||
updatefileindex() # Update the file index
|
||||
search_db.close() # Close the search database connection
|
||||
|
||||
136
search.py
136
search.py
@ -17,87 +17,91 @@ def searchcommand():
|
||||
query = request.form.get("query", "").strip()
|
||||
category = request.form.get("category", "").strip()
|
||||
searchfolder = request.form.get("folder", "").strip()
|
||||
datefrom = request.form.get("datefrom", "").strip()
|
||||
dateto = request.form.get("dateto", "").strip()
|
||||
|
||||
include_transcript = request.form.get("includeTranscript") in ["true", "on"]
|
||||
words = [w for w in query.split() if w]
|
||||
cursor = search_db.cursor()
|
||||
|
||||
allowed_basefolders = list(session['folders'].keys())
|
||||
|
||||
# if the search folder is allowed to be searched, select it
|
||||
# if not just allowed_basefolders rules apply
|
||||
if searchfolder != "" and searchfolder in allowed_basefolders:
|
||||
# Determine allowed basefolders
|
||||
allowed_basefolders = list(session['folders'].keys())
|
||||
if searchfolder and searchfolder in allowed_basefolders:
|
||||
allowed_basefolders = [searchfolder]
|
||||
|
||||
if not include_transcript:
|
||||
conditions = []
|
||||
params = []
|
||||
# Apply query words to relative_path and filename
|
||||
for word in words:
|
||||
conditions.append("(relative_path LIKE ? OR filename LIKE ?)")
|
||||
params.extend([f"%{word}%", f"%{word}%"])
|
||||
# Search category in filename
|
||||
if category:
|
||||
conditions.append("(filename LIKE ?)")
|
||||
params.extend([f"%{category}%"])
|
||||
# Only include rows where basefolder is in allowed_basefolders
|
||||
if allowed_basefolders:
|
||||
placeholders = ",".join("?" for _ in allowed_basefolders)
|
||||
conditions.append(f"basefolder IN ({placeholders})")
|
||||
params.extend(allowed_basefolders)
|
||||
|
||||
sql = "SELECT * FROM files"
|
||||
if conditions:
|
||||
sql += " WHERE " + " AND ".join(conditions)
|
||||
|
||||
cursor.execute(sql, params)
|
||||
raw_results = cursor.fetchall()
|
||||
results = [dict(row) for row in raw_results]
|
||||
|
||||
# Randomize the list before sorting to break ties randomly.
|
||||
random.shuffle(results)
|
||||
results.sort(key=lambda x: x["hitcount"], reverse=True)
|
||||
# Build conditions and parameters
|
||||
conditions = []
|
||||
params = []
|
||||
|
||||
# Choose fields for word search
|
||||
if include_transcript:
|
||||
fields = ['filename', 'transcript']
|
||||
else:
|
||||
# Advanced search: include transcript. Count transcript hits.
|
||||
conditions = []
|
||||
params = []
|
||||
# Apply query words for filename and transcript
|
||||
for word in words:
|
||||
conditions.append("(filename LIKE ? OR transcript LIKE ?)")
|
||||
params.extend([f"%{word}%", f"%{word}%"])
|
||||
# Search category in filename
|
||||
if category:
|
||||
conditions.append("(filename LIKE ?)")
|
||||
params.extend([f"%{category}%"])
|
||||
# Only include rows where basefolder is in allowed_basefolders
|
||||
if allowed_basefolders:
|
||||
placeholders = ",".join("?" for _ in allowed_basefolders)
|
||||
conditions.append(f"basefolder IN ({placeholders})")
|
||||
params.extend(allowed_basefolders)
|
||||
fields = ['relative_path', 'filename']
|
||||
|
||||
sql = "SELECT * FROM files"
|
||||
if conditions:
|
||||
sql += " WHERE " + " AND ".join(conditions)
|
||||
cursor.execute(sql, params)
|
||||
raw_results = cursor.fetchall()
|
||||
for word in words:
|
||||
field_clauses = [f"{f} LIKE ?" for f in fields]
|
||||
conditions.append(f"({ ' OR '.join(field_clauses) })")
|
||||
for _ in fields:
|
||||
params.append(f"%{word}%")
|
||||
|
||||
results = []
|
||||
for row in raw_results:
|
||||
result = dict(row)
|
||||
transcript = result.get("transcript") or ""
|
||||
total_hits = sum(transcript.lower().count(word.lower()) for word in words)
|
||||
result["transcript_hits"] = total_hits
|
||||
result.pop("transcript", None)
|
||||
results.append(result)
|
||||
|
||||
# Randomize the list before sorting to break ties randomly.
|
||||
random.shuffle(results)
|
||||
results.sort(key=lambda x: x["transcript_hits"], reverse=True)
|
||||
# Category filter
|
||||
if category:
|
||||
conditions.append("filename LIKE ?")
|
||||
params.append(f"%{category}%")
|
||||
|
||||
# Basefolder filter
|
||||
if allowed_basefolders:
|
||||
placeholders = ",".join("?" for _ in allowed_basefolders)
|
||||
conditions.append(f"basefolder IN ({placeholders})")
|
||||
params.extend(allowed_basefolders)
|
||||
|
||||
# Date range filters
|
||||
if datefrom:
|
||||
try:
|
||||
conditions.append("performance_date >= ?")
|
||||
params.append(datefrom)
|
||||
except ValueError:
|
||||
pass
|
||||
if dateto:
|
||||
try:
|
||||
conditions.append("performance_date <= ?")
|
||||
params.append(dateto)
|
||||
except ValueError:
|
||||
pass
|
||||
# Ensure we only include entries with dates when filtering by date
|
||||
if datefrom or dateto:
|
||||
conditions.append("performance_date IS NOT NULL")
|
||||
|
||||
# Build and execute SQL
|
||||
sql = "SELECT * FROM files"
|
||||
if conditions:
|
||||
sql += " WHERE " + " AND ".join(conditions)
|
||||
cursor.execute(sql, params)
|
||||
raw_results = cursor.fetchall()
|
||||
|
||||
# Process results
|
||||
results = []
|
||||
for row in raw_results:
|
||||
record = dict(row)
|
||||
if include_transcript:
|
||||
transcript = record.get('transcript', '') or ''
|
||||
record['transcript_hits'] = sum(
|
||||
transcript.lower().count(w.lower()) for w in words
|
||||
)
|
||||
record.pop('transcript', None)
|
||||
results.append(record)
|
||||
|
||||
# Randomize and sort
|
||||
random.shuffle(results)
|
||||
key = 'transcript_hits' if include_transcript else 'hitcount'
|
||||
results.sort(key=lambda x: x.get(key, 0), reverse=True)
|
||||
|
||||
# Limit results
|
||||
results = results[:100]
|
||||
return jsonify(results=results)
|
||||
|
||||
|
||||
def search():
|
||||
allowed_basefolders = list(session['folders'].keys())
|
||||
title_short = app_config.get('TITLE_SHORT', 'Default Title')
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
<title>{% block title %}Meine Links{% endblock %}</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='theme.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
|
||||
<!-- Bootstrap CSS for modern styling -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<!-- Your CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='theme.css') }}">
|
||||
@ -50,70 +51,116 @@
|
||||
</a>
|
||||
<h1>{{ title_long }}</h1>
|
||||
</header>
|
||||
|
||||
<div class="search-container">
|
||||
<h1>Suche</h1>
|
||||
<form id="searchForm" method="post" class="mb-4">
|
||||
|
||||
<!-- Suchwörter -->
|
||||
<div class="mb-3">
|
||||
<label for="query" class="h5 form-label">Suchwörter:</label>
|
||||
<input type="text" id="query" name="query" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<!-- Toggle für Suchoptionen -->
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<h2 class="h5 mb-0">Suchoptionen</h2>
|
||||
<button class="btn btn-sm btn-link p-0"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#searchOptions"
|
||||
aria-expanded="false"
|
||||
aria-controls="searchOptions"
|
||||
aria-label="Toggle Suchoptionen">
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Suchoptionen einklappbar -->
|
||||
<div id="searchOptions" class="collapse border rounded p-3 mb-3">
|
||||
|
||||
<!-- Kategorie -->
|
||||
<div class="mb-3">
|
||||
<label for="query" class="form-label">Suchwörter:</label>
|
||||
<input type="text" id="query" name="query" class="form-control" required>
|
||||
</div>
|
||||
<!-- Radio Button for Kategorie -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Kategorie:</label>
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<!-- "Alles" is default if nothing is selected -->
|
||||
<input class="form-check-input" type="radio" name="category" id="none" value="" checked>
|
||||
<label class="form-check-label" for="none">Alles</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="lied" value="Lied">
|
||||
<label class="form-check-label" for="lied">Lied</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="gedicht" value="Gedicht">
|
||||
<label class="form-check-label" for="gedicht">Gedicht</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="predigt" value="Predigt">
|
||||
<label class="form-check-label" for="predigt">Predigt</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="chor" value="chor">
|
||||
<label class="form-check-label" for="chor">Chor</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="orchester" value="orchester">
|
||||
<label class="form-check-label" for="orchester">Orchester</label>
|
||||
</div>
|
||||
<label class="form-label">Kategorie:</label>
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="none" value="" checked>
|
||||
<label class="form-check-label" for="none">Alles</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="lied" value="Lied">
|
||||
<label class="form-check-label" for="lied">Lied</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="gedicht" value="Gedicht">
|
||||
<label class="form-check-label" for="gedicht">Gedicht</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="predigt" value="Predigt">
|
||||
<label class="form-check-label" for="predigt">Predigt</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="chor" value="chor">
|
||||
<label class="form-check-label" for="chor">Chor</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="category" id="orchester" value="orchester">
|
||||
<label class="form-check-label" for="orchester">Orchester</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dropdown search folder-->
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- In Ordner suchen -->
|
||||
<div class="mb-3">
|
||||
<label for="folder" class="form-label">In Ordner suchen:</label>
|
||||
<select id="folder" name="folder" class="form-select">
|
||||
<option value="">Alle</option>
|
||||
{% for folder in search_folders %}
|
||||
<option value="{{ folder }}">{{ folder }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="folder" class="form-label">In Ordner suchen:</label>
|
||||
<select id="folder" name="folder" class="form-select">
|
||||
<option value="">Alle</option>
|
||||
{% for folder in search_folders %}
|
||||
<option value="{{ folder }}">{{ folder }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<!-- Checkbox for transcript search -->
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Transkript durchsuchen -->
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="includeTranscript" name="includeTranscript">
|
||||
<label class="form-check-label" for="includeTranscript">Im Transkript suchen</label>
|
||||
<input type="checkbox" class="form-check-input" id="includeTranscript" name="includeTranscript">
|
||||
<label class="form-check-label" for="includeTranscript">Im Transkript suchen</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Suchen</button>
|
||||
<!-- Clear Button -->
|
||||
<button type="button" id="clearBtn" class="btn btn-secondary ms-2">neue Suche</button>
|
||||
<!-- Back Button -->
|
||||
<button type="button" id="backBtn" class="btn btn-secondary ms-2">zurück</button>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Zeitraum -->
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="datefrom" class="form-label">Datum von:</label>
|
||||
<input type="date" id="datefrom" name="datefrom" class="form-control">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="dateto" class="form-label">Datum bis:</label>
|
||||
<input type="date" id="dateto" name="dateto" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Ende Suchoptionen -->
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Suchen</button>
|
||||
<button type="button" id="clearBtn" class="btn btn-secondary ms-2">zurücksetzen</button>
|
||||
<button type="button" id="backBtn" class="btn btn-secondary ms-2">beenden</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<!-- Container for AJAX-loaded results -->
|
||||
|
||||
<!-- AJAX-loaded results -->
|
||||
<div id="results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@ -209,6 +256,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
formData.append('query', query);
|
||||
formData.append('category', category);
|
||||
formData.append('folder', document.getElementById('folder').value);
|
||||
formData.append('datefrom', document.getElementById('datefrom').value);
|
||||
formData.append('dateto', document.getElementById('dateto').value);
|
||||
formData.append('includeTranscript', includeTranscript);
|
||||
|
||||
fetch('/searchcommand', {
|
||||
@ -241,12 +290,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
localStorage.removeItem("searchResponse");
|
||||
localStorage.removeItem("searchQuery");
|
||||
localStorage.removeItem("searchCategory");
|
||||
localStorage.removeItem("folder");
|
||||
localStorage.removeItem("datefrom");
|
||||
localStorage.removeItem("dateto");
|
||||
localStorage.removeItem("searchIncludeTranscript");
|
||||
// Reset form fields to defaults
|
||||
document.getElementById('query').value = '';
|
||||
document.querySelector('input[name="category"][value=""]').checked = true;
|
||||
const otherRadios = document.querySelectorAll('input[name="category"]:not([value=""])');
|
||||
otherRadios.forEach(radio => radio.checked = false);
|
||||
document.getElementById('folder').value = ''; // Reset to "Alle"
|
||||
document.getElementById('datefrom').value = ''; // Reset date from
|
||||
document.getElementById('dateto').value = ''; // Reset date to
|
||||
document.getElementById('includeTranscript').checked = false;
|
||||
// Clear the results div
|
||||
document.getElementById('results').innerHTML = '';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user