"Refactor app. Update import/Improve Python Cython code formatting (#__"

This commit is contained in:
2025-05-10 23:12:51 +02:00
parent d1352286b7
commit 82d03f6c48
2 changed files with 231 additions and 0 deletions

Binary file not shown.

231
app.py
View File

@@ -2,6 +2,8 @@
# -*- coding: utf-8 -*-
import os
import logging
import traceback
from datetime import datetime, timedelta, timezone
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, g
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
@@ -47,6 +49,87 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', os.path.join(os.getcwd(), 'uploads'))
# Logger-Konfiguration
log_level = os.environ.get('LOG_LEVEL', 'INFO').upper()
log_dir = os.path.join(basedir, 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'app.log')
# Handler für Datei-Logs
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(getattr(logging, log_level))
# Handler für Konsolen-Logs
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
console_handler.setLevel(getattr(logging, log_level))
# Konfiguriere App-Logger
app.logger.addHandler(file_handler)
app.logger.addHandler(console_handler)
app.logger.setLevel(getattr(logging, log_level))
app.logger.info('Anwendung gestartet')
# Einheitliches Fehlerbehandlungssystem
class ErrorHandler:
"""Zentralisierte Fehlerbehandlung für die gesamte Anwendung"""
@staticmethod
def log_exception(e, endpoint=None, code=500):
"""Protokolliert eine Ausnahme mit Kontext-Informationen"""
# Stack-Trace für Debugging
trace = traceback.format_exc()
# Benutzer-Informationen (wenn verfügbar)
user_info = f"User: {current_user.id} ({current_user.username})" if current_user and current_user.is_authenticated else "Nicht angemeldet"
# Request-Informationen
request_info = f"Endpoint: {endpoint or request.path}, Method: {request.method}, IP: {request.remote_addr}"
# Vollständige Log-Nachricht
app.logger.error(f"Fehler {code}: {str(e)}\n{request_info}\n{user_info}\n{trace}")
@staticmethod
def api_error(message, code=400, details=None):
"""Erstellt eine standardisierte API-Fehlerantwort"""
response = {
'success': False,
'error': {
'code': code,
'message': message
}
}
if details:
response['error']['details'] = details
return jsonify(response), code
@staticmethod
def handle_exception(e, is_api_request=False):
"""Behandelt eine Ausnahme basierend auf ihrem Typ"""
# Bestimme, ob es sich um eine API-Anfrage handelt
if is_api_request is None:
is_api_request = request.path.startswith('/api/') or request.headers.get('Accept') == 'application/json'
# Protokolliere die Ausnahme
ErrorHandler.log_exception(e)
# Erstelle benutzerfreundliche Fehlermeldung
user_message = "Ein unerwarteter Fehler ist aufgetreten."
if is_api_request:
# API-Antwort für Ausnahme
return ErrorHandler.api_error(user_message, 500)
else:
# Web-Antwort für Ausnahme (mit Flash-Nachricht)
flash(user_message, 'error')
return render_template('errors/500.html'), 500
# Globaler Exception-Handler
@app.errorhandler(Exception)
def handle_unhandled_exception(e):
"""Fängt alle unbehandelten Ausnahmen ab"""
return ErrorHandler.handle_exception(e)
# OpenAI API-Konfiguration
api_key = os.environ.get("OPENAI_API_KEY", "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")
@@ -2535,6 +2618,154 @@ def export_mindmap(mindmap_id):
'message': f'Fehler beim Exportieren: {str(e)}'
}), 500
@app.route('/api/mindmap/<int:mindmap_id>/import', methods=['POST'])
@login_required
def import_mindmap(mindmap_id):
"""
Importiert Daten in eine bestehende Mindmap.
Die Daten können in verschiedenen Formaten (JSON, XML, CSV) hochgeladen werden.
"""
try:
# Sicherheitscheck: Nur eigene Mindmaps können bearbeitet werden
mindmap = UserMindmap.query.get_or_404(mindmap_id)
if mindmap.user_id != current_user.id:
return jsonify({
'success': False,
'message': 'Keine Berechtigung zum Bearbeiten dieser Mindmap'
}), 403
# Prüfen, ob eine Datei hochgeladen wurde
if 'file' not in request.files:
return jsonify({
'success': False,
'message': 'Keine Datei ausgewählt'
}), 400
file = request.files['file']
if file.filename == '':
return jsonify({
'success': False,
'message': 'Keine Datei ausgewählt'
}), 400
# Format anhand der Dateiendung erkennen
file_ext = file.filename.rsplit('.', 1)[1].lower() if '.' in file.filename else None
if file_ext not in ['json', 'xml', 'csv']:
return jsonify({
'success': False,
'message': f'Nicht unterstütztes Dateiformat: {file_ext}'
}), 400
# Datei einlesen
import_data = None
if file_ext == 'json':
import_data = json.loads(file.read().decode('utf-8'))
elif file_ext == 'xml':
import xml.etree.ElementTree as ET
import xmltodict
xml_data = file.read().decode('utf-8')
import_data = xmltodict.parse(xml_data)
elif file_ext == 'csv':
import io
import csv
csv_data = file.read().decode('utf-8')
reader = csv.DictReader(io.StringIO(csv_data))
nodes = []
for row in reader:
nodes.append(row)
import_data = {'nodes': nodes}
# Daten in die Mindmap importieren
if 'nodes' in import_data:
# Bestehende Knoten in der Mindmap für Referenz
existing_nodes = db.session.query(
UserMindmapNode.node_id
).filter_by(
user_mindmap_id=mindmap_id
).all()
existing_node_ids = [n[0] for n in existing_nodes]
# Mapping von alten zu neuen Knoten-IDs für importierte Knoten
id_mapping = {}
# Knoten importieren
for node_data in import_data.get('nodes', []):
# Prüfen, ob es sich um Stringkeys (aus CSV) oder Dict (aus JSON) handelt
if isinstance(node_data, dict):
node_name = node_data.get('name')
node_desc = node_data.get('description', '')
node_color = node_data.get('color_code', '#9F7AEA')
x_pos = float(node_data.get('x_position', 0))
y_pos = float(node_data.get('y_position', 0))
node_scale = float(node_data.get('scale', 1.0))
old_id = node_data.get('id')
else:
# Fallback für andere Formate
continue
# Neuen Knoten erstellen, wenn nötig
new_node = MindMapNode(
name=node_name,
description=node_desc,
color_code=node_color
)
db.session.add(new_node)
db.session.flush() # ID generieren
# Verknüpfung zur Mindmap erstellen
user_node = UserMindmapNode(
user_mindmap_id=mindmap_id,
node_id=new_node.id,
x_position=x_pos,
y_position=y_pos,
scale=node_scale
)
db.session.add(user_node)
# ID-Mapping für Beziehungen speichern
if old_id:
id_mapping[old_id] = new_node.id
# Beziehungen zwischen Knoten importieren
for rel in import_data.get('relationships', []):
source_id = rel.get('source')
target_id = rel.get('target')
if source_id in id_mapping and target_id in id_mapping:
# Knoten-Objekte holen
source_node = MindMapNode.query.get(id_mapping[source_id])
target_node = MindMapNode.query.get(id_mapping[target_id])
if source_node and target_node:
# Beziehung erstellen
source_node.children.append(target_node)
db.session.commit()
return jsonify({
'success': True,
'message': f'{len(import_data.get("nodes", []))} Knoten erfolgreich importiert'
})
else:
return jsonify({
'success': False,
'message': 'Keine Knotendaten in der Importdatei gefunden'
}), 400
except Exception as e:
db.session.rollback()
print(f"Fehler beim Importieren der Mindmap: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Importieren: {str(e)}'
}), 500
# Automatische Datenbankinitialisierung - Aktualisiert für Flask 2.2+ Kompatibilität
def initialize_app():
"""Initialisierung der Anwendung"""