Some code organisation and recovery procedure fix

This commit is contained in:
2024-12-09 23:23:11 +01:00
parent 5f08435816
commit 3acafaea32
7 changed files with 305 additions and 234 deletions

View File

@@ -75,10 +75,10 @@ GridLayout
Component.onCompleted: Component.onCompleted:
{ {
config.usernameNotAvailable.connect(usernameNotAvailable) config.adminNotAvailable.connect(adminNotAvailable)
} }
function usernameNotAvailable() function adminNotAvailable()
{ {
benutzerName.placeholderText = qsTr ("Benutzername ist bereits vergeben") benutzerName.placeholderText = qsTr ("Benutzername ist bereits vergeben")
benutzerName.clear() benutzerName.clear()

View File

@@ -1,10 +1,13 @@
import QtCore
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts import QtQuick.Layouts
Item Item
{ {
property string recpass: ""
anchors.fill: parent anchors.fill: parent
ColumnLayout ColumnLayout
@@ -133,11 +136,58 @@ Item
Layout.fillHeight: true Layout.fillHeight: true
} }
Dialog
{
id: recoveryPaswordDialog
modal: true
title: qsTr("Wiederherstellung")
anchors.centerIn: parent
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted:
{
recpass = recoveryPaswordInput.text
getRecoveryDialog.open()
}
ColumnLayout
{
RowLayout
{
Label
{
text: qsTr("Wiederherstellungspasswort eingeben: ")
}
TextField
{
id: recoveryPaswordInput
text: ""
echoMode: TextInput.Password
implicitWidth: 300
placeholderText: qsTr("Hier Wiederherstellungspasswort eingeben")
}
}
}
}
FileDialog
{
id: getRecoveryDialog
title: qsTr("Wiederherstellungsdatei")
fileMode: FileDialog.OpenFile
nameFilters: ["PYQCRM Recovery files (*.pyqrec)"]
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
onAccepted: config.getRecoveryKey(getRecoveryDialog.currentFile, recpass)
onRejected: quit()
}
} }
Component.onCompleted: Component.onCompleted:
{ {
loggedin_user.loginOkay.connect(loggedin) loggedin_user.loginOkay.connect(loggedin)
config.invalidEncryptionKey.connect(getEncryptionKey)
config.checkEncryptionKey()
} }
function loggedin() function loggedin()
@@ -145,4 +195,8 @@ Item
appLoader.source = "Dashboard.qml" appLoader.source = "Dashboard.qml"
} }
function getEncryptionKey()
{
recoveryPaswordDialog.open()
}
} }

View File

@@ -11,113 +11,6 @@ Item
{ {
property string recpass: "" property string recpass: ""
property bool adminAvailable: true property bool adminAvailable: true
Component.onCompleted:
{
config.dbConnectionError.connect(onDbConnectionError)
config.adminUserError.connect(onAdminUserError)
config.backupEncryptionKey.connect(onBackupEncryptionKey)
}
function onBackupEncryptionKey()
{
recoveryDialog.open()
}
function onDbConnectionError(msg, success)
{
if (!success)
conErrDialog.open()
}
function onAdminUserError(msg, success)
{
if (success)
{
encryptPwDialog.open()
}
else
{
adminAvailable = false
firstStart.push("AdminUserConfig.qml")
}
}
MessageDialog
{
id: recoveryDialog
text: qsTr("Diesen Wiederherstellungscode musst du sicher aufbewahren!\nMöchtest du das jetzt machen?")
title: qsTr("Wiederherstellen")
buttons: MessageDialog.Yes | MessageDialog.No
onAccepted: saveRecoveryDialog.open()
}
FileDialog
{
id: saveRecoveryDialog
title: qsTr("Wiederherstellungsdatei")
fileMode: adminAvailable? FileDialog.OpenFile: FileDialog.SaveFile
nameFilters: ["PYQCRM Recovery files (*.pyqrec)"]
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
onAccepted:
{
if (!adminAvailable) config.saveRecoveryKey(saveRecoveryDialog.currentFile, recpass)
else
{
config.getRecoveryKey(saveRecoveryDialog.currentFile, recpass)
}
appLoader.source = "Dashboard.qml"
topBar.visible = true
}
}
MessageDialog
{
id: conErrDialog
text: qsTr("Datenbankverbindung fehlgeschlagen")
title: qsTr("Datenbank Verbindung")
}
Dialog
{
id: encryptPwDialog
modal: true
title: qsTr("Wiederherstellung")
anchors.centerIn: parent
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted:
{
recpass = encryptPassword.text
saveRecoveryDialog.open()
}
ColumnLayout
{
RowLayout
{
Label
{
text: qsTr("Wiederherstellungspasswort eingeben: ")
}
TextField
{
id: encryptPassword
echoMode: TextInput.Password
implicitWidth: 300
placeholderText: qsTr("Hier Wiederherstellungspasswort eingeben")
}
}
}
}
anchors.fill: parent anchors.fill: parent
StackView StackView
@@ -125,15 +18,14 @@ Item
id: firstStart id: firstStart
anchors.fill: parent anchors.fill: parent
initialItem: "DbConfiguration.qml" initialItem: "DbConfiguration.qml"
//initialItem: "AdminUserConfig.qml"
} }
RowLayout RowLayout
{ {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.margins: 9 anchors.margins: 9
width: parent.width width: parent.width
Item Item
{ {
Layout.fillWidth: true Layout.fillWidth: true
@@ -164,28 +56,125 @@ Item
if (pyqcrm_conf) if (pyqcrm_conf)
{ {
config.setConfig(pyqcrm_conf) config.setConfig(pyqcrm_conf)
} }
} }
else else
{ {
pyqcrm_conf = Qmldict.firstConf(submitBtn.grids) pyqcrm_conf = Qmldict.firstConf(submitBtn.grids)
if (pyqcrm_conf) if (pyqcrm_conf) config.addAdminUser(pyqcrm_conf)
{
admin = config.addAdminUser(pyqcrm_conf)
if (admin)
{
//appLoader.source = "Dashboard.qml"
//topBar.visible = true
}
else
{
console.log("Konfiguration Admin fehlgeschlagen")
}
}
} }
} }
} }
} }
MessageDialog
{
id: recoveryDialog
text: qsTr("Diesen Wiederherstellungscode musst du sicher aufbewahren!\nMöchtest du das jetzt machen?")
title: qsTr("Wiederherstellen")
buttons: MessageDialog.Yes | MessageDialog.No
onAccepted: recoveryPaswordDialog.open()
onRejected: gotoLogin()
}
MessageDialog
{
id: conErrDialog
text: qsTr("Datenbankverbindung fehlgeschlagen")
title: qsTr("Datenbank Verbindung")
}
Dialog
{
id: recoveryPaswordDialog
modal: true
title: qsTr("Wiederherstellung")
anchors.centerIn: parent
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted:
{
recpass = recoveryPaswordInput.text
saveRecoveryDialog.open()
}
ColumnLayout
{
RowLayout
{
Label
{
text: qsTr("Wiederherstellungspasswort eingeben: ")
}
TextField
{
id: recoveryPaswordInput
text: ""
echoMode: TextInput.Password
implicitWidth: 300
placeholderText: qsTr("Hier Wiederherstellungspasswort eingeben")
}
}
}
}
FileDialog
{
id: saveRecoveryDialog
title: qsTr("Wiederherstellungsdatei")
fileMode: adminAvailable? FileDialog.OpenFile: FileDialog.SaveFile
nameFilters: ["PYQCRM Recovery files (*.pyqrec)"]
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
onAccepted:
{
if (!adminAvailable) config.saveRecoveryKey(saveRecoveryDialog.currentFile, recpass)
else config.getRecoveryKey(saveRecoveryDialog.currentFile, recpass)
gotoLogin()
}
onRejected:
{
if (adminAvailable) quit()
}
}
Component.onCompleted:
{
config.dbConnectionError.connect(onDbConnectionError)
config.adminUserError.connect(onAdminUserError)
config.backupEncryptionKey.connect(onBackupEncryptionKey)
}
function gotoLogin()
{
appLoader.source= "LoginScreen.qml"
topBar.visible = true
}
function onBackupEncryptionKey()
{
recoveryDialog.open()
}
function onDbConnectionError(msg, success)
{
if (!success)
conErrDialog.open()
}
function onAdminUserError(msg, success)
{
if (success)
{
recoveryPaswordDialog.open()
}
else
{
adminAvailable = false
firstStart.push("AdminUserConfig.qml")
}
}
} }

View File

@@ -47,6 +47,7 @@ ApplicationWindow
property alias window: appWindow property alias window: appWindow
} }
Component.onCompleted: Component.onCompleted:
{ {
if(bad_config) if(bad_config)
@@ -54,8 +55,8 @@ ApplicationWindow
importDialog.open() importDialog.open()
} }
else appLoader.source= "LoginScreen.qml" else appLoader.source= "LoginScreen.qml"
} }
Dialog Dialog
{ {
id: importDialog id: importDialog
@@ -65,11 +66,10 @@ ApplicationWindow
onAccepted: settingsFiledialog.open() onAccepted: settingsFiledialog.open()
onRejected: appLoader.source= "firststart.qml" onRejected: appLoader.source= "firststart.qml"
title: qsTr("Einstellungen importieren") title: qsTr("Einstellungen importieren")
} }
FileDialog FileDialog
{ {
id: settingsFiledialog id: settingsFiledialog
title: qsTr("PYQCRM Einstellungen") title: qsTr("PYQCRM Einstellungen")
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
@@ -77,18 +77,19 @@ ApplicationWindow
nameFilters: [qsTr("PYQCRM Einstellungen (*.pyqcrm)")] nameFilters: [qsTr("PYQCRM Einstellungen (*.pyqcrm)")]
onAccepted: onAccepted:
{ {
encryptPwDialog.open() exportFilePassword.open()
confile = selectedFile confile = selectedFile
} }
} }
Dialog Dialog
{ {
id: encryptPwDialog id: exportFilePassword
modal: true modal: true
title: qsTr("PYQCRM Einstellungen") title: qsTr("PYQCRM Einstellungen")
anchors.centerIn: parent anchors.centerIn: parent
standardButtons: Dialog.Ok | Dialog.Cancel standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: config.importConfig(confile, encryptPassword.text) onAccepted: config.importConfig(confile, exportPasswordInput.text)
ColumnLayout ColumnLayout
{ {
RowLayout RowLayout
@@ -100,7 +101,7 @@ ApplicationWindow
TextField TextField
{ {
id: encryptPassword id: exportPasswordInput
echoMode: TextInput.Password echoMode: TextInput.Password
implicitWidth: 300 implicitWidth: 300
} }

View File

@@ -19,43 +19,120 @@ from .PyqcrmFlags import PyqcrmFlags
class ConfigLoader(QObject): class ConfigLoader(QObject):
__config = None __config = None
__version = "0.1-alpha" __version = "0.1-alpha"
__check_enc_key = True
dbConnectionError = Signal(str, bool) dbConnectionError = Signal(str, bool)
adminUserError = Signal(str, bool) adminUserError = Signal(str, bool)
usernameNotAvailable = Signal() adminNotAvailable = Signal()
configurationReady = Signal(bool) configurationReady = Signal()
backupEncryptionKey = Signal() backupEncryptionKey = Signal()
invalidEncryptionKey = Signal()
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.config_dir = user_config_dir() + '/pyqcrm' #user_config_dir = Funktion platformdirs # print(f"In {__file__} file, __init__()")
self.config_dir = user_config_dir() + '/pyqcrm'
config_dir = Path(self.config_dir) config_dir = Path(self.config_dir)
if config_dir.exists(): if config_dir.exists():
self.__configLoad() self.__configLoad()
if self.__config:
self.checkEncryptionKey()
else: else:
config_dir.mkdir(0o750, True, True) config_dir.mkdir(0o750, True, True)
@Slot(dict, result= bool)
def setConfig(self, app_config):
# print(f"In {__file__} file, setConfig()")
if not self.__config:
base_conf = self.__initializeConfig()
conf = self.__checkDbConnection(app_config)
app_config = toml.dumps(app_config)
if conf:
app_config = base_conf + app_config
self.__config = toml.loads(app_config)
self.__saveConfig()
conf = self.__checkAdminUser()
if conf:
self.configurationReady.emit()
def __initializeConfig(self):
# print(f"In {__file__} file, __initializeConfig()")
self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8")
conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n"
conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n"
conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n"
return conf
def __checkDbConnection(self, db_config):
# print(f"In {__file__} file, __checkDbConnection()")
con = DbManager(db_config['database']).getConnection()
if con:
self.dbConnectionError.emit("Connection OK", True)
return True
else:
self.dbConnectionError.emit("Connection fehlgeschlagen", False)
return False
def __saveConfig(self):
# print(f"In {__file__} file, saveConfig()")
try:
with open (self.config_dir + '/pyqcrm.toml', 'w') as f:
# print(self.__config)
config = Vermasseln().oscarVermasseln(toml.dumps(self.__config))
f.write(config)
except FileNotFoundError:
print("Konnte die Konfiguration nicht speichern.")
def __checkAdminUser(self):
# print(f"In {__file__} file, __checkAdminUser()")
con = DbManager().getConnection()
cur = con.cursor()
cur.callproc("checkAdmin")
result = cur.fetchall()
if not result:
#if not result[0][0] == 1:
self.adminUserError.emit("Kein Admin vorhanden", False)
return False
else:
self.adminUserError.emit("Admin vorhanden", True)
return True
@Slot(dict, result= bool)
def addAdminUser(self, user_config):
# print(f"In {__file__} file, addAdminUser()")
admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser()
if not admin:
#self.adminNotAvailable.emit()
self.adminUserError.emit("Benutzername nich verfügbar", False)
else:
self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'Yes'
self.__saveConfig()
self.backupEncryptionKey.emit()
return admin
@Slot(str, str) @Slot(str, str)
def saveRecoveryKey(self, recovery_file, recovery_password): def saveRecoveryKey(self, recovery_file, recovery_password):
# print(f"In {__file__} file, saveRecoveryKey()")
rp = self.__setRecoveryPassword(recovery_password) rp = self.__setRecoveryPassword(recovery_password)
rf = rp[1] + self.__encrypt_key + rp[0] rf = rp[1] + self.__encrypt_key + rp[0]
rf = Vermasseln().oscarVermasseln(rf) rf = Vermasseln().oscarVermasseln(rf)
rec_file = urlparse(recovery_file) rec_file = urlparse(recovery_file)
rec_file = rec_file.path rec_file = rec_file.path + ".pyqrec"
if os.name == "nt": if os.name == "nt":
rec_file = rec_file [1:] rec_file = rec_file [1:]
try: try:
with open(rec_file, "w") as f: with open(rec_file, "w") as f:
f.write(rf) f.write(rf)
self.configurationReady.emit(True) self.configurationReady.emit()
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
@Slot(str, str) @Slot(str, str)
def getRecoveryKey(self, recovery_file, recovery_password): def getRecoveryKey(self, recovery_file, recovery_password):
# print(f"In {__file__} file, getRecoveryKey()")
rec_file = urlparse(recovery_file) rec_file = urlparse(recovery_file)
rec_file = rec_file.path rec_file = rec_file.path
if os.name == "nt": if os.name == "nt":
@@ -70,21 +147,33 @@ class ConfigLoader(QObject):
salt = rf[-32:] salt = rf[-32:]
ok = self.__checkRecoveryPassword(recovery_password, password, salt) ok = self.__checkRecoveryPassword(recovery_password, password, salt)
if ok: if ok:
self.configurationReady.emit(True) self.__setEncryptionKey(ek)
self.configurationReady.emit()
else: else:
self.configurationReady.emit(False) self.__invalidateEncryptionKey()
self.invalidEncryptionKey.emit()
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
def __invalidateEncryptionKey(self):
# print(f"In {__file__} file, __invalidateEncryptionKey()")
self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'No'
self.__saveConfig()
@Slot()
def checkEncryptionKey(self):
# print(f"In {__file__} file, __checkEncryptionKey()")
if self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] == 'No':
self.invalidEncryptionKey.emit()
def __checkRecoveryPassword(self, recovery_password, password, salt): def __checkRecoveryPassword(self, recovery_password, password, salt):
# print(f"In {__file__} file, __checkRecoveryPassword()")
rp = self.__setRecoveryPassword(recovery_password, salt) rp = self.__setRecoveryPassword(recovery_password, salt)
return rp[1] == password return rp[1] == password
@Slot(str, str) @Slot(str, str)
def importConfig(self, confile, password): def importConfig(self, confile, password):
# print(f"In {__file__} file, importConfig()")
confile = urlparse(confile) confile = urlparse(confile)
confile = confile.path confile = confile.path
@@ -92,38 +181,13 @@ class ConfigLoader(QObject):
confile = confile[1:] confile = confile[1:]
shutil.copyfile(confile, self.config_dir+ '/pyqcrm.toml') shutil.copyfile(confile, self.config_dir+ '/pyqcrm.toml')
@Slot(dict, result= bool)
def addAdminUser(self, user_config):
admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser()
if not admin:
self.usernameNotAvailable.emit()
else:
self.backupEncryptionKey.emit()
return admin
@Slot(dict, result= bool)
def setConfig(self, app_config):
base_conf = self.__initializeConfig()
conf = self.__checkDbConnection(app_config)
app_config = toml.dumps(app_config)
if conf:
app_config = base_conf + app_config
self.__config = toml.loads(app_config)
self.__saveConfig()
conf = self.__checkAdminUser()
if conf:
self.configurationReady.emit(True)
def __configLoad(self): def __configLoad(self):
# print(f"In {__file__} file, __configLoad()")
try: try:
with open (self.config_dir + '/pyqcrm.toml', 'r') as f: with open (self.config_dir + '/pyqcrm.toml', 'r') as f:
config = f.read() config = f.read()
self.__config = toml.loads(Vermasseln().entschluesseln(config)) self.__config = toml.loads(Vermasseln().entschluesseln(config))
self.configurationReady.emit(True) self.configurationReady.emit()
except FileNotFoundError: except FileNotFoundError:
print("Konnte die Konfiguration nicht laden.") print("Konnte die Konfiguration nicht laden.")
except TypeError: except TypeError:
@@ -131,50 +195,20 @@ class ConfigLoader(QObject):
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
def getConfig(self): def getConfig(self):
# print(f"In {__file__} file, getConfig()")
# print(self.__config)
return self.__config return self.__config
def __initializeConfig(self):
self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8")
conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\nENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n"
return conf
def __checkDbConnection(self, db_config):
con = DbManager(db_config['database']).getConnection()
if con:
self.dbConnectionError.emit("Connection OK", True)
return True
else:
self.dbConnectionError.emit("Connection fehlgeschlagen", False)
return False
def __checkAdminUser(self):
con = DbManager().getConnection()
cur = con.cursor()
cur.callproc("checkAdmin")
result = cur.fetchall()
if not result:
#if not result[0][0] == 1:
self.adminUserError.emit("Kein Admin vorhanden", False)
return False
else:
self.adminUserError.emit("Admin vorhanden", True)
return True
def __setRecoveryPassword(self, key, salt = None): def __setRecoveryPassword(self, key, salt = None):
# print(f"In {__file__} file, __setRecoveryPassword()")
key = Vermasseln.userPasswordHash(key, salt) key = Vermasseln.userPasswordHash(key, salt)
return key.split("$") return key.split("$")
def __saveConfig(self): @Slot(str)
def __setEncryptionKey(self, enc_key):
try: # print(f"In {__file__} file, __setEncryptionKey()")
with open (self.config_dir + '/pyqcrm.toml', 'w') as f: self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'Yes'
config = Vermasseln().oscarVermasseln(toml.dumps(self.__config)) self.__config['pyqcrm']['ENCRYPTION_KEY'] = enc_key
f.write(config) self.__saveConfig()
except FileNotFoundError:
print("Konnte die Konfiguration nicht speichern.")

View File

@@ -21,7 +21,7 @@ class DbManager(object):
cls.__connection = mariadb.connect(**cls.__con_param) cls.__connection = mariadb.connect(**cls.__con_param)
except mariadb.InterfaceError as e: except mariadb.InterfaceError as e:
cls.__connection = mariadb.connect(**cls.__con_param) cls.__connection = mariadb.connect(**cls.__con_param)
print(f"INTERFACE ERROR: {e}") print(f"DbManager Connection (INTERFACE ERROR): {e}..reconnecting...")
except mariadb.Error as e: except mariadb.Error as e:
print(f"Connection parameters are wrong: {e}") print(f"Connection parameters are wrong: {e}")

37
main.py
View File

@@ -26,32 +26,31 @@ from lib.DB.AddressModel import AddressModel
# name="" # name=""
# type="" # type=""
bad_config = False
am = None am = None
bm = None bm = None
user = None user = None
def initializeProgram(conf_ok): def initializeProgram():
if conf_ok: # print(f"In {__file__} file, initializeProgram()")
global am, bad_config, bm, user global am, bad_config, bm, user
if not bad_config:
dbconf = config.getConfig()['database'] dbconf = config.getConfig()['database']
DbManager(dbconf) DbManager(dbconf)
bad_config = False bad_config = False
bm = BusinessModel() bm = BusinessModel()
user = UserManager() user = UserManager()
am = AddressModel() am = AddressModel()
publishContext()
else: publishContext()
# TODO: show message and exit program
print("Kein Zugriff auf die Datenbank")
def publishContext(): def publishContext():
global engine # print(f"In {__file__} file, publishContext()")
global engine, am, bad_config, bm, user
engine.rootContext().setContextProperty("loggedin_user", user) engine.rootContext().setContextProperty("loggedin_user", user)
engine.rootContext().setContextProperty("bm", bm) engine.rootContext().setContextProperty("bm", bm)
engine.rootContext().setContextProperty("am", am) engine.rootContext().setContextProperty("am", am)
if __name__ == "__main__": if __name__ == "__main__":
#QResource.registerResource("rc_qml.py") #QResource.registerResource("rc_qml.py")
app = QGuiApplication(sys.argv) app = QGuiApplication(sys.argv)
@@ -59,12 +58,6 @@ if __name__ == "__main__":
engine.addImportPath("qrc:/"); engine.addImportPath("qrc:/");
bad_config = False
# qml_file = Path(__file__).resolve().parent / "Gui/main.qml" # qml_file = Path(__file__).resolve().parent / "Gui/main.qml"
qml_file = "qrc:/Gui/main.qml" qml_file = "qrc:/Gui/main.qml"
@@ -72,16 +65,16 @@ if __name__ == "__main__":
config = ConfigLoader() config = ConfigLoader()
if not config.getConfig(): if not config.getConfig():
bad_config = True bad_config = True
config.configurationReady.connect(initializeProgram) config.configurationReady.connect(initializeProgram)
else: else:
initializeProgram() initializeProgram()
engine.rootContext().setContextProperty("bad_config", bad_config) # print(f"Fehler: {i}") engine.rootContext().setContextProperty("bad_config", bad_config) # print(f"Fehler: {i}")
engine.rootContext().setContextProperty("config", config) engine.rootContext().setContextProperty("config", config)
engine.load(qml_file) engine.load(qml_file)