Skip to content

Commit 6c7c2d8

Browse files
committed
exclude truncate on templates
1 parent ffcf30e commit 6c7c2d8

File tree

8 files changed

+107
-29
lines changed

8 files changed

+107
-29
lines changed

database_manager.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,11 +1044,24 @@ def drop_database_connections(self, database_name):
10441044
self.progress_updated.emit(error_msg)
10451045
return False
10461046

1047-
def create_template(self, source_db, template_name, template_comment=None):
1048-
"""Create a template from source database with optional comment."""
1047+
def create_template(self, source_db, template_name, template_comment=None,
1048+
preserve_qgis_projects=False, excluded_schemas=None):
1049+
"""Create a template from source database with optional comment and data preservation options."""
10491050
try:
1051+
if excluded_schemas is None:
1052+
excluded_schemas = []
1053+
10501054
self.progress_updated.emit(f"Creating template '{template_name}' from '{source_db}'...")
10511055

1056+
# Log preservation settings
1057+
if preserve_qgis_projects or excluded_schemas:
1058+
preservation_info = []
1059+
if preserve_qgis_projects:
1060+
preservation_info.append("qgis_projects table will be preserved")
1061+
if excluded_schemas:
1062+
preservation_info.append(f"schemas {excluded_schemas} will be preserved")
1063+
self.progress_updated.emit(f"Data preservation: {'; '.join(preservation_info)}")
1064+
10521065
# Check for active connections first
10531066
connection_count = self.get_connection_count(source_db)
10541067
if connection_count > 0:
@@ -1090,9 +1103,7 @@ def create_template(self, source_db, template_name, template_comment=None):
10901103
cursor.execute(f"COMMENT ON DATABASE \"{template_name}\" IS '{escaped_comment}';")
10911104
self.progress_updated.emit(f"Comment added: {template_comment}")
10921105

1093-
self.progress_updated.emit("Removing data from template...")
1094-
1095-
# Connect to template database to remove data
1106+
# Connect to template database to remove data selectively
10961107
cursor.close()
10971108
conn.close()
10981109

@@ -1113,21 +1124,50 @@ def create_template(self, source_db, template_name, template_comment=None):
11131124

11141125
tables = template_cursor.fetchall()
11151126

1116-
# Truncate all tables
1127+
# Process tables with selective preservation
1128+
truncated_count = 0
1129+
preserved_count = 0
1130+
1131+
self.progress_updated.emit("Processing tables with data preservation rules...")
1132+
11171133
for schema, table in tables:
1134+
# Check if this schema should be excluded entirely
1135+
if schema in excluded_schemas:
1136+
self.progress_updated.emit(f"Preserving data in {schema}.{table} (excluded schema)")
1137+
preserved_count += 1
1138+
continue
1139+
1140+
# Check if this is the qgis_projects table and should be preserved
1141+
if preserve_qgis_projects and table == 'qgis_projects':
1142+
self.progress_updated.emit(f"Preserving data in {schema}.{table} (qgis_projects table)")
1143+
preserved_count += 1
1144+
continue
1145+
1146+
# Truncate this table
11181147
try:
11191148
template_cursor.execute(f'TRUNCATE TABLE "{schema}"."{table}" CASCADE;')
11201149
self.progress_updated.emit(f"Cleared data from {schema}.{table}")
1150+
truncated_count += 1
11211151
except psycopg2.Error as e:
11221152
self.log_message(f"Warning: Could not truncate {schema}.{table}: {str(e)}", Qgis.Warning)
11231153

11241154
template_cursor.close()
11251155
template_conn.close()
11261156

1157+
# Prepare success message with detailed info
11271158
success_msg = f"Template '{template_name}' created successfully!"
11281159
if template_comment:
11291160
success_msg += f" Comment: {template_comment}"
11301161

1162+
details = []
1163+
if truncated_count > 0:
1164+
details.append(f"{truncated_count} tables truncated")
1165+
if preserved_count > 0:
1166+
details.append(f"{preserved_count} tables preserved")
1167+
1168+
if details:
1169+
success_msg += f" ({', '.join(details)})"
1170+
11311171
self.progress_updated.emit(success_msg)
11321172
self.operation_finished.emit(True, success_msg)
11331173
return True
@@ -1137,7 +1177,7 @@ def create_template(self, source_db, template_name, template_comment=None):
11371177
self.log_message(error_msg, Qgis.Critical)
11381178
self.operation_finished.emit(False, error_msg)
11391179
return False
1140-
1180+
11411181
def create_database_from_template(self, template_name, new_db_name, db_comment=None):
11421182
"""Create a new database from template with optional comment."""
11431183
try:

tabs/archive_project_tab.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,6 @@ def _show_help_popup(self):
227227
msg.setWindowTitle("Help - Portable Project Archiver")
228228
msg.setTextFormat(1) # Rich text format
229229
msg.setText(help_text)
230-
msg.setIcon(QMessageBox.Information)
231230
msg.setStandardButtons(QMessageBox.Ok)
232231
msg.exec_()
233232

tabs/clean_qgs_tab.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ def _show_help_popup(self):
184184
msg.setWindowTitle("Help - QGS File Credential Cleaner")
185185
msg.setTextFormat(1) # Rich text format
186186
msg.setText(help_text)
187-
msg.setIcon(QMessageBox.Information)
188187
msg.setStandardButtons(QMessageBox.Ok)
189188
msg.exec_()
190189

tabs/connection_tab.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ def _show_help_popup(self):
121121
msg.setWindowTitle("Help - PostgreSQL Connection")
122122
msg.setTextFormat(1) # Rich text format
123123
msg.setText(help_text)
124-
msg.setIcon(QMessageBox.Information)
125124
msg.setStandardButtons(QMessageBox.Ok)
126125
msg.exec_()
127126

tabs/databases_tab.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,31 +371,31 @@ def _show_help_popup(self):
371371
help_text = (
372372
"<h3>Database Manager</h3>"
373373
"<p>Create and manage PostgreSQL databases from templates or existing databases.</p>"
374-
"<h4>🆕 Database Creation:</h4>"
374+
"<h4>Database Creation:</h4>"
375375
"<ul>"
376376
"<li><b>From Template:</b> Creates database from template with empty data</li>"
377377
"<li><b>From Existing Database:</b> Creates a copy of an existing database</li>"
378378
"<li><b>Ready to use:</b> Database is immediately available for connections</li>"
379379
"</ul>"
380-
"<h4>🗑️ Database Deletion:</h4>"
380+
"<h4>Database Deletion:</h4>"
381381
"<ul>"
382-
"<li><b>⚠️ IRREVERSIBLE:</b> Once deleted, data cannot be recovered</li>"
382+
"<li><b>IRREVERSIBLE:</b> Once deleted, data cannot be recovered</li>"
383383
"<li><b>Strong warning:</b> Single confirmation dialog with clear consequences</li>"
384384
"<li><b>Safety checks:</b> Prevents deletion of system databases</li>"
385385
"</ul>"
386-
"<h4>💬 Database Comments:</h4>"
386+
"<h4>Database Comments:</h4>"
387387
"<ul>"
388388
"<li><b>Documentation:</b> Comments help identify database purpose and content</li>"
389389
"<li><b>Metadata:</b> Comments are stored in PostgreSQL's system catalog</li>"
390390
"</ul>"
391-
"<h4>📋 Database Creation Process:</h4>"
391+
"<h4>Database Creation Process:</h4>"
392392
"<ol>"
393393
"<li><b>Choose source type:</b> Select template or existing database</li>"
394394
"<li><b>Select source:</b> Choose from available templates or databases</li>"
395395
"<li><b>Name database:</b> Enter a unique name (letters, numbers, underscores only)</li>"
396396
"<li><b>Create:</b> PostgreSQL creates the new database</li>"
397397
"</ol>"
398-
"<h4>🔥 Database Deletion Process:</h4>"
398+
"<h4>Database Deletion Process:</h4>"
399399
"<ol>"
400400
"<li><b>Select database:</b> Choose database from the list</li>"
401401
"<li><b>Type database name:</b> Confirm by typing exact database name</li>"
@@ -407,7 +407,6 @@ def _show_help_popup(self):
407407
msg.setWindowTitle("Help - Database Manager")
408408
msg.setTextFormat(1) # Rich text format
409409
msg.setText(help_text)
410-
msg.setIcon(QMessageBox.Information)
411410
msg.setStandardButtons(QMessageBox.Ok)
412411

413412
# Make dialog larger to accommodate more text

tabs/qgis_projects_tab.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ def _show_help_popup(self):
148148
msg.setWindowTitle("Help - QGIS Project Layer Updater")
149149
msg.setTextFormat(1) # Rich text format
150150
msg.setText(help_text)
151-
msg.setIcon(QMessageBox.Information)
152151
msg.setStandardButtons(QMessageBox.Ok)
153152
msg.exec_()
154153

tabs/templates_tab.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from qgis.PyQt.QtWidgets import (QVBoxLayout, QHBoxLayout, QFormLayout,
88
QComboBox, QLineEdit, QPushButton, QGroupBox,
99
QTableWidget, QTableWidgetItem, QMessageBox, QDialog, QDialogButtonBox,
10-
QLabel, QTextEdit, QHeaderView)
10+
QLabel, QTextEdit, QHeaderView, QCheckBox)
1111
from qgis.PyQt.QtCore import Qt
1212
from qgis.PyQt.QtGui import QFont
1313
from .base_tab import BaseTab
@@ -140,12 +140,25 @@ def setup_ui(self):
140140
self.template_comment_edit = QLineEdit()
141141
self.template_comment_edit.setPlaceholderText("Optional description for the template (e.g., 'Production schema template for project X')")
142142

143+
# Add checkbox for qgis_projects table protection
144+
self.protect_qgis_projects_checkbox = QCheckBox("Preserve qgis_projects table data")
145+
self.protect_qgis_projects_checkbox.setChecked(True) # Default checked
146+
self.protect_qgis_projects_checkbox.setToolTip("When checked, the 'qgis_projects' table will not be truncated and its data will be preserved in the template")
147+
148+
# Add input for schema exclusions
149+
self.exclude_schemas_edit = QLineEdit()
150+
self.exclude_schemas_edit.setText("listen") # Prefill with "listen"
151+
self.exclude_schemas_edit.setPlaceholderText("Comma-separated list of schemas to preserve (e.g., listen, metadata, system)")
152+
self.exclude_schemas_edit.setToolTip("Enter schema names separated by commas. Tables in these schemas will not be truncated and their data will be preserved in the template")
153+
143154
self.create_template_btn = QPushButton("Create Template")
144155
self.create_template_btn.clicked.connect(self.create_template)
145156

146157
create_layout.addRow("Source Database:", self.source_db_combo)
147158
create_layout.addRow("Template Name:", self.template_name_edit)
148159
create_layout.addRow("Comment:", self.template_comment_edit)
160+
create_layout.addRow("", self.protect_qgis_projects_checkbox)
161+
create_layout.addRow("Exclude Schemas:", self.exclude_schemas_edit)
149162
create_layout.addWidget(self.create_template_btn)
150163

151164
layout.addWidget(create_group)
@@ -192,28 +205,31 @@ def _show_help_popup(self):
192205
"<li><b>Name the template:</b> Give your template a descriptive name</li>"
193206
"<li><b>Add comment (optional):</b> Provide a description to help identify the template's purpose, "
194207
"such as 'Production schema for customer management system' or 'Development environment template'</li>"
208+
"<li><b>Configure data preservation:</b> Choose which tables/schemas to preserve data from</li>"
195209
"<li><b>Structure copied:</b> All tables, views, functions, indexes are copied</li>"
196-
"<li><b>Data removed:</b> Template contains no records (empty tables)</li>"
210+
"<li><b>Data handling:</b> By default, template contains no records, but you can preserve specific tables/schemas</li>"
197211
"<li><b>Template ready:</b> Use template to create new databases instantly</li>"
198212
"</ol>"
213+
"<h4>Data Preservation Options:</h4>"
214+
"<ul>"
215+
"<li><b>Preserve qgis_projects table:</b> When checked, the 'qgis_projects' table data will be kept in the template</li>"
216+
"<li><b>Exclude Schemas:</b> Comma-separated list of schema names whose tables will not be truncated</li>"
217+
"</ul>"
199218
"<h4>Template Comments:</h4>"
200219
"<p>The <b>comment field</b> allows you to add a descriptive note to your template that will be stored "
201-
"in the PostgreSQL database catalog. This helps you and other users understand the purpose and content "
202-
"of each template. Comments are especially useful when managing multiple templates.</p>"
220+
"in the PostgreSQL database catalog. This helps you and other users understand the purpose and content. </p>"
203221
"<h4>⚠️ Important notes:</h4>"
204222
"<ul>"
205223
"<li><b>Active connections:</b> All connections to source database will be dropped during creation</li>"
206-
"<li><b>Structure only:</b> Templates preserve schema but remove all data</li>"
207-
"<li><b>PostgreSQL feature:</b> Uses native PostgreSQL template functionality</li>"
208-
"<li><b>Comments are permanent:</b> Once set, comments become part of the database metadata</li>"
224+
"<li><b>Structure preserved:</b> Templates preserve schema and optionally selected data</li>"
225+
"<li><b>Schema exclusions:</b> Tables in excluded schemas will keep their data in the template</li>"
209226
"</ul>"
210227
)
211228

212229
msg = QMessageBox(self)
213230
msg.setWindowTitle("Help - PostgreSQL Template Manager")
214231
msg.setTextFormat(1) # Rich text format
215232
msg.setText(help_text)
216-
msg.setIcon(QMessageBox.Information)
217233
msg.setStandardButtons(QMessageBox.Ok)
218234
msg.exec_()
219235

@@ -271,6 +287,15 @@ def create_template(self):
271287
template_name = self.template_name_edit.text().strip()
272288
template_comment = self.template_comment_edit.text().strip()
273289

290+
# Get the new options
291+
preserve_qgis_projects = self.protect_qgis_projects_checkbox.isChecked()
292+
exclude_schemas_text = self.exclude_schemas_edit.text().strip()
293+
294+
# Parse excluded schemas
295+
excluded_schemas = []
296+
if exclude_schemas_text:
297+
excluded_schemas = [schema.strip() for schema in exclude_schemas_text.split(",") if schema.strip()]
298+
274299
if not self.validate_selection(self.source_db_combo, "source database"):
275300
return
276301

@@ -305,11 +330,21 @@ def create_template(self):
305330
else:
306331
# No active connections, but still show a confirmation
307332
confirmation_text = f"Create template '{template_name}' from database '{source_db}'?\n\n"
308-
confirmation_text += "This will create a copy of the database structure without data."
333+
confirmation_text += "This will create a copy of the database structure with selected data preservation options."
309334

310335
if template_comment:
311336
confirmation_text += f"\n\nComment: {template_comment}"
312337

338+
# Add information about data preservation
339+
if preserve_qgis_projects or excluded_schemas:
340+
confirmation_text += "\n\nData preservation settings:"
341+
if preserve_qgis_projects:
342+
confirmation_text += "\n• qgis_projects table data will be preserved"
343+
if excluded_schemas:
344+
confirmation_text += f"\n• Schemas excluded from truncation: {', '.join(excluded_schemas)}"
345+
else:
346+
confirmation_text += "\n\nAll table data will be removed (standard template behavior)."
347+
313348
reply = QMessageBox.question(
314349
self,
315350
"Create Template",
@@ -323,7 +358,13 @@ def create_template(self):
323358

324359
# Proceed with template creation
325360
self.emit_progress_started()
326-
self.db_manager.create_template(source_db, template_name, template_comment)
361+
self.db_manager.create_template(
362+
source_db,
363+
template_name,
364+
template_comment,
365+
preserve_qgis_projects=preserve_qgis_projects,
366+
excluded_schemas=excluded_schemas
367+
)
327368

328369
except Exception as e:
329370
self.emit_log(f"Error checking connections: {str(e)}")
@@ -362,6 +403,9 @@ def on_operation_finished(self, success, message):
362403
self.emit_log(f"✓ {message}")
363404
self.template_name_edit.clear()
364405
self.template_comment_edit.clear()
406+
# Reset data preservation options to defaults
407+
self.protect_qgis_projects_checkbox.setChecked(True)
408+
self.exclude_schemas_edit.setText("listen")
365409
self.refresh_templates()
366410
else:
367411
self.emit_log(f"✗ {message}")

tabs/truncate_tab.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ def _show_help_popup(self):
288288
msg.setWindowTitle("Help - Truncate Tables")
289289
msg.setTextFormat(1) # Rich text format
290290
msg.setText(help_text)
291-
msg.setIcon(QMessageBox.Information)
292291
msg.setStandardButtons(QMessageBox.Ok)
293292

294293
# Make dialog appropriately sized

0 commit comments

Comments
 (0)