"""Journal-Routen: Index/Dashboard, Future Log, Wochen-/Monatsübersicht, Tagesansicht. Enthält auch Helfer für Wochen-/Monatsberechnung, Export (CSV) und Notiz-Aktionen (erledigt markieren, in anderen Tag / Future Log verschieben). """ import csv import io import json import calendar from datetime import date, datetime, timedelta from urllib.parse import urlencode from flask import ( Blueprint, render_template, request, redirect, url_for, session, flash, Response, ) from auth import login_required from db import get_connection journal_bp = Blueprint("journal", __name__) # Deutsche Wochentage / Monate WEEKDAYS = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] MONTHS = [ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ] BULLET_LABELS = { ".": "Aufgabe", "x": "Termin", "-": "Notiz", "?": "Frage", } def _user_id() -> int: return session["user_id"] def monday_of_week(d: date) -> date: """Liefert den Montag der Woche, in der d liegt.""" return d - timedelta(days=d.weekday()) def get_week_days(monday: date) -> list[date]: return [monday + timedelta(days=i) for i in range(7)] def parse_date(s: str) -> date | None: try: return datetime.strptime(s, "%Y-%m-%d").date() except (ValueError, TypeError): return None def _fetch_notes(user_id: int, note_date: date) -> list[dict]: with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s AND note_date = %s " "ORDER BY id ASC", (user_id, note_date), ) return cur.fetchall() def _active_notes(user_id: int, note_date: date) -> list[dict]: return [n for n in _fetch_notes(user_id, note_date) if n["status"] != "moved"] def parse_refs(raw) -> list[dict]: """Parst den JSON-Spaltenwert der Verweise in eine Liste.""" if not raw: return [] if isinstance(raw, (list, dict)): return raw if isinstance(raw, list) else [raw] try: data = json.loads(raw) return data if isinstance(data, list) else [] except (ValueError, TypeError): return [] REF_TYPE_DISPLAY = { "email": "📧", "link": "🔗", "phone": "📞", "mention": "@", } def ref_type_label(ref_type: str) -> str: return REF_TYPE_DISPLAY.get(ref_type, "🔖") def ref_href(ref_type: str, value: str) -> str: value = (value or "").strip() if ref_type == "email": return f"mailto:{value}" if ref_type == "phone": return f"tel:{value.replace(' ', '')}" if ref_type == "mention": return f"mailto:{value}" # link und Standard if value.startswith(("http://", "https://")): return value return f"https://{value}" def bullet_label(bullet_type: str) -> str: return BULLET_LABELS.get(bullet_type, "Notiz") PRIORITY_LABELS = {0: "Niedrig", 1: "Mittel", 2: "Hoch"} STATUS_LABELS = {"open": "Offen", "done": "Erledigt", "moved": "Verschoben"} def _report_filters() -> dict: """Liest die Report-Filter aus request.args (GET-Formular/Export-Links).""" args = request.args range_type = args.get("range", "week") if range_type == "month": month_param = args.get("month", "") try: y, mo = (int(x) for x in month_param.split("-")) start = date(y, mo, 1) except (ValueError, TypeError): start = date.today().replace(day=1) end = date(start.year, start.month, calendar.monthrange(start.year, start.month)[1]) label = start.strftime("%B %Y") elif range_type == "custom": start = parse_date(args.get("from", "")) end = parse_date(args.get("to", "")) if not start or not end or end < start: start = monday_of_week(date.today()) end = start + timedelta(days=6) label = f"{start.strftime('%d.%m.%Y')} – {end.strftime('%d.%m.%Y')}" range_type = "custom" else: # week week_param = args.get("week", "") try: iso_year, iso_week = week_param.split("-W") start = date.fromisocalendar(int(iso_year), int(iso_week), 1) except (ValueError, TypeError): start = monday_of_week(date.today()) end = start + timedelta(days=6) label = f"Woche {start.strftime('%d.%m.%Y')} – {end.strftime('%d.%m.%Y')}" # Filter (leere Auswahl = alle zulassen) weekdays = [int(x) for x in args.getlist("wd")] bullets = [b for b in args.getlist("bullet") if b in BULLET_LABELS] statuses = [s for s in args.getlist("status") if s in STATUS_LABELS] priorities = [int(p) for p in args.getlist("priority") if p.isdigit()] if not weekdays: weekdays = list(range(7)) if not bullets: bullets = list(BULLET_LABELS.keys()) if not statuses: statuses = ["open", "done"] if not priorities: priorities = [0, 1, 2] return { "range_type": range_type, "start": start, "end": end, "label": label, "weekdays": weekdays, "bullets": bullets, "statuses": statuses, "priorities": priorities, } def _run_report(uid: int, f: dict) -> tuple[list[dict], dict]: """Führt den Report aus und liefert (rows, summary).""" with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s " "AND note_date BETWEEN %s AND %s " "ORDER BY note_date ASC, id ASC", (uid, f["start"], f["end"]), ) rows = cur.fetchall() filtered = [] for n in rows: if n["note_date"].weekday() not in f["weekdays"]: continue if n["bullet_type"] not in f["bullets"]: continue if n["status"] not in f["statuses"]: continue if n["priority"] not in f["priorities"]: continue filtered.append(n) rows_out = [] for n in filtered: refs = ", ".join( f"{ref_type_label(r.get('type', ''))} {r.get('value', '')}" for r in parse_refs(n.get("refs")) ) rows_out.append({ "id": n["id"], "date_str": n["note_date"].strftime("%d.%m.%Y"), "iso": n["note_date"].strftime("%Y-%m-%d"), "weekday": WEEKDAYS[n["note_date"].weekday()], "bullet": n["bullet_type"], "bullet_label": bullet_label(n["bullet_type"]), "note_text": n["note_text"], "priority": PRIORITY_LABELS.get(n.get("priority", 0), str(n.get("priority", ""))), "status": STATUS_LABELS.get(n.get("status"), n.get("status", "")), "status_code": n.get("status"), "refs": refs, }) summary = { "total": len(rows_out), "open": sum(1 for r in rows_out if r["status_code"] == "open"), "done": sum(1 for r in rows_out if r["status_code"] == "done"), } return rows_out, summary @journal_bp.route("/") @login_required def index(): """Dashboard: aktuelle Woche + offene Aufgaben.""" today = date.today() monday = monday_of_week(today) week_days = get_week_days(monday) # Detaillierte Info pro Tag day_summary = [] for d in week_days: notes = _active_notes(_user_id(), d) day_summary.append( { "date": d, "weekday": WEEKDAYS[d.weekday()], "open": sum(1 for n in notes if n["status"] == "open"), "done": sum(1 for n in notes if n["status"] == "done"), "total": len(notes), } ) # Offene Aufgaben außerhalb dieser Woche with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s AND status = 'open' " "AND note_date < %s ORDER BY note_date ASC LIMIT 50", (_user_id(), monday), ) overdue = cur.fetchall() return render_template( "index.html", today=today, monday=monday, day_summary=day_summary, overdue=overdue, weekdays=WEEKDAYS, ) @journal_bp.route("/future", methods=["GET", "POST"]) @login_required def future(): if request.method == "POST": note_text = request.form.get("note_text", "").strip() month_str = request.form.get("month", "") month = parse_date(month_str) if note_text and month: with get_connection() as conn: with conn.cursor() as cur: cur.execute( "INSERT INTO future_log (user_id, month_date, note_text) " "VALUES (%s, %s, %s)", (_user_id(), month, note_text), ) flash("Eintrag ins Future Log hinzugefügt.", "success") else: flash("Bitte Text und Monat angeben.", "danger") return redirect(url_for("journal.future")) with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM future_log WHERE user_id = %s ORDER BY month_date ASC, id ASC", (_user_id(),), ) entries = cur.fetchall() # Gruppieren nach Monat grouped = {} for e in entries: key = e["month_date"].strftime("%Y-%m") grouped.setdefault(key, []).append(e) return render_template("future.html", grouped=grouped, months=MONTHS) @journal_bp.route("/woche") @login_required def week(): """Wochenübersicht: Mon–So mit allen Einzeleinträgen pro Tag.""" week_param = request.args.get("week", "") if week_param: try: iso_year, iso_week = week_param.split("-W") monday = date.fromisocalendar(int(iso_year), int(iso_week), 1) except (ValueError, TypeError): monday = monday_of_week(date.today()) else: monday = monday_of_week(date.today()) week_days = get_week_days(monday) sunday = monday + timedelta(days=6) # Alle Notizen der Woche holen (inkl. erledigter) und nach Tag gruppieren with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s " "AND note_date BETWEEN %s AND %s ORDER BY note_date ASC, id ASC", (_user_id(), monday, sunday), ) rows = cur.fetchall() by_date: dict[date, list[dict]] = {d: [] for d in week_days} for n in rows: by_date.setdefault(n["note_date"], []).append(n) days_detail = [] for d in week_days: notes = by_date.get(d, []) active = [n for n in notes if n["status"] != "moved"] days_detail.append( { "date": d, "weekday": WEEKDAYS[d.weekday()], "open": sum(1 for n in active if n["status"] == "open"), "done": sum(1 for n in active if n["status"] == "done"), "moved": sum(1 for n in notes if n["status"] == "moved"), "total": len(active), "notes": active, } ) prev_monday = monday - timedelta(days=7) next_monday = monday + timedelta(days=7) return render_template( "week.html", monday=monday, sunday=sunday, days_detail=days_detail, weekdays=WEEKDAYS, prev_monday=prev_monday, next_monday=next_monday, bullet_labels=BULLET_LABELS, ) @journal_bp.route("/monat") @login_required def month(): """Monatsübersicht: Kalenderraster + Liste aller Einträge des Monats.""" month_param = request.args.get("month", "") if month_param: try: year, mon = (int(x) for x in month_param.split("-")) first = date(year, mon, 1) except (ValueError, TypeError): first = date.today().replace(day=1) else: first = date.today().replace(day=1) last = date(first.year, first.month, calendar.monthrange(first.year, first.month)[1]) # Alle Notizen des Monats holen with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s " "AND note_date BETWEEN %s AND %s ORDER BY note_date ASC, id ASC", (_user_id(), first, last), ) rows = cur.fetchall() by_date: dict[int, list[dict]] = {} for n in rows: if n["status"] != "moved": by_date.setdefault(n["note_date"].day, []).append(n) # Kalenderraster aufbauen (Wochen Montag–Sonntag) start_pad = first.weekday() # 0=Montag start = first - timedelta(days=start_pad) weeks: list[list[dict]] = [] current = start for _ in range(6): week_cells = [] for i in range(7): d = current + timedelta(days=i) in_month = d.month == first.month week_cells.append( { "date": d, "in_month": in_month, "today": d == date.today(), "notes": by_date.get(d.day, []) if in_month else [], } ) weeks.append(week_cells) current += timedelta(days=7) if current > last + timedelta(days=6): break now_first = date.today().replace(day=1) prev_month = (first.replace(day=1) - timedelta(days=1)).replace(day=1) next_month = (first.replace(day=28) + timedelta(days=7)).replace(day=1) return render_template( "monat.html", first=first, last=last, weeks=weeks, weekdays=WEEKDAYS, prev_month=prev_month, next_month=next_month, all_notes=rows, ) def _csv_response(title: str, filename: str, rows: list[dict]) -> Response: """Baut eine CSV-Datei als HTTP-Antwort zusammen. Trennzeichen: Semikolon (deutsches Excel erwartet ';' statt Komma). """ output = io.StringIO() writer = csv.writer(output, delimiter=";") writer.writerow([ "Datum", "Wochentag", "Bullet", "Typ", "Notiz", "Priorität", "Status", "Verweise", ]) for n in rows: refs = ", ".join( f"{ref_type_label(r.get('type', ''))} {r.get('value', '')}" for r in parse_refs(n.get("refs")) ) writer.writerow([ n["note_date"].strftime("%d.%m.%Y"), WEEKDAYS[n["note_date"].weekday()], n.get("bullet_type", ""), bullet_label(n.get("bullet_type", "-")), n.get("note_text", ""), n.get("priority", 0), {"open": "Offen", "done": "Erledigt", "moved": "Verschoben"}.get( n.get("status"), n.get("status", "") ), refs, ]) output.seek(0) return Response( "\ufeff" + output.getvalue(), # BOM für Excel mimetype="text/csv; charset=utf-8", headers={"Content-Disposition": f"attachment; filename={filename}.csv"}, ) @journal_bp.route("/woche/export") @login_required def export_week(): week_param = request.args.get("week", "") if week_param: try: iso_year, iso_week = week_param.split("-W") monday = date.fromisocalendar(int(iso_year), int(iso_week), 1) except (ValueError, TypeError): monday = monday_of_week(date.today()) else: monday = monday_of_week(date.today()) sunday = monday + timedelta(days=6) with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s " "AND note_date BETWEEN %s AND %s ORDER BY note_date ASC, id ASC", (_user_id(), monday, sunday), ) rows = cur.fetchall() filename = f"woche_{monday.strftime('%Y-%m-%d')}" return _csv_response("Wochenübersicht", filename, rows) @journal_bp.route("/monat/export") @login_required def export_month(): month_param = request.args.get("month", "") if month_param: try: year, mon = (int(x) for x in month_param.split("-")) first = date(year, mon, 1) except (ValueError, TypeError): first = date.today().replace(day=1) else: first = date.today().replace(day=1) last = date(first.year, first.month, calendar.monthrange(first.year, first.month)[1]) with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE user_id = %s " "AND note_date BETWEEN %s AND %s ORDER BY note_date ASC, id ASC", (_user_id(), first, last), ) rows = cur.fetchall() filename = f"monat_{first.strftime('%Y-%m')}" return _csv_response("Monatsübersicht", filename, rows) @journal_bp.route("/report") @login_required def report(): """Report-Generator: dynamischer Wochen-/Monatsbericht mit Filtern.""" filters = _report_filters() rows, summary = _run_report(_user_id(), filters) today = date.today() # Für Export-Links: aktuelle Filter als Query-String aufbauen # (urlencode erhält wiederholte Parameter wie wd=0&wd=1 – dict() würde sie kollabieren) def _qs(fmt): params = [("range", filters["range_type"]), ("format", fmt)] params += [("wd", str(wd)) for wd in filters["weekdays"]] params += [("bullet", b) for b in filters["bullets"]] params += [("status", s) for s in filters["statuses"]] params += [("priority", str(p)) for p in filters["priorities"]] if filters["range_type"] == "month": params.append(("month", filters["start"].strftime("%Y-%m"))) elif filters["range_type"] == "custom": params.append(("from", filters["start"].strftime("%Y-%m-%d"))) params.append(("to", filters["end"].strftime("%Y-%m-%d"))) else: params.append(("week", filters["start"].strftime("%G-W%V"))) return url_for("journal.report_export") + "?" + urlencode(params) export_csv_url = _qs("csv") export_pdf_url = _qs("pdf") current_week = monday_of_week(today).strftime("%G-W%V") current_month = today.strftime("%Y-%m") filters["week_value"] = current_week filters["from_value"] = filters["start"].strftime("%Y-%m-%d") if filters["range_type"] == "custom" else "" filters["to_value"] = filters["end"].strftime("%Y-%m-%d") if filters["range_type"] == "custom" else "" return render_template( "report.html", f=filters, rows=rows, summary=summary, weekdays=WEEKDAYS, bullet_labels=BULLET_LABELS, current_week=current_week, current_month=current_month, today_str=today.strftime("%Y-%m-%d"), export_csv_url=export_csv_url, export_pdf_url=export_pdf_url, ) @journal_bp.route("/report/export") @login_required def report_export(): """Export des Report-Generators als CSV oder PDF (format=pdf|csv).""" filters = _report_filters() rows, summary = _run_report(_user_id(), filters) fmt = request.args.get("format", "csv") safe_name = filters["label"].replace(" ", "_").replace("–", "-") filename = f"report_{safe_name}" if fmt == "pdf": from pdf_export import build_pdf pdf_bytes = build_pdf( title="Bullet-Journal-Report", subtitle=filters["label"], rows=rows, summary=summary, ) return Response( pdf_bytes, mimetype="application/pdf", headers={ "Content-Disposition": f"attachment; filename={filename}.pdf" }, ) # CSV – Semikolon als Trennzeichen (deutsches Excel erwartet ';') output = io.StringIO() writer = csv.writer(output, delimiter=";") writer.writerow([ "Datum", "Wochentag", "Bullet", "Typ", "Notiz", "Priorität", "Status", "Verweise", ]) for r in rows: writer.writerow([ r["date_str"], r["weekday"], r["bullet"], r["bullet_label"], r["note_text"], r["priority"], r["status"], r["refs"], ]) output.seek(0) return Response( "\ufeff" + output.getvalue(), mimetype="text/csv; charset=utf-8", headers={"Content-Disposition": f"attachment; filename={filename}.csv"}, ) @journal_bp.route("/note//edit", methods=["GET", "POST"]) @login_required def edit_note(note_id: int): """Bearbeitet eine Notiz (Text, Bullet, Priorität, Verweise).""" with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE id = %s AND user_id = %s", (note_id, _user_id()), ) note = cur.fetchone() if not note: flash("Notiz nicht gefunden.", "danger") return redirect(url_for("journal.index")) if request.method == "POST": bullet = request.form.get("bullet", "-") note_text = request.form.get("note_text", "").strip() try: priority = int(request.form.get("priority", "0")) except ValueError: priority = 0 ref_type = request.form.get("ref_type", "").strip() ref_value = request.form.get("ref_value", "").strip() refs = None if ref_value: refs = json.dumps([{"type": ref_type or "link", "value": ref_value}]) if bullet not in (".", "x", "-", "?"): bullet = "-" if not note_text: flash("Notiz darf nicht leer sein.", "danger") else: with get_connection() as conn: with conn.cursor() as cur: cur.execute( "UPDATE notes SET bullet_type = %s, note_text = %s, " "priority = %s, refs = %s WHERE id = %s AND user_id = %s", (bullet, note_text, priority, refs, note_id, _user_id()), ) flash("Notiz aktualisiert.", "success") return redirect(url_for("journal.day", date_str=note["note_date"].strftime("%Y-%m-%d"))) refs = parse_refs(note.get("refs")) current_ref = refs[0] if refs else {} return render_template( "edit_note.html", note=note, bullet_labels=BULLET_LABELS, current_ref=current_ref, ) @journal_bp.route("/note//delete", methods=["POST"]) @login_required def delete_note(note_id: int): with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT note_date FROM notes WHERE id = %s AND user_id = %s", (note_id, _user_id()), ) note = cur.fetchone() if not note: flash("Notiz nicht gefunden.", "danger") return redirect(url_for("journal.index")) cur.execute( "DELETE FROM notes WHERE id = %s AND user_id = %s", (note_id, _user_id()), ) flash("Notiz gelöscht.", "success") return redirect(request.referrer or url_for("journal.index")) @journal_bp.route("/future//edit", methods=["GET", "POST"]) @login_required def edit_future(entry_id: int): with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM future_log WHERE id = %s AND user_id = %s", (entry_id, _user_id()), ) entry = cur.fetchone() if not entry: flash("Eintrag nicht gefunden.", "danger") return redirect(url_for("journal.future")) if request.method == "POST": note_text = request.form.get("note_text", "").strip() month_str = request.form.get("month", "") month = parse_date(month_str) if note_text and month: with get_connection() as conn: with conn.cursor() as cur: cur.execute( "UPDATE future_log SET note_text = %s, month_date = %s " "WHERE id = %s AND user_id = %s", (note_text, month, entry_id, _user_id()), ) flash("Eintrag aktualisiert.", "success") else: flash("Bitte Text und Monat angeben.", "danger") return redirect(url_for("journal.future")) return render_template("edit_future.html", entry=entry) @journal_bp.route("/future//delete", methods=["POST"]) @login_required def delete_future(entry_id: int): with get_connection() as conn: with conn.cursor() as cur: cur.execute( "DELETE FROM future_log WHERE id = %s AND user_id = %s", (entry_id, _user_id()), ) flash("Eintrag aus dem Future Log gelöscht.", "success") return redirect(url_for("journal.future")) @journal_bp.route("/tag/", methods=["GET", "POST"]) @login_required def day(date_str: str): """Tagesansicht mit 3 Spalten (Bullet | Notiz | Priorität).""" target = parse_date(date_str) if target is None: flash("Ungültiges Datum.", "danger") return redirect(url_for("journal.index")) if request.method == "POST": bullet = request.form.get("bullet", "-") note_text = request.form.get("note_text", "").strip() priority = request.form.get("priority", "0") try: priority = int(priority) except ValueError: priority = 0 # Verweise (optional): Typ + Wert ref_type = request.form.get("ref_type", "").strip() ref_value = request.form.get("ref_value", "").strip() refs = None if ref_value: refs = json.dumps([{"type": ref_type or "link", "value": ref_value}]) if bullet not in (".", "x", "-", "?"): bullet = "-" if note_text: with get_connection() as conn: with conn.cursor() as cur: cur.execute( "INSERT INTO notes (user_id, note_date, bullet_type, " "note_text, priority, refs) VALUES (%s, %s, %s, %s, %s, %s)", (_user_id(), target, bullet, note_text, priority, refs), ) flash("Notiz hinzugefügt.", "success") else: flash("Notiz darf nicht leer sein.", "danger") return redirect(url_for("journal.day", date_str=date_str)) notes = _active_notes(_user_id(), target) prev = target - timedelta(days=1) nxt = target + timedelta(days=1) return render_template( "day.html", target=target, weekday=WEEKDAYS[target.weekday()], notes=notes, bullet_labels=BULLET_LABELS, prev=prev, nxt=nxt, ref_types=list(REF_TYPE_DISPLAY.keys()), ) @journal_bp.route("/note//mark", methods=["POST"]) @login_required def mark_done(note_id: int): with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE id = %s AND user_id = %s", (note_id, _user_id()), ) note = cur.fetchone() if not note: flash("Notiz nicht gefunden.", "danger") return redirect(url_for("journal.index")) new_status = "done" if note["status"] != "done" else "open" cur.execute( "UPDATE notes SET status = %s WHERE id = %s", (new_status, note_id), ) flash("Status aktualisiert.", "success") return redirect(request.referrer or url_for("journal.index")) @journal_bp.route("/note//move", methods=["POST"]) @login_required def move_note(note_id: int): """Verschiebt eine Notiz in einen anderen Tag oder ins Future Log. Verbessert: Beim Verschieben in einen anderen Tag wird KEINE Kopie angelegt, sondern der Eintrag selbst auf das neue Datum gesetzt und `moved_from_date` gespeichert (Herkunft bleibt nachvollziehbar). Formularfelder: target=date|future, target_date=YYYY-MM-DD """ with get_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM notes WHERE id = %s AND user_id = %s", (note_id, _user_id()), ) note = cur.fetchone() if not note: flash("Notiz nicht gefunden.", "danger") return redirect(url_for("journal.index")) target = request.form.get("target", "") if target == "future": # In Future Log übernehmen refs = note.get("refs") cur.execute( "INSERT INTO future_log (user_id, month_date, note_text, refs) " "VALUES (%s, %s, %s, %s)", (_user_id(), note["note_date"], note["note_text"], refs), ) cur.execute( "UPDATE notes SET status = 'moved', moved_to_future = 1 " "WHERE id = %s", (note_id,), ) flash("In Future Log verschoben.", "success") else: target_date = parse_date(request.form.get("target_date", "")) if not target_date: flash("Bitte ein Zieldatum auswählen.", "danger") elif target_date == note["note_date"]: flash("Das Zieldatum entspricht dem aktuellen Tag.", "warning") else: cur.execute( "UPDATE notes SET note_date = %s, status = 'open', " "moved_to_future = 0, moved_to_date = NULL, " "moved_from_date = %s WHERE id = %s", (target_date, note["note_date"], note_id), ) flash(f"Verschoben nach {target_date.strftime('%d.%m.%Y')}.", "success") return redirect(request.referrer or url_for("journal.index"))