Initial commit: Notes Manager App (notes.braetter-int.de)
- Python/Flask Backend - SQLAlchemy Models (notes, tasks, templates, users) - Gunicorn + Nginx Deploy-Konfiguration - Static Assets (CSS/JS) - Jinja2 Templates
This commit is contained in:
285
app/routes.py
Executable file
285
app/routes.py
Executable file
@@ -0,0 +1,285 @@
|
||||
from datetime import datetime, date
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, Response
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
from .models import db, User, DocumentationEntry, DocumentationTemplate, TaskItem
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
|
||||
|
||||
def parse_date(value):
|
||||
if not value:
|
||||
return date.today()
|
||||
return datetime.strptime(value, '%Y-%m-%d').date()
|
||||
|
||||
|
||||
@main_bp.route('/')
|
||||
def index():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return redirect(url_for('main.login'))
|
||||
|
||||
|
||||
@main_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username', '').strip()
|
||||
password = request.form.get('password', '')
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and check_password_hash(user.password_hash, password):
|
||||
login_user(user)
|
||||
flash('Anmeldung erfolgreich.', 'success')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
flash('Ungültige Zugangsdaten.', 'danger')
|
||||
return render_template('login.html')
|
||||
|
||||
|
||||
@main_bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash('Du wurdest abgemeldet.', 'info')
|
||||
return redirect(url_for('main.login'))
|
||||
|
||||
|
||||
@main_bp.route('/dashboard')
|
||||
@login_required
|
||||
def dashboard():
|
||||
today = date.today()
|
||||
entries_today = DocumentationEntry.query.filter_by(work_date=today).order_by(DocumentationEntry.created_at.desc()).all()
|
||||
open_tasks = TaskItem.query.filter(TaskItem.status != 'erledigt').order_by(TaskItem.priority.desc(), TaskItem.due_date.asc()).all()
|
||||
latest_entries = DocumentationEntry.query.order_by(DocumentationEntry.updated_at.desc()).limit(8).all()
|
||||
stats = {
|
||||
'entries_total': DocumentationEntry.query.count(),
|
||||
'entries_today': len(entries_today),
|
||||
'templates_total': DocumentationTemplate.query.count(),
|
||||
'open_tasks': len(open_tasks),
|
||||
}
|
||||
return render_template('dashboard.html', today=today, entries_today=entries_today, open_tasks=open_tasks, latest_entries=latest_entries, stats=stats)
|
||||
|
||||
|
||||
@main_bp.route('/entries')
|
||||
@login_required
|
||||
def entries():
|
||||
q = request.args.get('q', '').strip()
|
||||
status = request.args.get('status', '').strip()
|
||||
category = request.args.get('category', '').strip()
|
||||
|
||||
query = DocumentationEntry.query
|
||||
if q:
|
||||
like = f'%{q}%'
|
||||
query = query.filter(
|
||||
db.or_(
|
||||
DocumentationEntry.title.ilike(like),
|
||||
DocumentationEntry.system_name.ilike(like),
|
||||
DocumentationEntry.content.ilike(like),
|
||||
DocumentationEntry.tags.ilike(like),
|
||||
)
|
||||
)
|
||||
if status:
|
||||
query = query.filter_by(status=status)
|
||||
if category:
|
||||
query = query.filter_by(category=category)
|
||||
|
||||
items = query.order_by(DocumentationEntry.work_date.desc(), DocumentationEntry.updated_at.desc()).all()
|
||||
categories = [c[0] for c in db.session.query(DocumentationEntry.category).distinct().order_by(DocumentationEntry.category).all()]
|
||||
return render_template('entries.html', items=items, q=q, status=status, category=category, categories=categories)
|
||||
|
||||
|
||||
@main_bp.route('/entries/new', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def entry_new():
|
||||
templates = DocumentationTemplate.query.order_by(DocumentationTemplate.name).all()
|
||||
if request.method == 'POST':
|
||||
entry = DocumentationEntry(
|
||||
title=request.form.get('title', '').strip(),
|
||||
work_date=parse_date(request.form.get('work_date')),
|
||||
category=request.form.get('category', '').strip() or 'Allgemein',
|
||||
system_name=request.form.get('system_name', '').strip(),
|
||||
priority=request.form.get('priority', 'mittel').strip(),
|
||||
status=request.form.get('status', 'offen').strip(),
|
||||
content=request.form.get('content', '').strip(),
|
||||
tags=request.form.get('tags', '').strip(),
|
||||
created_by=current_user.display_name,
|
||||
)
|
||||
if not entry.title or not entry.content:
|
||||
flash('Titel und Inhalt sind Pflichtfelder.', 'danger')
|
||||
return render_template('entry_form.html', templates=templates, entry=None)
|
||||
db.session.add(entry)
|
||||
db.session.commit()
|
||||
flash('Dokumentationseintrag wurde erstellt.', 'success')
|
||||
return redirect(url_for('main.entries'))
|
||||
return render_template('entry_form.html', templates=templates, entry=None)
|
||||
|
||||
|
||||
@main_bp.route('/entries/<int:entry_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def entry_edit(entry_id):
|
||||
entry = DocumentationEntry.query.get_or_404(entry_id)
|
||||
templates = DocumentationTemplate.query.order_by(DocumentationTemplate.name).all()
|
||||
if request.method == 'POST':
|
||||
entry.title = request.form.get('title', '').strip()
|
||||
entry.work_date = parse_date(request.form.get('work_date'))
|
||||
entry.category = request.form.get('category', '').strip() or 'Allgemein'
|
||||
entry.system_name = request.form.get('system_name', '').strip()
|
||||
entry.priority = request.form.get('priority', 'mittel').strip()
|
||||
entry.status = request.form.get('status', 'offen').strip()
|
||||
entry.content = request.form.get('content', '').strip()
|
||||
entry.tags = request.form.get('tags', '').strip()
|
||||
if not entry.title or not entry.content:
|
||||
flash('Titel und Inhalt sind Pflichtfelder.', 'danger')
|
||||
return render_template('entry_form.html', templates=templates, entry=entry)
|
||||
db.session.commit()
|
||||
flash('Eintrag aktualisiert.', 'success')
|
||||
return redirect(url_for('main.entries'))
|
||||
return render_template('entry_form.html', templates=templates, entry=entry)
|
||||
|
||||
|
||||
@main_bp.route('/entries/<int:entry_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def entry_delete(entry_id):
|
||||
entry = DocumentationEntry.query.get_or_404(entry_id)
|
||||
db.session.delete(entry)
|
||||
db.session.commit()
|
||||
flash('Eintrag gelöscht.', 'info')
|
||||
return redirect(url_for('main.entries'))
|
||||
|
||||
|
||||
@main_bp.route('/entries/<int:entry_id>')
|
||||
@login_required
|
||||
def entry_view(entry_id):
|
||||
entry = DocumentationEntry.query.get_or_404(entry_id)
|
||||
return render_template('entry_view.html', entry=entry)
|
||||
|
||||
|
||||
@main_bp.route('/templates', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def templates():
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name', '').strip()
|
||||
content = request.form.get('content', '').strip()
|
||||
description = request.form.get('description', '').strip()
|
||||
if name and content:
|
||||
db.session.add(DocumentationTemplate(name=name, description=description, content=content))
|
||||
db.session.commit()
|
||||
flash('Vorlage gespeichert.', 'success')
|
||||
return redirect(url_for('main.templates'))
|
||||
flash('Name und Inhalt sind Pflichtfelder.', 'danger')
|
||||
items = DocumentationTemplate.query.order_by(DocumentationTemplate.name).all()
|
||||
return render_template('templates.html', items=items)
|
||||
|
||||
|
||||
@main_bp.route('/templates/<int:template_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def template_delete(template_id):
|
||||
item = DocumentationTemplate.query.get_or_404(template_id)
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
flash('Vorlage gelöscht.', 'info')
|
||||
return redirect(url_for('main.templates'))
|
||||
|
||||
|
||||
@main_bp.route('/tasks', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def tasks():
|
||||
if request.method == 'POST':
|
||||
task = TaskItem(
|
||||
title=request.form.get('title', '').strip(),
|
||||
description=request.form.get('description', '').strip(),
|
||||
due_date=parse_date(request.form.get('due_date')) if request.form.get('due_date') else None,
|
||||
status=request.form.get('status', 'offen').strip(),
|
||||
priority=request.form.get('priority', 'mittel').strip(),
|
||||
)
|
||||
if not task.title:
|
||||
flash('Titel ist ein Pflichtfeld.', 'danger')
|
||||
else:
|
||||
db.session.add(task)
|
||||
db.session.commit()
|
||||
flash('Aufgabe gespeichert.', 'success')
|
||||
return redirect(url_for('main.tasks'))
|
||||
items = TaskItem.query.order_by(TaskItem.status.asc(), TaskItem.priority.desc(), TaskItem.due_date.asc()).all()
|
||||
return render_template('tasks.html', items=items)
|
||||
|
||||
|
||||
@main_bp.route('/tasks/<int:task_id>/toggle', methods=['POST'])
|
||||
@login_required
|
||||
def task_toggle(task_id):
|
||||
task = TaskItem.query.get_or_404(task_id)
|
||||
task.status = 'erledigt' if task.status != 'erledigt' else 'offen'
|
||||
db.session.commit()
|
||||
flash('Aufgabenstatus geändert.', 'success')
|
||||
return redirect(url_for('main.tasks'))
|
||||
|
||||
|
||||
@main_bp.route('/tasks/<int:task_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def task_delete(task_id):
|
||||
task = TaskItem.query.get_or_404(task_id)
|
||||
db.session.delete(task)
|
||||
db.session.commit()
|
||||
flash('Aufgabe gelöscht.', 'info')
|
||||
return redirect(url_for('main.tasks'))
|
||||
|
||||
|
||||
@main_bp.route('/users', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def users():
|
||||
if current_user.role != 'admin':
|
||||
flash('Nur Administratoren dürfen Benutzer verwalten.', 'danger')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username', '').strip()
|
||||
display_name = request.form.get('display_name', '').strip()
|
||||
password = request.form.get('password', '').strip()
|
||||
role = request.form.get('role', 'user').strip()
|
||||
|
||||
if not username or not display_name or not password:
|
||||
flash('Bitte alle Pflichtfelder füllen.', 'danger')
|
||||
elif User.query.filter_by(username=username).first():
|
||||
flash('Benutzername existiert bereits.', 'warning')
|
||||
else:
|
||||
db.session.add(User(
|
||||
username=username,
|
||||
display_name=display_name,
|
||||
role=role,
|
||||
password_hash=generate_password_hash(password)
|
||||
))
|
||||
db.session.commit()
|
||||
flash('Benutzer angelegt.', 'success')
|
||||
return redirect(url_for('main.users'))
|
||||
|
||||
items = User.query.order_by(User.username).all()
|
||||
return render_template('users.html', items=items)
|
||||
|
||||
|
||||
@main_bp.route('/export/markdown')
|
||||
@login_required
|
||||
def export_markdown():
|
||||
items = DocumentationEntry.query.order_by(DocumentationEntry.work_date.desc(), DocumentationEntry.id.desc()).all()
|
||||
lines = ['# Export Dokumentation', '']
|
||||
for item in items:
|
||||
lines.extend([
|
||||
f'## {item.title}',
|
||||
f'- Datum: {item.work_date.isoformat()}',
|
||||
f'- Kategorie: {item.category}',
|
||||
f'- System: {item.system_name or "-"}',
|
||||
f'- Priorität: {item.priority}',
|
||||
f'- Status: {item.status}',
|
||||
f'- Tags: {item.tags or "-"}',
|
||||
f'- Erstellt von: {item.created_by}',
|
||||
'',
|
||||
item.content,
|
||||
'',
|
||||
'---',
|
||||
''
|
||||
])
|
||||
content = '\n'.join(lines)
|
||||
return Response(
|
||||
content,
|
||||
mimetype='text/markdown',
|
||||
headers={'Content-Disposition': 'attachment; filename=dokumentation_export.md'}
|
||||
)
|
||||
Reference in New Issue
Block a user