"Refactor app. Update import/Improve Python Cython code formatting (#__"
This commit is contained in:
Binary file not shown.
231
app.py
231
app.py
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user