From 0d6d8420f8bd4031b3a37115a87b136082272bb6 Mon Sep 17 00:00:00 2001 From: lbartoletti Date: Wed, 2 Oct 2019 09:31:42 +0200 Subject: [PATCH] fix filter: readd QgsLayerTree widget. pep8 --- __init__.py | 10 +++++ config.ui | 30 ++++++++++++++- config_dialog.py | 47 ++++++++++++++++++++---- connection_wrapper.py | 10 +++-- credentials_dialog.py | 5 ++- error_dialog.py | 1 + event_dialog.py | 85 ++++++++++++++++++++++++++++--------------- main.py | 59 ++++++++++++++++++++---------- 8 files changed, 185 insertions(+), 62 deletions(-) diff --git a/__init__.py b/__init__.py index 013aa67..d0ea81e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,13 +1,23 @@ def name(): return u"PostgreSQL history viewer" + + def description(): return u"History viewer for a PostgreSQL base with audit triggers" + + def version(): return u"1.0" + + def qgisMinimumVersion(): return u"3.4" + + def qgisMaximumVersion(): return u"9.99" + + def classFactory(iface): from .main import Plugin return Plugin(iface) diff --git a/config.ui b/config.ui index 0c9d734..e945d93 100644 --- a/config.ui +++ b/config.ui @@ -7,7 +7,7 @@ 0 0 629 - 154 + 472 @@ -76,6 +76,26 @@ + + + + Layer + + + + + + + + + + Corresponding database table + + + + + + @@ -88,6 +108,14 @@ + + + QgsLayerTreeView + QTreeView +
qgis.gui
+ 1 +
+
diff --git a/config_dialog.py b/config_dialog.py index 395fdc6..3be2a45 100644 --- a/config_dialog.py +++ b/config_dialog.py @@ -28,20 +28,29 @@ from .connection_wrapper import ConnectionWrapper -FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'config.ui')) +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'config.ui')) + class ConfigDialog(QDialog, FORM_CLASS): - def __init__(self, parent, db_connection = "", audit_table = "", table_map = {}, replay_function = None): + def __init__(self, parent, db_connection="", audit_table="", table_map={}, replay_function=None): """Constructor. @param parent parent widget """ super(ConfigDialog, self).__init__(parent) self.setupUi(self) - self.reloadBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'icons', 'repeat.svg'))) + self.reloadBtn.setIcon( + QIcon(os.path.join(os.path.dirname(__file__), 'icons', 'repeat.svg'))) self._table_map = table_map + self.tree_group = QgsProject.instance().layerTreeRoot().clone() + self.tree_model = QgsLayerTreeModel(self.tree_group) + self.treeView.setModel(self.tree_model) + + self.treeView.currentLayerChanged.connect(self.onLayerChanged) + # Create database connection wrapper. # Disabled transaction group. self.connection_wrapper = ConnectionWrapper() @@ -49,14 +58,17 @@ def __init__(self, parent, db_connection = "", audit_table = "", table_map = {}, self.reloadBtn.clicked.connect(self.onDatabaseChanged) self.dbConnectionBtn.clicked.connect(self.onBrowseConnection) + self.tableCombo.currentIndexChanged.connect(self.onTableEdit) if db_connection: self.dbConnectionText.setText(db_connection) self.reloadBtn.click() if audit_table: - self.auditTableCombo.setCurrentIndex(self.auditTableCombo.findText(audit_table)) + self.auditTableCombo.setCurrentIndex( + self.auditTableCombo.findText(audit_table)) if replay_function: - self.replayFunctionCombo.setCurrentIndex(self.replayFunctionCombo.findText(replay_function)) + self.replayFunctionCombo.setCurrentIndex( + self.replayFunctionCombo.findText(replay_function)) self.replayFunctionChk.setChecked(True) self.tables = None @@ -84,7 +96,8 @@ def onBrowseConnection(self): s.beginGroup("/PostgreSQL/connections") children = s.childGroups() connections = {} - map = {"dbname":"database", "host":"host", "port":"port", "service":"service", "password":"password", "user":"username", "sslmode": "sslmode"} + map = {"dbname": "database", "host": "host", "port": "port", "service": "service", + "password": "password", "user": "username", "sslmode": "sslmode"} for g in children: s.beginGroup(g) cstring = "" @@ -111,7 +124,7 @@ def onMenu(action): self.reloadBtn.click() menu.triggered.connect(onMenu) - menu.exec_(self.dbConnectionBtn.mapToGlobal(QPoint(0,0))) + menu.exec_(self.dbConnectionBtn.mapToGlobal(QPoint(0, 0))) def onDatabaseChanged(self): dbparams = self.dbConnectionText.text() @@ -136,9 +149,13 @@ def onDatabaseChanged(self): cur.execute(q) + self.tableCombo.clear() + self.tableCombo.addItem("") + for r in cur.fetchall(): t = r[0] + "." + r[1] self.auditTableCombo.addItem(t) + self.tableCombo.addItem(t) # populate functions q = "select routine_schema, routine_name from information_schema.routines where " \ @@ -152,6 +169,22 @@ def onDatabaseChanged(self): t = r[0] + "." + r[1] self.replayFunctionCombo.addItem(t) + def onLayerChanged(self, layer): + if layer is None: + return + table_name = self._table_map.get(layer.id()) + if table_name is not None: + idx = self.tableCombo.findText(table_name) + self.tableCombo.setCurrentIndex(idx) + else: + self.tableCombo.setCurrentIndex(0) + + def onTableEdit(self, idx): + table_name = self.tableCombo.itemText(idx) + current = self.treeView.currentLayer() + if current is not None: + self._table_map[current.id()] = table_name + def table_map(self): return self._table_map diff --git a/connection_wrapper.py b/connection_wrapper.py index 33b5245..026938b 100644 --- a/connection_wrapper.py +++ b/connection_wrapper.py @@ -34,6 +34,8 @@ # In some case transaction group cannot be used. To disable transaction # group use disableTransactionGroup(). # Direct connection allow the use of cursor() for cursor creation. + + class ConnectionWrapper(): # Disable transaction group. @@ -161,7 +163,7 @@ def isConnected(self): # QGis.QgsTransactionGroup database connection. qgisTransactionGroupConnection = None - qgisTransactionGroupDisabled = False + qgisTransactionGroupDisabled = False def __exit__(self, exc_type, exc_value, traceback): self.closeConnection() @@ -200,7 +202,8 @@ def createConnectionFromTransactionsGroup(self, db_connection): uriStr = sourceUri.connectionInfo() try: - print("Getting transactions group for provider ", providerKey, " and database connection: ", uriStr) + print("Getting transactions group for provider ", + providerKey, " and database connection: ", uriStr) return QgsProject.instance().transactionGroup(providerKey, uriStr) except: @@ -233,7 +236,8 @@ def createSingleConnection(self, db_connection): # User has validated: get credentials & create single connection again. else: - db_connection = db_connection + "user='" + self.credDlg.getUserText() + "' password='" + self.credDlg.getPasswordText() + "'" + db_connection = db_connection + "user='" + self.credDlg.getUserText() + \ + "' password='" + self.credDlg.getPasswordText() + "'" return self.createSingleConnection(db_connection) diff --git a/credentials_dialog.py b/credentials_dialog.py index a20cb18..4962531 100644 --- a/credentials_dialog.py +++ b/credentials_dialog.py @@ -23,9 +23,12 @@ from PyQt5.QtGui import * from PyQt5.QtWidgets import QDialog -FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'credentials_dialog.ui')) +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'credentials_dialog.ui')) # Display a dialog for user credentials input. + + class CredentialsDialog(QDialog, FORM_CLASS): def setErrorText(self, text): self.errorText.setText(text) diff --git a/error_dialog.py b/error_dialog.py index 809f3f6..5c6d380 100644 --- a/error_dialog.py +++ b/error_dialog.py @@ -26,6 +26,7 @@ FORM_CLASS, _ = uic.loadUiType(os.path.join( os.path.dirname(__file__), 'error_dialog.ui')) + class ErrorDialog(QDialog, FORM_CLASS): def __init__(self, parent): super(ErrorDialog, self).__init__(parent) diff --git a/event_dialog.py b/event_dialog.py index 0804933..eccf85e 100644 --- a/event_dialog.py +++ b/event_dialog.py @@ -16,6 +16,7 @@ */ """ # -*- coding: utf-8 -*- +import re import os from psycopg2 import Error @@ -39,15 +40,17 @@ FORM_CLASS, _ = uic.loadUiType(os.path.join( os.path.dirname(__file__), 'event_dialog.ui')) -import re # Convert a string representing a hstore from psycopg2 to a Python dict kv_re = re.compile('"(\w+)"=>(NULL|""|".*?[^\\\\]")(?:, |$)') + + def parse_hstore(hstore_str): if hstore_str is None: return {} return dict([(m.group(1), None if m.group(2) == 'NULL' else m.group(2).replace('\\"', '"')[1:-1]) for m in re.finditer(kv_re, hstore_str)]) + def ewkb_to_geom(ewkb_str): if ewkb_str is None: return QgsGeometry() @@ -64,12 +67,15 @@ def ewkb_to_geom(ewkb_str): g.fromWkb(w) return g + def reset_table_widget(table_widget): table_widget.clearContents() for r in range(table_widget.rowCount() - 1, -1, -1): table_widget.removeRow(r) # Incremental loader + + class EventModel(QAbstractTableModel): def __init__(self, cursor): QAbstractItemModel.__init__(self) @@ -80,8 +86,8 @@ def __init__(self, cursor): def flags(self, idx): return Qt.NoItemFlags | Qt.ItemIsSelectable | Qt.ItemIsEnabled - def data(self, idx, role = Qt.DisplayRole): - #print idx.column(), role + def data(self, idx, role=Qt.DisplayRole): + # print idx.column(), role if idx.row() >= len(self.__data): rc = len(self.__data) # fetch more @@ -134,9 +140,10 @@ def rowCount(self, parent): def columnCount(self, parent): return 5 + class GeometryDisplayer: - def __init__(self, canvas ): + def __init__(self, canvas): self.canvas = canvas # main rubber @@ -161,7 +168,7 @@ def oldGeometryColor(self): def newGeometryColor(self): return QColor("#00f") - def display(self, geom1, geom2 = None): + def display(self, geom1, geom2=None): """ @param geom1 base geometry (old geometry for an update) @param geom2 new geometry for an update @@ -177,6 +184,7 @@ def display(self, geom1, geom2 = None): bbox.scale(1.5) self.canvas.setExtent(bbox) + class EventDialog(QDialog, FORM_CLASS): # Editable layer to alter edition mode (transaction group). @@ -190,7 +198,7 @@ class EventDialog(QDialog, FORM_CLASS): # catchLayerModifications = True - def __init__(self, parent, connection_wrapper_read, connection_wrapper_write, map_canvas, audit_table, replay_function = None, table_map = {}, selected_layer_id = None, selected_feature_id = None): + def __init__(self, parent, connection_wrapper_read, connection_wrapper_write, map_canvas, audit_table, replay_function=None, table_map={}, selected_layer_id=None, selected_feature_id=None): """Constructor. @param parent parent widget @param connection_wrapper_read connection wrapper (dbapi2) @@ -211,11 +219,13 @@ def __init__(self, parent, connection_wrapper_read, connection_wrapper_write, ma self.setupUi(self) # reload button icons - self.searchButton.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'icons', 'mActionFilter2.svg'))) - self.replayButton.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'icons', 'mIconWarn.png'))) + self.searchButton.setIcon( + QIcon(os.path.join(os.path.dirname(__file__), 'icons', 'mActionFilter2.svg'))) + self.replayButton.setIcon( + QIcon(os.path.join(os.path.dirname(__file__), 'icons', 'mIconWarn.png'))) # Store connections. - self.connection_wrapper_read = connection_wrapper_read + self.connection_wrapper_read = connection_wrapper_read self.connection_wrapper_write = connection_wrapper_write self.map_canvas = map_canvas @@ -237,7 +247,7 @@ def __init__(self, parent, connection_wrapper_read, connection_wrapper_write, ma # populate layer combo layer_idx = None for i, layer_id in enumerate(self.table_map.keys()): - l = QgsProject.instance().mapLayer( layer_id ) + l = QgsProject.instance().mapLayer(layer_id) if l is None: continue print(layer_id, selected_layer_id) @@ -269,7 +279,7 @@ def __init__(self, parent, connection_wrapper_read, connection_wrapper_write, ma self.geometryGroup.setLayout(self.vbox) self.geometryGroup.hide() - self.hsplitter.setSizes([100,100]) + self.hsplitter.setSizes([100, 100]) self.displayer = GeometryDisplayer(self.map_canvas) self.inner_displayer = GeometryDisplayer(self.inner_canvas) @@ -284,15 +294,19 @@ def __init__(self, parent, connection_wrapper_read, connection_wrapper_write, ma self.oldGeometryLabel = QLabel() self.oldGeometryLabel.setText("------- old geometry") - self.oldGeometryLabel.setStyleSheet("color: " + self.displayer.oldGeometryColor().name()) + self.oldGeometryLabel.setStyleSheet( + "color: " + self.displayer.oldGeometryColor().name()) self.newGeometryLabel = QLabel() - self.newGeometryLabel.setText("------- new geometry (will be restored when replaying event)") - self.newGeometryLabel.setStyleSheet("color: " + self.displayer.newGeometryColor().name()) + self.newGeometryLabel.setText( + "------- new geometry (will be restored when replaying event)") + self.newGeometryLabel.setStyleSheet( + "color: " + self.displayer.newGeometryColor().name()) self.hbox.addWidget(self.oldGeometryLabel) self.hbox.addWidget(self.newGeometryLabel) - self.hbox.addItem(QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Fixed)) + self.hbox.addItem(QSpacerItem( + 20, 20, QSizePolicy.Expanding, QSizePolicy.Fixed)) self.vbox.addLayout(self.hbox) self.vbox.addWidget(self.inner_canvas) @@ -337,8 +351,10 @@ def populate(self): # filter by data if self.dataChck.isChecked(): v = self.dataEdit.text() - v = v.replace('\\', '\\\\').replace("'", "''").replace('%', '\\%').replace('_', '\\_') - wheres.append("(SELECT string_agg(v,' ') FROM svals(row_data) as v) ILIKE '%{}%'".format(v)) + v = v.replace('\\', '\\\\').replace( + "'", "''").replace('%', '\\%').replace('_', '\\_') + wheres.append( + "(SELECT string_agg(v,' ') FROM svals(row_data) as v) ILIKE '%{}%'".format(v)) # filter by event type types = [] @@ -353,13 +369,16 @@ def populate(self): # filter by dates if self.afterChck.isChecked(): dt = self.afterDt.dateTime() - wheres.append("action_tstamp_clk > '{}'".format(dt.toString(Qt.ISODate))) + wheres.append("action_tstamp_clk > '{}'".format( + dt.toString(Qt.ISODate))) if self.beforeChck.isChecked(): dt = self.beforeDt.dateTime() - wheres.append("action_tstamp_clk < '{}'".format(dt.toString(Qt.ISODate))) + wheres.append("action_tstamp_clk < '{}'".format( + dt.toString(Qt.ISODate))) # base query - q = "SELECT event_id, action_tstamp_clk, schema_name || '.' || table_name, action, application_name, session_user_name, row_data, changed_fields FROM {} l".format(self.audit_table) + q = "SELECT event_id, action_tstamp_clk, schema_name || '.' || table_name, action, application_name, session_user_name, row_data, changed_fields FROM {} l".format( + self.audit_table) # where clause if len(wheres) > 0: q += " WHERE " + " AND ".join(wheres) @@ -382,10 +401,10 @@ def populate(self): self.eventTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) - def updateReplayButton(self): self.replayButton.setEnabled(False) - self.replayButton.setToolTip("No replay function or layer is in edition mode: replay action is not available.") + self.replayButton.setToolTip( + "No replay function or layer is in edition mode: replay action is not available.") if self.replay_function and self.replayEnabled == True: self.replayButton.setEnabled(True) @@ -418,7 +437,8 @@ def onEventSelection(self, current_idx, previous_idx): print("Cursor creation has failed") return - q = "SELECT f_geometry_column FROM geometry_columns WHERE f_table_schema='{}' AND f_table_name='{}'".format(schema, table) + q = "SELECT f_geometry_column FROM geometry_columns WHERE f_table_schema='{}' AND f_table_name='{}'".format( + schema, table) cur.execute(q) self.geometry_columns[table_name] = [r[0] for r in cur.fetchall()] gcolumns = self.geometry_columns[table_name] @@ -443,7 +463,8 @@ def onEventSelection(self, current_idx, previous_idx): # update elif action == 'U': self.dataTable.setColumnCount(3) - self.dataTable.setHorizontalHeaderLabels(["Column", "Old value", "New value"]) + self.dataTable.setHorizontalHeaderLabels( + ["Column", "Old value", "New value"]) changed_fields = self.eventModel.changed_fields(i) j = 0 for k, v in data.items(): @@ -479,7 +500,7 @@ def undisplayGeometry(self): self.displayer.reset() self.inner_displayer.reset() - def displayGeometry(self, geom, geom2 = None): + def displayGeometry(self, geom, geom2=None): self.inner_displayer.display(geom, geom2) self.geometryGroup.show() @@ -491,7 +512,8 @@ def onReplayEvent(self): if i == -1: return # event_id from current selection - event_id = self.eventModel.data(self.eventModel.index(i, 0), Qt.UserRole) + event_id = self.eventModel.data( + self.eventModel.index(i, 0), Qt.UserRole) error = "" @@ -511,7 +533,8 @@ def onReplayEvent(self): if error != "": self.error_dlg = ErrorDialog(self) - self.error_dlg.setErrorText("An error has occurred during database access.") + self.error_dlg.setErrorText( + "An error has occurred during database access.") self.error_dlg.setContextText(error) self.error_dlg.setDetailsText("") self.error_dlg.exec_() @@ -528,7 +551,7 @@ def onReplayEvent(self): def isLayerDatabaseCurrentConnection(self, layer): source = layer.source() - layerUri = QgsDataSourceUri(source) + layerUri = QgsDataSourceUri(source) pluginuri = QgsDataSourceUri(self.connection_wrapper_read.db_source) return self.areConnectionsEquals(layerUri, pluginuri) @@ -602,8 +625,10 @@ def updateReplayButtonState(self): # Watch layer edition mode changes. if getattr(layer, "beforeEditingStarted", None) != None and getattr(layer, "editingStopped", None) != None: try: - layer.editingStarted.connect(self.layerEditionModeChanged, Qt.UniqueConnection) - layer.editingStopped.connect(self.layerEditionModeChanged, Qt.UniqueConnection) + layer.editingStarted.connect( + self.layerEditionModeChanged, Qt.UniqueConnection) + layer.editingStopped.connect( + self.layerEditionModeChanged, Qt.UniqueConnection) except: pass diff --git a/main.py b/main.py index 64c58e2..786ec0f 100644 --- a/main.py +++ b/main.py @@ -35,38 +35,52 @@ from .config_dialog import ConfigDialog from .connection_wrapper import ConnectionWrapper -PLUGIN_PATH=os.path.dirname(__file__) +PLUGIN_PATH = os.path.dirname(__file__) + def database_connection_string(): - db_connection, ok = QgsProject.instance().readEntry("HistoryViewer", "db_connection", "") + db_connection, ok = QgsProject.instance().readEntry( + "HistoryViewer", "db_connection", "") return db_connection + def set_database_connection_string(db_connection): QgsProject.instance().writeEntry("HistoryViewer", "db_connection", db_connection) + def project_audit_table(): - audit_table, ok = QgsProject.instance().readEntry("HistoryViewer", "audit_table", "") + audit_table, ok = QgsProject.instance().readEntry( + "HistoryViewer", "audit_table", "") return audit_table + def set_project_replay_function(replay_function): - QgsProject.instance().writeEntry("HistoryViewer", "replay_function", replay_function) + QgsProject.instance().writeEntry( + "HistoryViewer", "replay_function", replay_function) + def project_replay_function(): - replay_function, ok = QgsProject.instance().readEntry("HistoryViewer", "replay_function", "") + replay_function, ok = QgsProject.instance().readEntry( + "HistoryViewer", "replay_function", "") return replay_function + def set_project_audit_table(audit_table): QgsProject.instance().writeEntry("HistoryViewer", "audit_table", audit_table) + def project_table_map(): # get table_map - table_map_strs, ok = QgsProject.instance().readListEntry("HistoryViewer", "table_map", []) + table_map_strs, ok = QgsProject.instance().readListEntry( + "HistoryViewer", "table_map", []) # list of "layer_id=table_name" strings table_map = dict([t.split('=') for t in table_map_strs]) return table_map + def set_project_table_map(table_map): - QgsProject.instance().writeEntry("HistoryViewer", "table_map", [k+"="+v for k,v in table_map.items()]) + QgsProject.instance().writeEntry("HistoryViewer", "table_map", + [k+"="+v for k, v in table_map.items()]) class Plugin(): @@ -80,26 +94,29 @@ def __init__(self, iface): self.connection_wrapper_write = ConnectionWrapper() def initGui(self): - self.listEventsAction = QAction(QIcon(os.path.join(PLUGIN_PATH, "icons", "qaudit-64.png")), u"List events", self.iface.mainWindow()) + self.listEventsAction = QAction(QIcon(os.path.join( + PLUGIN_PATH, "icons", "qaudit-64.png")), u"List events", self.iface.mainWindow()) self.listEventsAction.triggered.connect(self.onListEvents) self.iface.addToolBarIcon(self.listEventsAction) self.iface.addPluginToMenu(plugin_name(), self.listEventsAction) - self.configureAction = QAction(u"Configuration", self.iface.mainWindow()) + self.configureAction = QAction( + u"Configuration", self.iface.mainWindow()) self.configureAction.triggered.connect(self.onConfigure) self.iface.addPluginToMenu(plugin_name(), self.configureAction) def unload(self): self.iface.removeToolBarIcon(self.listEventsAction) - self.iface.removePluginMenu(plugin_name(),self.listEventsAction) - self.iface.removePluginMenu(plugin_name(),self.configureAction) + self.iface.removePluginMenu(plugin_name(), self.listEventsAction) + self.iface.removePluginMenu(plugin_name(), self.configureAction) - def onListEvents(self, layer_id = None, feature_id = None): + def onListEvents(self, layer_id=None, feature_id=None): # Get database connection string. db_connection = database_connection_string() if not db_connection: - QMessageBox.critical(None, "Configuration problem", "No database configuration has been found, please configure the project") + QMessageBox.critical(None, "Configuration problem", + "No database configuration has been found, please configure the project") r = self.onConfigure() # Retry if needed. @@ -115,7 +132,7 @@ def onListEvents(self, layer_id = None, feature_id = None): # Reuse read connection for write direct connection. self.connection_wrapper_write.psycopg2Connection = self.connection_wrapper_read.psycopg2Connection - self.connection_wrapper_write.db_source = self.connection_wrapper_read.db_source + self.connection_wrapper_write.db_source = self.connection_wrapper_read.db_source self.connection_wrapper_write.openConnection(db_connection) @@ -132,17 +149,18 @@ def onListEvents(self, layer_id = None, feature_id = None): self.connection_wrapper_write, self.iface.mapCanvas(), project_audit_table(), - replay_function = project_replay_function(), - table_map = table_map, - selected_layer_id = layer_id, - selected_feature_id = feature_id) + replay_function=project_replay_function(), + table_map=table_map, + selected_layer_id=layer_id, + selected_feature_id=feature_id) # Populate dialog & catch error if any. try: self.dlg.populate() except Error as e: - QMessageBox.critical(None, "Configuration problem", "Database configuration is invalid, please check the project configuration") + QMessageBox.critical(None, "Configuration problem", + "Database configuration is invalid, please check the project configuration") r = self.onConfigure() # Retry if needed. @@ -160,7 +178,8 @@ def onConfigure(self): db_connection = database_connection_string() audit_table = project_audit_table() replay_function = project_replay_function() - self.config_dlg = ConfigDialog(self.iface.mainWindow(), db_connection, audit_table, table_map, replay_function) + self.config_dlg = ConfigDialog(self.iface.mainWindow( + ), db_connection, audit_table, table_map, replay_function) r = self.config_dlg.exec_() if r == 1: