23 |
29 |
24 class DjangoMigrationsListDialog(QDialog, Ui_DjangoMigrationsListDialog): |
30 class DjangoMigrationsListDialog(QDialog, Ui_DjangoMigrationsListDialog): |
25 """ |
31 """ |
26 Class implementing a dialog show a list of all available migrations. |
32 Class implementing a dialog show a list of all available migrations. |
27 """ |
33 """ |
|
34 |
28 MigrationsListMode = "L" |
35 MigrationsListMode = "L" |
29 MigrationsPlanMode = "P" |
36 MigrationsPlanMode = "P" |
30 |
37 |
31 def __init__(self, mode, django, parent=None): |
38 def __init__(self, mode, django, parent=None): |
32 """ |
39 """ |
33 Constructor |
40 Constructor |
34 |
41 |
35 @param mode mode of the dialog |
42 @param mode mode of the dialog |
36 @type str |
43 @type str |
37 @param django reference to the Django project object |
44 @param django reference to the Django project object |
38 @type Project |
45 @type Project |
39 @param parent reference to the parent widget |
46 @param parent reference to the parent widget |
40 @type QWidget |
47 @type QWidget |
41 """ |
48 """ |
42 super().__init__(parent) |
49 super().__init__(parent) |
43 self.setupUi(self) |
50 self.setupUi(self) |
44 |
51 |
45 self.buttonBox.button( |
52 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) |
46 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
53 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
47 self.buttonBox.button( |
54 |
48 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
49 |
|
50 self.ioEncoding = Preferences.getSystem("IOEncoding") |
55 self.ioEncoding = Preferences.getSystem("IOEncoding") |
51 |
56 |
52 self.proc = QProcess() |
57 self.proc = QProcess() |
53 self.proc.finished.connect(self.__procFinished) |
58 self.proc.finished.connect(self.__procFinished) |
54 self.proc.readyReadStandardOutput.connect(self.__readStdout) |
59 self.proc.readyReadStandardOutput.connect(self.__readStdout) |
55 self.proc.readyReadStandardError.connect(self.__readStderr) |
60 self.proc.readyReadStandardError.connect(self.__readStderr) |
56 |
61 |
57 self.__pyExe = "" |
62 self.__pyExe = "" |
58 self.__sitePath = "" |
63 self.__sitePath = "" |
59 |
64 |
60 self.__django = django |
65 self.__django = django |
61 |
66 |
62 self.__mode = mode |
67 self.__mode = mode |
63 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
68 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
64 self.setWindowTitle(self.tr("Available Migrations")) |
69 self.setWindowTitle(self.tr("Available Migrations")) |
65 self.migrationsList.setHeaderLabels([ |
70 self.migrationsList.setHeaderLabels( |
66 self.tr("Name"), |
71 [ |
67 ]) |
72 self.tr("Name"), |
|
73 ] |
|
74 ) |
68 self.migrationsList.setSelectionMode( |
75 self.migrationsList.setSelectionMode( |
69 QAbstractItemView.SelectionMode.ExtendedSelection) |
76 QAbstractItemView.SelectionMode.ExtendedSelection |
|
77 ) |
70 else: |
78 else: |
71 self.setWindowTitle(self.tr("Migrations Plan")) |
79 self.setWindowTitle(self.tr("Migrations Plan")) |
72 self.migrationsList.setHeaderLabels([ |
80 self.migrationsList.setHeaderLabels( |
73 self.tr("Migration"), |
81 [ |
74 self.tr("Dependencies"), |
82 self.tr("Migration"), |
75 ]) |
83 self.tr("Dependencies"), |
|
84 ] |
|
85 ) |
76 self.migrationsList.setSelectionMode( |
86 self.migrationsList.setSelectionMode( |
77 QAbstractItemView.SelectionMode.SingleSelection) |
87 QAbstractItemView.SelectionMode.SingleSelection |
78 |
88 ) |
|
89 |
79 self.refreshButton = self.buttonBox.addButton( |
90 self.refreshButton = self.buttonBox.addButton( |
80 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole) |
91 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole |
81 self.refreshButton.setToolTip( |
92 ) |
82 self.tr("Press to refresh the list")) |
93 self.refreshButton.setToolTip(self.tr("Press to refresh the list")) |
83 self.refreshButton.setEnabled(False) |
94 self.refreshButton.setEnabled(False) |
84 |
95 |
85 @pyqtSlot(QAbstractButton) |
96 @pyqtSlot(QAbstractButton) |
86 def on_buttonBox_clicked(self, button): |
97 def on_buttonBox_clicked(self, button): |
87 """ |
98 """ |
88 Private slot called by a button of the button box clicked. |
99 Private slot called by a button of the button box clicked. |
89 |
100 |
90 @param button button that was clicked |
101 @param button button that was clicked |
91 @type QAbstractButton |
102 @type QAbstractButton |
92 """ |
103 """ |
93 if button == self.buttonBox.button( |
104 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): |
94 QDialogButtonBox.StandardButton.Close |
|
95 ): |
|
96 self.close() |
105 self.close() |
97 elif button == self.buttonBox.button( |
106 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): |
98 QDialogButtonBox.StandardButton.Cancel |
|
99 ): |
|
100 self.__finish() |
107 self.__finish() |
101 elif button == self.refreshButton: |
108 elif button == self.refreshButton: |
102 self.on_refreshButton_clicked() |
109 self.on_refreshButton_clicked() |
103 |
110 |
104 def __finish(self): |
111 def __finish(self): |
105 """ |
112 """ |
106 Private slot called when the process finished or the user pressed the |
113 Private slot called when the process finished or the user pressed the |
107 button. |
114 button. |
108 """ |
115 """ |
109 if ( |
116 if ( |
110 self.proc is not None and |
117 self.proc is not None |
111 self.proc.state() != QProcess.ProcessState.NotRunning |
118 and self.proc.state() != QProcess.ProcessState.NotRunning |
112 ): |
119 ): |
113 self.proc.terminate() |
120 self.proc.terminate() |
114 QTimer.singleShot(2000, self.proc.kill) |
121 QTimer.singleShot(2000, self.proc.kill) |
115 self.proc.waitForFinished(3000) |
122 self.proc.waitForFinished(3000) |
116 |
123 |
117 self.buttonBox.button( |
124 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) |
118 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
125 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
119 self.buttonBox.button( |
126 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) |
120 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
127 |
121 self.buttonBox.button( |
|
122 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
123 |
|
124 self.refreshButton.setEnabled(True) |
128 self.refreshButton.setEnabled(True) |
125 |
129 |
126 self.__resizeColumns() |
130 self.__resizeColumns() |
127 |
131 |
128 def __procFinished(self, exitCode, exitStatus): |
132 def __procFinished(self, exitCode, exitStatus): |
129 """ |
133 """ |
130 Private slot connected to the finished signal. |
134 Private slot connected to the finished signal. |
131 |
135 |
132 @param exitCode exit code of the process |
136 @param exitCode exit code of the process |
133 @type int |
137 @type int |
134 @param exitStatus exit status of the process |
138 @param exitStatus exit status of the process |
135 @type QProcess.ExitStatus |
139 @type QProcess.ExitStatus |
136 """ |
140 """ |
137 self.__finish() |
141 self.__finish() |
138 |
142 |
139 def __resizeColumns(self): |
143 def __resizeColumns(self): |
140 """ |
144 """ |
141 Private method to resize the list columns. |
145 Private method to resize the list columns. |
142 """ |
146 """ |
143 self.migrationsList.header().resizeSections( |
147 self.migrationsList.header().resizeSections( |
144 QHeaderView.ResizeMode.ResizeToContents) |
148 QHeaderView.ResizeMode.ResizeToContents |
|
149 ) |
145 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
150 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
146 self.migrationsList.header().setStretchLastSection(True) |
151 self.migrationsList.header().setStretchLastSection(True) |
147 |
152 |
148 def start(self, pythonExecutable, sitePath, databaseName): |
153 def start(self, pythonExecutable, sitePath, databaseName): |
149 """ |
154 """ |
150 Public slot used to start the process. |
155 Public slot used to start the process. |
151 |
156 |
152 @param pythonExecutable Python executable to be used |
157 @param pythonExecutable Python executable to be used |
153 @type str |
158 @type str |
154 @param sitePath path of the site |
159 @param sitePath path of the site |
155 @type str |
160 @type str |
156 @param databaseName name of the database to be used |
161 @param databaseName name of the database to be used |
178 args.append("--plan") |
183 args.append("--plan") |
179 args.append("--verbosity") |
184 args.append("--verbosity") |
180 args.append("2") |
185 args.append("2") |
181 if databaseName: |
186 if databaseName: |
182 args.append("--database={0}".format(databaseName)) |
187 args.append("--database={0}".format(databaseName)) |
183 |
188 |
184 self.proc.start(pythonExecutable, args) |
189 self.proc.start(pythonExecutable, args) |
185 procStarted = self.proc.waitForStarted() |
190 procStarted = self.proc.waitForStarted() |
186 if not procStarted: |
191 if not procStarted: |
187 self.buttonBox.setFocus() |
192 self.buttonBox.setFocus() |
188 EricMessageBox.critical( |
193 EricMessageBox.critical( |
189 self, |
194 self, |
190 self.tr('Process Generation Error'), |
195 self.tr("Process Generation Error"), |
191 self.tr( |
196 self.tr( |
192 'The process {0} could not be started. ' |
197 "The process {0} could not be started. " |
193 'Ensure, that it is in the search path.' |
198 "Ensure, that it is in the search path." |
194 ).format(pythonExecutable)) |
199 ).format(pythonExecutable), |
|
200 ) |
195 return procStarted |
201 return procStarted |
196 |
202 |
197 def __readStdout(self): |
203 def __readStdout(self): |
198 """ |
204 """ |
199 Private slot to handle the readyReadStdout signal. |
205 Private slot to handle the readyReadStdout signal. |
200 |
206 |
201 It reads the output of the process, formats it and inserts it into |
207 It reads the output of the process, formats it and inserts it into |
202 the contents pane. |
208 the contents pane. |
203 """ |
209 """ |
204 while self.proc.canReadLine(): |
210 while self.proc.canReadLine(): |
205 s = str(self.proc.readLine(), self.ioEncoding, 'replace').rstrip() |
211 s = str(self.proc.readLine(), self.ioEncoding, "replace").rstrip() |
206 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
212 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
207 self.__createListItem(s) |
213 self.__createListItem(s) |
208 else: |
214 else: |
209 self.__createPlanItem(s) |
215 self.__createPlanItem(s) |
210 |
216 |
211 def __createListItem(self, line): |
217 def __createListItem(self, line): |
212 """ |
218 """ |
213 Private method to create an item for list mode. |
219 Private method to create an item for list mode. |
214 |
220 |
215 @param line line of text |
221 @param line line of text |
216 @type str |
222 @type str |
217 """ |
223 """ |
218 if not line.startswith(" "): |
224 if not line.startswith(" "): |
219 # application name |
225 # application name |
220 self.__lastTopItem = QTreeWidgetItem( |
226 self.__lastTopItem = QTreeWidgetItem(self.migrationsList, [line.strip()]) |
221 self.migrationsList, [line.strip()]) |
|
222 self.__lastTopItem.setExpanded(True) |
227 self.__lastTopItem.setExpanded(True) |
223 else: |
228 else: |
224 # migration name |
229 # migration name |
225 line = line.strip() |
230 line = line.strip() |
226 if line.startswith("["): |
231 if line.startswith("["): |
232 itm = QTreeWidgetItem(self.migrationsList, [name]) |
237 itm = QTreeWidgetItem(self.migrationsList, [name]) |
233 if applied[1] == " ": |
238 if applied[1] == " ": |
234 itm.setCheckState(0, Qt.CheckState.Unchecked) |
239 itm.setCheckState(0, Qt.CheckState.Unchecked) |
235 else: |
240 else: |
236 itm.setCheckState(0, Qt.CheckState.Checked) |
241 itm.setCheckState(0, Qt.CheckState.Checked) |
237 |
242 |
238 def __createPlanItem(self, line): |
243 def __createPlanItem(self, line): |
239 """ |
244 """ |
240 Private method to create an item for plan mode. |
245 Private method to create an item for plan mode. |
241 |
246 |
242 @param line line of text |
247 @param line line of text |
243 @type str |
248 @type str |
244 """ |
249 """ |
245 line = line.strip() |
250 line = line.strip() |
246 applied = line[:3] |
251 applied = line[:3] |
247 parts = line[3:].strip().split(None, 2) |
252 parts = line[3:].strip().split(None, 2) |
248 if len(parts) == 3: |
253 if len(parts) == 3: |
249 dependencies = "\n".join([ |
254 dependencies = "\n".join( |
250 d.strip() for d in parts[2].strip()[1:-1].split(",") |
255 [d.strip() for d in parts[2].strip()[1:-1].split(",")] |
251 ]) |
256 ) |
252 itm = QTreeWidgetItem(self.migrationsList, [ |
257 itm = QTreeWidgetItem( |
253 parts[0].strip(), |
258 self.migrationsList, |
254 dependencies, |
259 [ |
255 ]) |
260 parts[0].strip(), |
256 else: |
261 dependencies, |
257 itm = QTreeWidgetItem(self.migrationsList, [ |
262 ], |
258 parts[0].strip(), |
263 ) |
259 "", |
264 else: |
260 ]) |
265 itm = QTreeWidgetItem( |
|
266 self.migrationsList, |
|
267 [ |
|
268 parts[0].strip(), |
|
269 "", |
|
270 ], |
|
271 ) |
261 if applied[1] != " ": |
272 if applied[1] != " ": |
262 itm.setCheckState(0, Qt.CheckState.Checked) |
273 itm.setCheckState(0, Qt.CheckState.Checked) |
263 |
274 |
264 def __readStderr(self): |
275 def __readStderr(self): |
265 """ |
276 """ |
266 Private slot to handle the readyReadStderr signal. |
277 Private slot to handle the readyReadStderr signal. |
267 |
278 |
268 It reads the error output of the process and inserts it into the |
279 It reads the error output of the process and inserts it into the |
269 error pane. |
280 error pane. |
270 """ |
281 """ |
271 if self.proc is not None: |
282 if self.proc is not None: |
272 self.errorGroup.show() |
283 self.errorGroup.show() |
273 s = str(self.proc.readAllStandardError(), self.ioEncoding, |
284 s = str(self.proc.readAllStandardError(), self.ioEncoding, "replace") |
274 'replace') |
|
275 self.errors.insertPlainText(s) |
285 self.errors.insertPlainText(s) |
276 self.errors.ensureCursorVisible() |
286 self.errors.ensureCursorVisible() |
277 |
287 |
278 @pyqtSlot() |
288 @pyqtSlot() |
279 def on_refreshButton_clicked(self): |
289 def on_refreshButton_clicked(self): |
280 """ |
290 """ |
281 Private slot to refresh the log. |
291 Private slot to refresh the log. |
282 """ |
292 """ |
283 self.buttonBox.button( |
293 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) |
284 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
294 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
285 self.buttonBox.button( |
295 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
286 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
296 |
287 self.buttonBox.button( |
|
288 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
289 |
|
290 self.refreshButton.setEnabled(False) |
297 self.refreshButton.setEnabled(False) |
291 |
298 |
292 self.start(self.__pyExe, self.__sitePath) |
299 self.start(self.__pyExe, self.__sitePath) |
293 |
300 |
294 @pyqtSlot(QPoint) |
301 @pyqtSlot(QPoint) |
295 def on_migrationsList_customContextMenuRequested(self, pos): |
302 def on_migrationsList_customContextMenuRequested(self, pos): |
296 """ |
303 """ |
297 Private slot to show the context menu. |
304 Private slot to show the context menu. |
298 |
305 |
299 @param pos position the context menu was requested at |
306 @param pos position the context menu was requested at |
300 @type QPoint |
307 @type QPoint |
301 """ |
308 """ |
302 menu = QMenu(self.migrationsList) |
309 menu = QMenu(self.migrationsList) |
303 menu.addAction(self.tr("Apply All Migrations"), |
310 menu.addAction(self.tr("Apply All Migrations"), self.__applyAllMigrations) |
304 self.__applyAllMigrations) |
311 |
305 |
|
306 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
312 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
307 selItems = self.migrationsList.selectedItems() |
313 selItems = self.migrationsList.selectedItems() |
308 if len(selItems) > 0: |
314 if len(selItems) > 0: |
309 selApps = [] |
315 selApps = [] |
310 for itm in selItems: |
316 for itm in selItems: |
311 if itm.parent() is None: |
317 if itm.parent() is None: |
312 selApps.append(itm) |
318 selApps.append(itm) |
313 |
319 |
314 menu.addAction( |
320 menu.addAction( |
315 self.tr("Apply Selected Migrations"), |
321 self.tr("Apply Selected Migrations"), self.__applyMigration |
316 self.__applyMigration).setEnabled(len(selItems) == 1) |
322 ).setEnabled(len(selItems) == 1) |
317 menu.addAction( |
323 menu.addAction( |
318 self.tr("Unapply Migrations"), |
324 self.tr("Unapply Migrations"), self.__unapplyMigration |
319 self.__unapplyMigration).setEnabled(len(selApps) == 1) |
325 ).setEnabled(len(selApps) == 1) |
320 menu.addSeparator() |
326 menu.addSeparator() |
321 menu.addAction( |
327 menu.addAction( |
322 self.tr("Make Migrations"), |
328 self.tr("Make Migrations"), self.__makeMigrations |
323 self.__makeMigrations).setEnabled(len(selApps) > 0) |
329 ).setEnabled(len(selApps) > 0) |
324 act = menu.addAction( |
330 act = menu.addAction( |
325 self.tr("Make Empty Migrations"), |
331 self.tr("Make Empty Migrations"), |
326 lambda: self.__makeMigrations(empty=True)) |
332 lambda: self.__makeMigrations(empty=True), |
|
333 ) |
327 act.setEnabled(len(selApps) > 0) |
334 act.setEnabled(len(selApps) > 0) |
328 act = menu.addAction( |
335 act = menu.addAction( |
329 self.tr("Make Migrations (dry-run)"), |
336 self.tr("Make Migrations (dry-run)"), |
330 lambda: self.__makeMigrations(dryRun=True)) |
337 lambda: self.__makeMigrations(dryRun=True), |
|
338 ) |
331 act.setEnabled(len(selApps) > 0) |
339 act.setEnabled(len(selApps) > 0) |
332 else: |
340 else: |
333 menu.addAction(self.tr("Apply Selected Migrations"), |
341 menu.addAction(self.tr("Apply Selected Migrations"), self.__applyMigration) |
334 self.__applyMigration) |
342 |
335 |
|
336 menu.popup(self.migrationsList.mapToGlobal(pos)) |
343 menu.popup(self.migrationsList.mapToGlobal(pos)) |
337 |
344 |
338 def __applyAllMigrations(self): |
345 def __applyAllMigrations(self): |
339 """ |
346 """ |
340 Private slot to apply all migrations. |
347 Private slot to apply all migrations. |
341 """ |
348 """ |
342 self.__django.applyMigrations() |
349 self.__django.applyMigrations() |
343 self.on_refreshButton_clicked() |
350 self.on_refreshButton_clicked() |
344 |
351 |
345 def __applyMigration(self): |
352 def __applyMigration(self): |
346 """ |
353 """ |
347 Private slot to apply the selected migrations. |
354 Private slot to apply the selected migrations. |
348 """ |
355 """ |
349 itm = self.migrationsList.selectedItems()[0] |
356 itm = self.migrationsList.selectedItems()[0] |
355 else: |
362 else: |
356 migration = itm.text(0) |
363 migration = itm.text(0) |
357 app = itm.parent().text(0) |
364 app = itm.parent().text(0) |
358 else: |
365 else: |
359 app, migration = itm.text(0).split(".", 1) |
366 app, migration = itm.text(0).split(".", 1) |
360 |
367 |
361 self.__django.applyMigrations(app=app, migration=migration) |
368 self.__django.applyMigrations(app=app, migration=migration) |
362 |
369 |
363 self.on_refreshButton_clicked() |
370 self.on_refreshButton_clicked() |
364 |
371 |
365 def __unapplyMigration(self): |
372 def __unapplyMigration(self): |
366 """ |
373 """ |
367 Private slot to unapply the selected migrations. |
374 Private slot to unapply the selected migrations. |
368 """ |
375 """ |
369 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
376 if self.__mode == DjangoMigrationsListDialog.MigrationsListMode: |
370 itm = self.migrationsList.selectedItems()[0] |
377 itm = self.migrationsList.selectedItems()[0] |
371 if itm.parent() is None: |
378 if itm.parent() is None: |
372 # only valid for app entries |
379 # only valid for app entries |
373 app = itm.text(0) |
380 app = itm.text(0) |
374 self.__django.applyMigrations(app=app, migration="zero") |
381 self.__django.applyMigrations(app=app, migration="zero") |
375 |
382 |
376 self.on_refreshButton_clicked() |
383 self.on_refreshButton_clicked() |
377 |
384 |
378 def __makeMigrations(self, dryRun=False, empty=False): |
385 def __makeMigrations(self, dryRun=False, empty=False): |
379 """ |
386 """ |
380 Private slot to make migrations for the selected apps. |
387 Private slot to make migrations for the selected apps. |
381 |
388 |
382 @param dryRun flag indicating a dry-run |
389 @param dryRun flag indicating a dry-run |
383 @type bool |
390 @type bool |
384 @param empty flag indicating an empty migration |
391 @param empty flag indicating an empty migration |
385 @type bool |
392 @type bool |
386 """ |
393 """ |
387 apps = [] |
394 apps = [] |
388 for itm in self.migrationsList.selectedItems(): |
395 for itm in self.migrationsList.selectedItems(): |
389 if itm.parent() is None: |
396 if itm.parent() is None: |
390 apps.append(itm.text(0)) |
397 apps.append(itm.text(0)) |
391 |
398 |
392 if apps: |
399 if apps: |
393 migration, ok = QInputDialog.getText( |
400 migration, ok = QInputDialog.getText( |
394 self, |
401 self, |
395 self.tr("Make Migrations"), |
402 self.tr("Make Migrations"), |
396 self.tr("Enter a name for the migrations (leave empty to" |
403 self.tr( |
397 " use system supplied name):"), |
404 "Enter a name for the migrations (leave empty to" |
398 QLineEdit.EchoMode.Normal) |
405 " use system supplied name):" |
399 |
406 ), |
|
407 QLineEdit.EchoMode.Normal, |
|
408 ) |
|
409 |
400 if ok: |
410 if ok: |
401 self.__django.makeMigrations(apps, migration, dryRun, empty, |
411 self.__django.makeMigrations(apps, migration, dryRun, empty, True) |
402 True) |
412 |
403 |
|
404 self.on_refreshButton_clicked() |
413 self.on_refreshButton_clicked() |