Laden der Daten aus Datenbank.

This commit is contained in:
2025-08-10 14:03:15 +02:00
parent 644ae4dc26
commit 690f8bd34d
3 changed files with 299 additions and 13 deletions
+4 -4
View File
@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1263</width> <width>1263</width>
<height>921</height> <height>774</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -142,7 +142,7 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="pushButton_3"> <widget class="QPushButton" name="pB_lade_aus_fn2">
<property name="text"> <property name="text">
<string>lade aus FN2</string> <string>lade aus FN2</string>
</property> </property>
@@ -172,7 +172,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>54</width> <width>54</width>
<height>865</height> <height>718</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -350,7 +350,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>649</width> <width>649</width>
<height>837</height> <height>690</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
+8 -8
View File
@@ -26,7 +26,7 @@ class Ui_MainWindow(object):
def setupUi(self, MainWindow): def setupUi(self, MainWindow):
if not MainWindow.objectName(): if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow") MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(1263, 921) MainWindow.resize(1263, 774)
self.actionNeu = QAction(MainWindow) self.actionNeu = QAction(MainWindow)
self.actionNeu.setObjectName(u"actionNeu") self.actionNeu.setObjectName(u"actionNeu")
icon = QIcon(QIcon.fromTheme(u"folder-new")) icon = QIcon(QIcon.fromTheme(u"folder-new"))
@@ -110,12 +110,12 @@ class Ui_MainWindow(object):
self.horizontalLayout_2.addItem(self.horizontalSpacer) self.horizontalLayout_2.addItem(self.horizontalSpacer)
self.pushButton_3 = QPushButton(self.frame_2) self.pB_lade_aus_fn2 = QPushButton(self.frame_2)
self.pushButton_3.setObjectName(u"pushButton_3") self.pB_lade_aus_fn2.setObjectName(u"pB_lade_aus_fn2")
icon6 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.GoDown)) icon6 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.GoDown))
self.pushButton_3.setIcon(icon6) self.pB_lade_aus_fn2.setIcon(icon6)
self.horizontalLayout_2.addWidget(self.pushButton_3) self.horizontalLayout_2.addWidget(self.pB_lade_aus_fn2)
self.verticalLayout.addWidget(self.frame_2) self.verticalLayout.addWidget(self.frame_2)
@@ -131,7 +131,7 @@ class Ui_MainWindow(object):
self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents = QWidget()
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 54, 865)) self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 54, 718))
self.verticalLayout_2 = QVBoxLayout(self.scrollAreaWidgetContents) self.verticalLayout_2 = QVBoxLayout(self.scrollAreaWidgetContents)
self.verticalLayout_2.setObjectName(u"verticalLayout_2") self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.label = QLabel(self.scrollAreaWidgetContents) self.label = QLabel(self.scrollAreaWidgetContents)
@@ -216,7 +216,7 @@ class Ui_MainWindow(object):
self.scrollArea_2.setWidgetResizable(True) self.scrollArea_2.setWidgetResizable(True)
self.scrollAreaWidgetContents_2 = QWidget() self.scrollAreaWidgetContents_2 = QWidget()
self.scrollAreaWidgetContents_2.setObjectName(u"scrollAreaWidgetContents_2") self.scrollAreaWidgetContents_2.setObjectName(u"scrollAreaWidgetContents_2")
self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 649, 837)) self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 649, 690))
self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents_2) self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents_2)
self.verticalLayout_3.setObjectName(u"verticalLayout_3") self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
@@ -286,7 +286,7 @@ class Ui_MainWindow(object):
self.actionVorhandene_Projekte.setText(QCoreApplication.translate("MainWindow", u"Vorhandene Projekte", None)) self.actionVorhandene_Projekte.setText(QCoreApplication.translate("MainWindow", u"Vorhandene Projekte", None))
self.pushButton.setText(QCoreApplication.translate("MainWindow", u"nur ge\u00e4nderte generieren", None)) self.pushButton.setText(QCoreApplication.translate("MainWindow", u"nur ge\u00e4nderte generieren", None))
self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"Alle generieren", None)) self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"Alle generieren", None))
self.pushButton_3.setText(QCoreApplication.translate("MainWindow", u"lade aus FN2", None)) self.pB_lade_aus_fn2.setText(QCoreApplication.translate("MainWindow", u"lade aus FN2", None))
self.label.setText("") self.label.setText("")
self.label_2.setText("") self.label_2.setText("")
self.label_6.setText(QCoreApplication.translate("MainWindow", u"Vorher (Referenz)", None)) self.label_6.setText(QCoreApplication.translate("MainWindow", u"Vorher (Referenz)", None))
+287 -1
View File
@@ -1,10 +1,11 @@
import glob import glob
import os import os
import time import time
import polars as pl
from PySide6.QtCore import Qt, QSize from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QCursor, QPixmap, QPainter, QAction, QIcon from PySide6.QtGui import QCursor, QPixmap, QPainter, QAction, QIcon
from PySide6.QtWidgets import QLabel, QMainWindow, QApplication, QStyleFactory, QMenu, QTreeWidgetItem from PySide6.QtWidgets import QLabel, QMainWindow, QApplication, QStyleFactory, QMenu, QTreeWidgetItem, QMessageBox
from PySide6.QtPdf import QPdfDocument from PySide6.QtPdf import QPdfDocument
from ui.MainWinddow_ui import Ui_MainWindow from ui.MainWinddow_ui import Ui_MainWindow
@@ -142,6 +143,8 @@ class MainWindow(QMainWindow):
print(f"Öffne Projekt: {project.name}") print(f"Öffne Projekt: {project.name}")
print(f"Projekt-Ordner: {project.project_dir}") print(f"Projekt-Ordner: {project.project_dir}")
self.project = project
try: try:
# Prüfe ob project.yaml existiert und nicht leer ist # Prüfe ob project.yaml existiert und nicht leer ist
project_yaml_path = Path(project.project_dir) / 'project.yaml' project_yaml_path = Path(project.project_dir) / 'project.yaml'
@@ -539,6 +542,9 @@ class MainWindow(QMainWindow):
self.ui.actionNeu.triggered.connect(self.open_new_project_dialog) self.ui.actionNeu.triggered.connect(self.open_new_project_dialog)
self.ui.actionEinstellungen.triggered.connect(self.open_settings_dialog) self.ui.actionEinstellungen.triggered.connect(self.open_settings_dialog)
# Button "lade aus FN2" verbinden
self.ui.pB_lade_aus_fn2.clicked.connect(self.on_load_from_fn2_clicked)
def _setup_tree_context_menu(self): def _setup_tree_context_menu(self):
"""Richtet das Kontextmenü für das TreeWidget ein.""" """Richtet das Kontextmenü für das TreeWidget ein."""
# Aktiviere Kontextmenü für das TreeWidget # Aktiviere Kontextmenü für das TreeWidget
@@ -1060,6 +1066,286 @@ class MainWindow(QMainWindow):
print("Neuen TreeNode als Root-Element hinzufügen") print("Neuen TreeNode als Root-Element hinzufügen")
# TODO: Dialog zum Eingeben der TreeNode-Daten öffnen # TODO: Dialog zum Eingeben der TreeNode-Daten öffnen
def on_load_from_fn2_clicked(self):
"""
Wird ausgeführt, wenn der Button "lade aus FN2" geklickt wird.
Führt SQL-Abfrage aus und aktualisiert die Projekt-Nodes.
"""
print("Button 'lade aus FN2' wurde geklickt!")
try:
# Prüfe ob ein Projekt geladen ist
if not hasattr(self, 'project') or not self.project:
QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.")
return
# Hole das aktuelle Projekt aus app_settings
if not self.project:
QMessageBox.warning(self, "Warnung", "Aktuelles Projekt nicht in den Einstellungen gefunden.")
return
# Hole die PostgreSQL-Datenbank-Konfiguration
db_config = self._get_database_config(self.project.postgre_sql_db_id)
if not db_config:
QMessageBox.warning(self, "Warnung", "PostgreSQL-Datenbank-Konfiguration nicht gefunden.")
return
# Führe SQL-Abfrage aus
df = self._execute_sql_query(db_config)
if df is None:
return # Fehler bereits angezeigt
# Verarbeite die Daten wie in readCsv.py
new_nodes = self._process_sql_data(df)
# Merge mit vorhandenen Nodes
self._merge_nodes_with_existing(new_nodes)
# Speichere die aktualisierten Projekt-Einstellungen
self._save_project_settings()
# Lade das Projekt neu
self._load_nodes_to_tree()
QMessageBox.information(self, "Erfolg", "Daten erfolgreich aus FN2 geladen und Projekt aktualisiert!")
except Exception as e:
print(f"Fehler beim Laden aus FN2: {e}")
QMessageBox.critical(self, "Fehler", f"Fehler beim Laden aus FN2:\n{str(e)}")
# def _get_current_project(self):
# """
# Ermittelt das aktuell geladene Projekt aus app_settings.
# Returns:
# PdfProject|None: Das aktuelle Projekt oder None
# """
# # Da wir kein direktes Attribut für das aktuelle Projekt haben,
# # nehmen wir das erste Projekt als Fallback oder implementieren eine bessere Logik
# if app_settings.pdf_projects:
# # TODO: Hier sollte eine bessere Logik implementiert werden,
# # um das tatsächlich aktuelle Projekt zu ermitteln
# return app_settings.pdf_projects[0]
# return None
def _get_database_config(self, db_id):
"""
Holt die Datenbank-Konfiguration anhand der ID.
Args:
db_id: ID der PostgreSQL-Datenbank
Returns:
PostgreSqlDb|None: Die Datenbank-Konfiguration oder None
"""
for db in app_settings.postgresql_dbs:
if db.id == db_id:
return db
return None
def _execute_sql_query(self, db_config):
"""
Führt die SQL-Abfrage aus der data.sql Datei aus.
Args:
db_config: PostgreSQL-Datenbank-Konfiguration
Returns:
pl.DataFrame|None: Die Abfrageergebnisse oder None bei Fehler
"""
try:
# Lade SQL-Abfrage aus Datei
sql_file_path = Path("src/res/data.sql")
if not sql_file_path.exists():
QMessageBox.critical(self, "Fehler", f"SQL-Datei nicht gefunden: {sql_file_path}")
return None
with open(sql_file_path, 'r', encoding='utf-8') as f:
sql_query = f.read()
print(f"SQL-Abfrage geladen: {len(sql_query)} Zeichen")
# Verbindung zur PostgreSQL-Datenbank herstellen
connection_string = ("postgresql://"
f"{db_config.username}:"
f"{db_config.password}@"
f"{db_config.host}:"
f"{db_config.port}/"
f"{db_config.database}?"
f"sslmode={db_config.ssl_mode.value}"
)
print(f"Verbinde zu PostgreSQL: {db_config.host}:{db_config.port}/{db_config.database}")
df = pl.read_database_uri(sql_query, connection_string, engine='connectorx').sort(["reporttyp_bez", "report_bez", "repfile_bez"])
return df
except Exception as e:
error_msg = f"Fehler beim Ausführen der SQL-Abfrage: {str(e)}"
print(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)
return None
def _process_sql_data(self, df):
"""
Verarbeitet die SQL-Daten wie in readCsv.py und erstellt Node-Struktur.
Args:
df: Polars DataFrame mit den SQL-Ergebnissen
Returns:
list[TreeNode]: Liste der erstellten Root-Nodes
"""
try:
start_time = time.time()
# Gruppiere die Daten wie in readCsv.py
ebene_1 = df.group_by(["reporttyp", "reporttyp_bez"]).len()
ebene_2 = df.group_by(["reporttyp", "report", "report_bez"]).len()
ebene_3 = df.group_by(["reporttyp", "report", "repfile", "repfile_bez", "xsl_datei"]).len()
group_time = time.time() - start_time
print(f"Performance: Gruppierung in {group_time:.3f}s")
new_nodes = []
start_time = time.time()
# Erstelle Node-Struktur wie in readCsv.py
for r1 in ebene_1.rows(named=True):
tn_1 = TreeNode(id=(r1["reporttyp"],), bez=r1["reporttyp_bez"], children=[])
r1_children = ebene_2.filter(pl.col("reporttyp") == r1["reporttyp"])
for r2 in r1_children.rows(named=True):
tn_2 = TreeNode(id=(r2["reporttyp"], r2["report"]), bez=r2["report_bez"], children=[])
r2_children = ebene_3.filter((pl.col("reporttyp") == r1["reporttyp"]) & (pl.col("report") == r2["report"]))
for r3 in r2_children.rows(named=True):
x = XslFile(
id=(r3["reporttyp"], r3["report"], r3["repfile"]),
bez=r3["repfile_bez"],
xsl_file=Path(r3["xsl_datei"]),
xmls=[]
)
tn_2.children.append(x)
tn_1.children.append(tn_2)
new_nodes.append(tn_1)
nodes_time = time.time() - start_time
print(f"Performance: Node-Erstellung in {nodes_time:.3f}s")
print(f"Erstellt: {len(new_nodes)} Root-Nodes")
return new_nodes
except Exception as e:
print(f"Fehler beim Verarbeiten der SQL-Daten: {e}")
raise
def _merge_nodes_with_existing(self, new_nodes):
"""
Merged neue Nodes mit vorhandenen Nodes basierend auf IDs.
Überschreibt nur einzelne Eigenschaften, nicht ganze Nodes.
Args:
new_nodes: Liste der neuen Nodes
"""
try:
print("Merge neue Nodes mit vorhandenen...")
# Erstelle ein Dictionary der neuen Nodes für schnellen Zugriff
new_nodes_dict = {}
self._build_nodes_dict(new_nodes, new_nodes_dict)
print(f"Neue Nodes Dictionary erstellt: {len(new_nodes_dict)} Einträge")
# Merge mit vorhandenen Nodes
if self.pdf_project.nodes:
self._merge_nodes_recursive(self.pdf_project.nodes, new_nodes_dict)
# Füge komplett neue Root-Nodes hinzu
existing_root_ids = {node.id for node in self.pdf_project.nodes}
for new_node in new_nodes:
if new_node.id not in existing_root_ids:
self.pdf_project.nodes.append(new_node)
print(f"Neue Root-Node hinzugefügt: {new_node.bez}")
print("Merge abgeschlossen")
except Exception as e:
print(f"Fehler beim Mergen der Nodes: {e}")
raise
def _build_nodes_dict(self, nodes, nodes_dict):
"""
Erstellt rekursiv ein Dictionary aller Nodes für schnellen ID-basierten Zugriff.
Args:
nodes: Liste der Nodes
nodes_dict: Dictionary zum Füllen
"""
for node in nodes:
nodes_dict[node.id] = node
if isinstance(node, TreeNode) and node.children:
self._build_nodes_dict(node.children, nodes_dict)
def _merge_nodes_recursive(self, existing_nodes, new_nodes_dict):
"""
Merged rekursiv vorhandene Nodes mit neuen Nodes.
Args:
existing_nodes: Liste der vorhandenen Nodes
new_nodes_dict: Dictionary der neuen Nodes
"""
for existing_node in existing_nodes:
if existing_node.id in new_nodes_dict:
new_node = new_nodes_dict[existing_node.id]
# Aktualisiere nur die Bezeichnung, falls sie sich geändert hat
if existing_node.bez != new_node.bez:
print(f"Aktualisiere Bezeichnung für Node {existing_node.id}: '{existing_node.bez}' -> '{new_node.bez}'")
existing_node.bez = new_node.bez
# Für XslFile: Aktualisiere xsl_file Pfad
if isinstance(existing_node, XslFile) and isinstance(new_node, XslFile):
if existing_node.xsl_file != new_node.xsl_file:
print(f"Aktualisiere XSL-Datei für Node {existing_node.id}: '{existing_node.xsl_file}' -> '{new_node.xsl_file}'")
existing_node.xsl_file = new_node.xsl_file
# Rekursiv für Kinder (nur bei TreeNode)
if isinstance(existing_node, TreeNode) and existing_node.children:
self._merge_nodes_recursive(existing_node.children, new_nodes_dict)
# Füge neue Kinder hinzu, die noch nicht existieren
if existing_node.id in new_nodes_dict:
new_node = new_nodes_dict[existing_node.id]
if isinstance(new_node, TreeNode) and new_node.children:
existing_child_ids = {child.id for child in existing_node.children}
for new_child in new_node.children:
if new_child.id not in existing_child_ids:
existing_node.children.append(new_child)
print(f"Neues Kind hinzugefügt zu Node {existing_node.id}: {new_child.bez}")
def _save_project_settings(self):
"""
Speichert die aktualisierten Projekt-Einstellungen.
Args:
current_project: Das aktuelle Projekt
"""
try:
start_time = time.time()
# Speichere in project.yaml im Projekt-Verzeichnis
self.pdf_project.writeSettings(project_dir=self.project.project_dir)
dump_time = time.time() - start_time
print(f"Performance: Projekt-Einstellungen gespeichert in {dump_time:.3f}s")
except Exception as e:
print(f"Fehler beim Speichern der Projekt-Einstellungen: {e}")
raise
def closeEvent(self, event): def closeEvent(self, event):
"""Wird beim Schließen der Anwendung aufgerufen.""" """Wird beim Schließen der Anwendung aufgerufen."""
# PDF-Dokumente schließen ist bei QtPdf automatisch durch Garbage Collection # PDF-Dokumente schließen ist bei QtPdf automatisch durch Garbage Collection