Merge lp:~phill-ridout/openlp/pathlib6 into lp:openlp

Proposed by Phill
Status: Merged
Merged at revision: 2769
Proposed branch: lp:~phill-ridout/openlp/pathlib6
Merge into: lp:openlp
Diff against target: 1335 lines (+392/-213)
20 files modified
openlp/core/common/httputils.py (+0/-1)
openlp/core/lib/db.py (+68/-15)
openlp/core/ui/advancedtab.py (+14/-11)
openlp/core/ui/exceptionform.py (+2/-12)
openlp/core/ui/lib/filedialog.py (+8/-8)
openlp/core/ui/mainwindow.py (+4/-10)
openlp/core/ui/servicemanager.py (+5/-3)
openlp/plugins/alerts/forms/alertform.py (+1/-1)
openlp/plugins/alerts/lib/alertstab.py (+0/-3)
openlp/plugins/custom/lib/customtab.py (+0/-3)
openlp/plugins/images/imageplugin.py (+2/-2)
openlp/plugins/images/lib/db.py (+3/-4)
openlp/plugins/images/lib/imagetab.py (+0/-3)
openlp/plugins/images/lib/mediaitem.py (+67/-65)
openlp/plugins/images/lib/upgrade.py (+70/-0)
openlp/plugins/songusage/forms/songusagedetaildialog.py (+0/-1)
openlp/plugins/songusage/forms/songusagedetailform.py (+17/-22)
tests/functional/openlp_core_ui/test_exceptionform.py (+28/-29)
tests/functional/openlp_plugins/images/test_lib.py (+20/-20)
tests/functional/openlp_plugins/images/test_upgrade.py (+83/-0)
To merge this branch: bzr merge lp:~phill-ridout/openlp/pathlib6
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Tim Bentley Approve
Phill Pending
Review via email: mp+331276@code.launchpad.net

This proposal supersedes a proposal from 2017-09-24.

Description of the change

This is ready to go now!

Upgrade the image plugin to use pathlib.
Other minor changes and fixes.

Add this to your merge proposal:
--------------------------------
lp:~phill-ridout/openlp/pathlib6 (revision 2774)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2207/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2110/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1997/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1364/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1196/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/326/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/165/
Stopping after failure

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Why have you removed a couple of __init__ methods?

review: Needs Information
Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

those __init__ methods just call the super method. They don't do anything I
fail to see any reason for keeping them!

On Sun, 24 Sep 2017, 22:01 Tim Bentley <email address hidden> wrote:

> Review: Needs Information
>
> Why have you removed a couple of __init__ methods?
> --
> https://code.launchpad.net/~phill-ridout/openlp/pathlib6/+merge/331252
> You are the owner of lp:~phill-ridout/openlp/pathlib6.
>

Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

Just noticed one minor mistake.

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) :
review: Approve
Revision history for this message
Tomas Groth (tomasgroth) wrote :

Just a minor thing, see inline comment.

review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/common/httputils.py'
2--- openlp/core/common/httputils.py 2017-09-06 20:18:08 +0000
3+++ openlp/core/common/httputils.py 2017-09-25 17:10:35 +0000
4@@ -24,7 +24,6 @@
5 """
6 import hashlib
7 import logging
8-import os
9 import platform
10 import socket
11 import sys
12
13=== modified file 'openlp/core/lib/db.py'
14--- openlp/core/lib/db.py 2017-08-12 17:45:56 +0000
15+++ openlp/core/lib/db.py 2017-09-25 17:10:35 +0000
16@@ -23,12 +23,13 @@
17 """
18 The :mod:`db` module provides the core database functionality for OpenLP
19 """
20+import json
21 import logging
22 import os
23 from copy import copy
24 from urllib.parse import quote_plus as urlquote
25
26-from sqlalchemy import Table, MetaData, Column, types, create_engine
27+from sqlalchemy import Table, MetaData, Column, types, create_engine, UnicodeText
28 from sqlalchemy.engine.url import make_url
29 from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError
30 from sqlalchemy.orm import scoped_session, sessionmaker, mapper
31@@ -37,7 +38,8 @@
32 from alembic.migration import MigrationContext
33 from alembic.operations import Operations
34
35-from openlp.core.common import AppLocation, Settings, translate, delete_file
36+from openlp.core.common import AppLocation, Settings, delete_file, translate
37+from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
38 from openlp.core.lib.ui import critical_error_message_box
39
40 log = logging.getLogger(__name__)
41@@ -133,9 +135,10 @@
42 if db_file_name is None:
43 return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
44 plugin=plugin_name)
45+ elif os.path.isabs(db_file_name):
46+ return 'sqlite:///{db_file_name}'.format(db_file_name=db_file_name)
47 else:
48- return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name),
49- name=db_file_name)
50+ return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name)
51
52
53 def handle_db_error(plugin_name, db_file_name):
54@@ -200,6 +203,55 @@
55 return instance
56
57
58+class PathType(types.TypeDecorator):
59+ """
60+ Create a PathType for storing Path objects with SQLAlchemy. Behind the scenes we convert the Path object to a JSON
61+ representation and store it as a Unicode type
62+ """
63+ impl = types.UnicodeText
64+
65+ def coerce_compared_value(self, op, value):
66+ """
67+ Some times it make sense to compare a PathType with a string. In the case a string is used coerce the the
68+ PathType to a UnicodeText type.
69+
70+ :param op: The operation being carried out. Not used, as we only care about the type that is being used with the
71+ operation.
72+ :param openlp.core.common.path.Path | str value: The value being used for the comparison. Most likely a Path
73+ Object or str.
74+ :return: The coerced value stored in the db
75+ :rtype: PathType or UnicodeText
76+ """
77+ if isinstance(value, str):
78+ return UnicodeText()
79+ else:
80+ return self
81+
82+ def process_bind_param(self, value, dialect):
83+ """
84+ Convert the Path object to a JSON representation
85+
86+ :param openlp.core.common.path.Path value: The value to convert
87+ :param dialect: Not used
88+ :return: The Path object as a JSON string
89+ :rtype: str
90+ """
91+ data_path = AppLocation.get_data_path()
92+ return json.dumps(value, cls=OpenLPJsonEncoder, base_path=data_path)
93+
94+ def process_result_value(self, value, dialect):
95+ """
96+ Convert the JSON representation back
97+
98+ :param types.UnicodeText value: The value to convert
99+ :param dialect: Not used
100+ :return: The JSON object converted Python object (in this case it should be a Path object)
101+ :rtype: openlp.core.common.path.Path
102+ """
103+ data_path = AppLocation.get_data_path()
104+ return json.loads(value, cls=OpenLPJsonDecoder, base_path=data_path)
105+
106+
107 def upgrade_db(url, upgrade):
108 """
109 Upgrade a database.
110@@ -208,7 +260,7 @@
111 :param upgrade: The python module that contains the upgrade instructions.
112 """
113 if not database_exists(url):
114- log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
115+ log.warning("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
116 return (0, 0)
117
118 log.debug('Checking upgrades for DB {db}'.format(db=url))
119@@ -273,10 +325,11 @@
120 :param plugin_name: The name of the plugin to remove the database for
121 :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
122 """
123+ db_file_path = AppLocation.get_section_data_path(plugin_name)
124 if db_file_name:
125- db_file_path = AppLocation.get_section_data_path(plugin_name) / db_file_name
126+ db_file_path = db_file_path / db_file_name
127 else:
128- db_file_path = AppLocation.get_section_data_path(plugin_name) / plugin_name
129+ db_file_path = db_file_path / plugin_name
130 return delete_file(db_file_path)
131
132
133@@ -284,30 +337,30 @@
134 """
135 Provide generic object persistence management
136 """
137- def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_mod=None, session=None):
138+ def __init__(self, plugin_name, init_schema, db_file_path=None, upgrade_mod=None, session=None):
139 """
140 Runs the initialisation process that includes creating the connection to the database and the tables if they do
141 not exist.
142
143 :param plugin_name: The name to setup paths and settings section names
144 :param init_schema: The init_schema function for this database
145- :param db_file_name: The upgrade_schema function for this database
146- :param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name
147- being used.
148+ :param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None
149+ resulting in the plugin_name being used.
150+ :param upgrade_mod: The upgrade_schema function for this database
151 """
152 self.is_dirty = False
153 self.session = None
154 self.db_url = None
155- if db_file_name:
156+ if db_file_path:
157 log.debug('Manager: Creating new DB url')
158- self.db_url = init_url(plugin_name, db_file_name)
159+ self.db_url = init_url(plugin_name, str(db_file_path))
160 else:
161 self.db_url = init_url(plugin_name)
162 if upgrade_mod:
163 try:
164 db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
165 except (SQLAlchemyError, DBAPIError):
166- handle_db_error(plugin_name, db_file_name)
167+ handle_db_error(plugin_name, str(db_file_path))
168 return
169 if db_ver > up_ver:
170 critical_error_message_box(
171@@ -322,7 +375,7 @@
172 try:
173 self.session = init_schema(self.db_url)
174 except (SQLAlchemyError, DBAPIError):
175- handle_db_error(plugin_name, db_file_name)
176+ handle_db_error(plugin_name, str(db_file_path))
177 else:
178 self.session = session
179
180
181=== modified file 'openlp/core/ui/advancedtab.py'
182--- openlp/core/ui/advancedtab.py 2017-08-23 20:21:11 +0000
183+++ openlp/core/ui/advancedtab.py 2017-09-25 17:10:35 +0000
184@@ -22,9 +22,8 @@
185 """
186 The :mod:`advancedtab` provides an advanced settings facility.
187 """
188+import logging
189 from datetime import datetime, timedelta
190-import logging
191-import os
192
193 from PyQt5 import QtCore, QtGui, QtWidgets
194
195@@ -492,24 +491,27 @@
196 self.service_name_edit.setText(UiStrings().DefaultServiceName)
197 self.service_name_edit.setFocus()
198
199- def on_data_directory_path_edit_path_changed(self, new_data_path):
200+ def on_data_directory_path_edit_path_changed(self, new_path):
201 """
202- Browse for a new data directory location.
203+ Handle the `editPathChanged` signal of the data_directory_path_edit
204+
205+ :param openlp.core.common.path.Path new_path: The new path
206+ :rtype: None
207 """
208 # Make sure they want to change the data.
209 answer = QtWidgets.QMessageBox.question(self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),
210 translate('OpenLP.AdvancedTab', 'Are you sure you want to change the '
211 'location of the OpenLP data directory to:\n\n{path}'
212 '\n\nThe data directory will be changed when OpenLP is '
213- 'closed.').format(path=new_data_path),
214+ 'closed.').format(path=new_path),
215 defaultButton=QtWidgets.QMessageBox.No)
216 if answer != QtWidgets.QMessageBox.Yes:
217 self.data_directory_path_edit.path = AppLocation.get_data_path()
218 return
219 # Check if data already exists here.
220- self.check_data_overwrite(path_to_str(new_data_path))
221+ self.check_data_overwrite(new_path)
222 # Save the new location.
223- self.main_window.set_new_data_path(path_to_str(new_data_path))
224+ self.main_window.new_data_path = new_path
225 self.data_directory_cancel_button.show()
226
227 def on_data_directory_copy_check_box_toggled(self):
228@@ -526,9 +528,10 @@
229 def check_data_overwrite(self, data_path):
230 """
231 Check if there's already data in the target directory.
232+
233+ :param openlp.core.common.path.Path data_path: The target directory to check
234 """
235- test_path = os.path.join(data_path, 'songs')
236- if os.path.exists(test_path):
237+ if (data_path / 'songs').exists():
238 self.data_exists = True
239 # Check is they want to replace existing data.
240 answer = QtWidgets.QMessageBox.warning(self,
241@@ -537,7 +540,7 @@
242 'WARNING: \n\nThe location you have selected \n\n{path}'
243 '\n\nappears to contain OpenLP data files. Do you wish to '
244 'replace these files with the current data '
245- 'files?').format(path=os.path.abspath(data_path,)),
246+ 'files?'.format(path=data_path)),
247 QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
248 QtWidgets.QMessageBox.No),
249 QtWidgets.QMessageBox.No)
250@@ -559,7 +562,7 @@
251 """
252 self.data_directory_path_edit.path = AppLocation.get_data_path()
253 self.data_directory_copy_check_box.setChecked(False)
254- self.main_window.set_new_data_path(None)
255+ self.main_window.new_data_path = None
256 self.main_window.set_copy_data(False)
257 self.data_directory_copy_check_box.hide()
258 self.data_directory_cancel_button.hide()
259
260=== modified file 'openlp/core/ui/exceptionform.py'
261--- openlp/core/ui/exceptionform.py 2017-08-26 15:06:11 +0000
262+++ openlp/core/ui/exceptionform.py 2017-09-25 17:10:35 +0000
263@@ -149,21 +149,11 @@
264 opts = self._create_report()
265 report_text = self.report_text.format(version=opts['version'], description=opts['description'],
266 traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])
267- filename = str(file_path)
268 try:
269- report_file = open(filename, 'w')
270- try:
271+ with file_path.open('w') as report_file:
272 report_file.write(report_text)
273- except UnicodeError:
274- report_file.close()
275- report_file = open(filename, 'wb')
276- report_file.write(report_text.encode('utf-8'))
277- finally:
278- report_file.close()
279 except IOError:
280 log.exception('Failed to write crash report')
281- finally:
282- report_file.close()
283
284 def on_send_report_button_clicked(self):
285 """
286@@ -219,7 +209,7 @@
287 translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),
288 Settings().value(self.settings_section + '/last directory'),
289 '{text} (*)'.format(text=UiStrings().AllFiles))
290- log.info('New file {file}'.format(file=file_path))
291+ log.info('New files {file_path}'.format(file_path=file_path))
292 if file_path:
293 self.file_attachment = str(file_path)
294
295
296=== modified file 'openlp/core/ui/lib/filedialog.py'
297--- openlp/core/ui/lib/filedialog.py 2017-09-20 20:44:57 +0000
298+++ openlp/core/ui/lib/filedialog.py 2017-09-25 17:10:35 +0000
299@@ -31,11 +31,11 @@
300 """
301 Wraps `getExistingDirectory` so that it can be called with, and return Path objects
302
303- :type parent: QtWidgets.QWidget or None
304+ :type parent: QtWidgets.QWidget | None
305 :type caption: str
306 :type directory: openlp.core.common.path.Path
307 :type options: QtWidgets.QFileDialog.Options
308- :rtype: tuple[Path, str]
309+ :rtype: tuple[openlp.core.common.path.Path, str]
310 """
311 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
312
313@@ -50,13 +50,13 @@
314 """
315 Wraps `getOpenFileName` so that it can be called with, and return Path objects
316
317- :type parent: QtWidgets.QWidget or None
318+ :type parent: QtWidgets.QWidget | None
319 :type caption: str
320 :type directory: openlp.core.common.path.Path
321 :type filter: str
322 :type initialFilter: str
323 :type options: QtWidgets.QFileDialog.Options
324- :rtype: tuple[Path, str]
325+ :rtype: tuple[openlp.core.common.path.Path, str]
326 """
327 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
328
329@@ -71,13 +71,13 @@
330 """
331 Wraps `getOpenFileNames` so that it can be called with, and return Path objects
332
333- :type parent: QtWidgets.QWidget or None
334+ :type parent: QtWidgets.QWidget | None
335 :type caption: str
336 :type directory: openlp.core.common.path.Path
337 :type filter: str
338 :type initialFilter: str
339 :type options: QtWidgets.QFileDialog.Options
340- :rtype: tuple[list[Path], str]
341+ :rtype: tuple[list[openlp.core.common.path.Path], str]
342 """
343 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
344
345@@ -93,13 +93,13 @@
346 """
347 Wraps `getSaveFileName` so that it can be called with, and return Path objects
348
349- :type parent: QtWidgets.QWidget or None
350+ :type parent: QtWidgets.QWidget | None
351 :type caption: str
352 :type directory: openlp.core.common.path.Path
353 :type filter: str
354 :type initialFilter: str
355 :type options: QtWidgets.QFileDialog.Options
356- :rtype: tuple[Path or None, str]
357+ :rtype: tuple[openlp.core.common.path.Path | None, str]
358 """
359 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
360
361
362=== modified file 'openlp/core/ui/mainwindow.py'
363--- openlp/core/ui/mainwindow.py 2017-09-20 20:44:57 +0000
364+++ openlp/core/ui/mainwindow.py 2017-09-25 17:10:35 +0000
365@@ -1332,12 +1332,6 @@
366 if self.application:
367 self.application.process_events()
368
369- def set_new_data_path(self, new_data_path):
370- """
371- Set the new data path
372- """
373- self.new_data_path = new_data_path
374-
375 def set_copy_data(self, copy_data):
376 """
377 Set the flag to copy the data
378@@ -1349,7 +1343,7 @@
379 Change the data directory.
380 """
381 log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))
382- old_data_path = str(AppLocation.get_data_path())
383+ old_data_path = AppLocation.get_data_path()
384 # Copy OpenLP data to new location if requested.
385 self.application.set_busy_cursor()
386 if self.copy_data:
387@@ -1358,7 +1352,7 @@
388 self.show_status_message(
389 translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
390 '- Please wait for copy to finish').format(path=self.new_data_path))
391- dir_util.copy_tree(old_data_path, self.new_data_path)
392+ dir_util.copy_tree(str(old_data_path), str(self.new_data_path))
393 log.info('Copy successful')
394 except (IOError, os.error, DistutilsFileError) as why:
395 self.application.set_normal_cursor()
396@@ -1373,9 +1367,9 @@
397 log.info('No data copy requested')
398 # Change the location of data directory in config file.
399 settings = QtCore.QSettings()
400- settings.setValue('advanced/data path', Path(self.new_data_path))
401+ settings.setValue('advanced/data path', self.new_data_path)
402 # Check if the new data path is our default.
403- if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):
404+ if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
405 settings.remove('advanced/data path')
406 self.application.set_normal_cursor()
407
408
409=== modified file 'openlp/core/ui/servicemanager.py'
410--- openlp/core/ui/servicemanager.py 2017-09-18 06:20:06 +0000
411+++ openlp/core/ui/servicemanager.py 2017-09-25 17:10:35 +0000
412@@ -376,7 +376,7 @@
413 self._file_name = path_to_str(file_path)
414 self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
415 Settings().setValue('servicemanager/last file', file_path)
416- if file_path and file_path.suffix() == '.oszl':
417+ if file_path and file_path.suffix == '.oszl':
418 self._save_lite = True
419 else:
420 self._save_lite = False
421@@ -699,13 +699,15 @@
422 default_file_name = format_time(default_pattern, local_time)
423 else:
424 default_file_name = ''
425+ default_file_path = Path(default_file_name)
426 directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory')
427- file_path = directory_path / default_file_name
428+ if directory_path:
429+ default_file_path = directory_path / default_file_path
430 # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
431 # the long term.
432 if self._file_name.endswith('oszl') or self.service_has_all_original_files:
433 file_path, filter_used = FileDialog.getSaveFileName(
434- self.main_window, UiStrings().SaveService, file_path,
435+ self.main_window, UiStrings().SaveService, default_file_path,
436 translate('OpenLP.ServiceManager',
437 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
438 else:
439
440=== modified file 'openlp/plugins/alerts/forms/alertform.py'
441--- openlp/plugins/alerts/forms/alertform.py 2017-06-09 06:06:49 +0000
442+++ openlp/plugins/alerts/forms/alertform.py 2017-09-25 17:10:35 +0000
443@@ -70,7 +70,7 @@
444 item_name = QtWidgets.QListWidgetItem(alert.text)
445 item_name.setData(QtCore.Qt.UserRole, alert.id)
446 self.alert_list_widget.addItem(item_name)
447- if alert.text == str(self.alert_text_edit.text()):
448+ if alert.text == self.alert_text_edit.text():
449 self.item_id = alert.id
450 self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name))
451
452
453=== modified file 'openlp/plugins/alerts/lib/alertstab.py'
454--- openlp/plugins/alerts/lib/alertstab.py 2017-08-03 17:54:40 +0000
455+++ openlp/plugins/alerts/lib/alertstab.py 2017-09-25 17:10:35 +0000
456@@ -32,9 +32,6 @@
457 """
458 AlertsTab is the alerts settings tab in the settings dialog.
459 """
460- def __init__(self, parent, name, visible_title, icon_path):
461- super(AlertsTab, self).__init__(parent, name, visible_title, icon_path)
462-
463 def setupUi(self):
464 self.setObjectName('AlertsTab')
465 super(AlertsTab, self).setupUi()
466
467=== modified file 'openlp/plugins/custom/lib/customtab.py'
468--- openlp/plugins/custom/lib/customtab.py 2016-12-31 11:01:36 +0000
469+++ openlp/plugins/custom/lib/customtab.py 2017-09-25 17:10:35 +0000
470@@ -34,9 +34,6 @@
471 """
472 CustomTab is the Custom settings tab in the settings dialog.
473 """
474- def __init__(self, parent, title, visible_title, icon_path):
475- super(CustomTab, self).__init__(parent, title, visible_title, icon_path)
476-
477 def setupUi(self):
478 self.setObjectName('CustomTab')
479 super(CustomTab, self).setupUi()
480
481=== modified file 'openlp/plugins/images/imageplugin.py'
482--- openlp/plugins/images/imageplugin.py 2017-09-09 20:00:48 +0000
483+++ openlp/plugins/images/imageplugin.py 2017-09-25 17:10:35 +0000
484@@ -29,7 +29,7 @@
485 from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
486 from openlp.core.lib.db import Manager
487 from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
488-from openlp.plugins.images.lib import ImageMediaItem, ImageTab
489+from openlp.plugins.images.lib import ImageMediaItem, ImageTab, upgrade
490 from openlp.plugins.images.lib.db import init_schema
491
492 log = logging.getLogger(__name__)
493@@ -50,7 +50,7 @@
494
495 def __init__(self):
496 super(ImagePlugin, self).__init__('images', __default_settings__, ImageMediaItem, ImageTab)
497- self.manager = Manager('images', init_schema)
498+ self.manager = Manager('images', init_schema, upgrade_mod=upgrade)
499 self.weight = -7
500 self.icon_path = ':/plugins/plugin_images.png'
501 self.icon = build_icon(self.icon_path)
502
503=== modified file 'openlp/plugins/images/lib/db.py'
504--- openlp/plugins/images/lib/db.py 2016-12-31 11:01:36 +0000
505+++ openlp/plugins/images/lib/db.py 2017-09-25 17:10:35 +0000
506@@ -22,11 +22,10 @@
507 """
508 The :mod:`db` module provides the database and schema that is the backend for the Images plugin.
509 """
510-
511 from sqlalchemy import Column, ForeignKey, Table, types
512 from sqlalchemy.orm import mapper
513
514-from openlp.core.lib.db import BaseModel, init_db
515+from openlp.core.lib.db import BaseModel, PathType, init_db
516
517
518 class ImageGroups(BaseModel):
519@@ -65,7 +64,7 @@
520
521 * id
522 * group_id
523- * filename
524+ * file_path
525 """
526 session, metadata = init_db(url)
527
528@@ -80,7 +79,7 @@
529 image_filenames_table = Table('image_filenames', metadata,
530 Column('id', types.Integer(), primary_key=True),
531 Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
532- Column('filename', types.Unicode(255), nullable=False)
533+ Column('file_path', PathType(), nullable=False)
534 )
535
536 mapper(ImageGroups, image_groups_table)
537
538=== modified file 'openlp/plugins/images/lib/imagetab.py'
539--- openlp/plugins/images/lib/imagetab.py 2016-12-31 11:01:36 +0000
540+++ openlp/plugins/images/lib/imagetab.py 2017-09-25 17:10:35 +0000
541@@ -31,9 +31,6 @@
542 """
543 ImageTab is the images settings tab in the settings dialog.
544 """
545- def __init__(self, parent, name, visible_title, icon_path):
546- super(ImageTab, self).__init__(parent, name, visible_title, icon_path)
547-
548 def setupUi(self):
549 self.setObjectName('ImagesTab')
550 super(ImageTab, self).setupUi()
551
552=== modified file 'openlp/plugins/images/lib/mediaitem.py'
553--- openlp/plugins/images/lib/mediaitem.py 2017-09-17 19:43:15 +0000
554+++ openlp/plugins/images/lib/mediaitem.py 2017-09-25 17:10:35 +0000
555@@ -21,7 +21,6 @@
556 ###############################################################################
557
558 import logging
559-import os
560
561 from PyQt5 import QtCore, QtGui, QtWidgets
562
563@@ -99,11 +98,11 @@
564 self.list_view.setIconSize(QtCore.QSize(88, 50))
565 self.list_view.setIndentation(self.list_view.default_indentation)
566 self.list_view.allow_internal_dnd = True
567- self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
568- check_directory_exists(Path(self.service_path))
569+ self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
570+ check_directory_exists(self.service_path)
571 # Load images from the database
572 self.load_full_list(
573- self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)
574+ self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path), initial_load=True)
575
576 def add_list_view_to_toolbar(self):
577 """
578@@ -211,8 +210,8 @@
579 """
580 images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
581 for image in images:
582- delete_file(Path(self.service_path, os.path.split(image.filename)[1]))
583- delete_file(Path(self.generate_thumbnail_path(image)))
584+ delete_file(self.service_path / image.file_path.name)
585+ delete_file(self.generate_thumbnail_path(image))
586 self.manager.delete_object(ImageFilenames, image.id)
587 image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
588 for group in image_groups:
589@@ -234,8 +233,8 @@
590 if row_item:
591 item_data = row_item.data(0, QtCore.Qt.UserRole)
592 if isinstance(item_data, ImageFilenames):
593- delete_file(Path(self.service_path, row_item.text(0)))
594- delete_file(Path(self.generate_thumbnail_path(item_data)))
595+ delete_file(self.service_path / row_item.text(0))
596+ delete_file(self.generate_thumbnail_path(item_data))
597 if item_data.group_id == 0:
598 self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
599 else:
600@@ -326,17 +325,19 @@
601 """
602 Generate a path to the thumbnail
603
604- :param image: An instance of ImageFileNames
605- :return: A path to the thumbnail of type str
606+ :param openlp.plugins.images.lib.db.ImageFilenames image: The image to generate the thumbnail path for.
607+ :return: A path to the thumbnail
608+ :rtype: openlp.core.common.path.Path
609 """
610- ext = os.path.splitext(image.filename)[1].lower()
611- return os.path.join(self.service_path, '{}{}'.format(str(image.id), ext))
612+ ext = image.file_path.suffix.lower()
613+ return self.service_path / '{name:d}{ext}'.format(name=image.id, ext=ext)
614
615 def load_full_list(self, images, initial_load=False, open_group=None):
616 """
617 Replace the list of images and groups in the interface.
618
619- :param images: A List of Image Filenames objects that will be used to reload the mediamanager list.
620+ :param list[openlp.plugins.images.lib.db.ImageFilenames] images: A List of Image Filenames objects that will be
621+ used to reload the mediamanager list.
622 :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
623 :param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
624 interface.
625@@ -352,34 +353,34 @@
626 self.expand_group(open_group.id)
627 # Sort the images by its filename considering language specific.
628 # characters.
629- images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1]))
630- for image_file in images:
631- log.debug('Loading image: {name}'.format(name=image_file.filename))
632- filename = os.path.split(image_file.filename)[1]
633- thumb = self.generate_thumbnail_path(image_file)
634- if not os.path.exists(image_file.filename):
635+ images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name))
636+ for image in images:
637+ log.debug('Loading image: {name}'.format(name=image.file_path))
638+ file_name = image.file_path.name
639+ thumbnail_path = self.generate_thumbnail_path(image)
640+ if not image.file_path.exists():
641 icon = build_icon(':/general/general_delete.png')
642 else:
643- if validate_thumb(Path(image_file.filename), Path(thumb)):
644- icon = build_icon(thumb)
645+ if validate_thumb(image.file_path, thumbnail_path):
646+ icon = build_icon(thumbnail_path)
647 else:
648- icon = create_thumb(image_file.filename, thumb)
649- item_name = QtWidgets.QTreeWidgetItem([filename])
650- item_name.setText(0, filename)
651+ icon = create_thumb(image.file_path, thumbnail_path)
652+ item_name = QtWidgets.QTreeWidgetItem([file_name])
653+ item_name.setText(0, file_name)
654 item_name.setIcon(0, icon)
655- item_name.setToolTip(0, image_file.filename)
656- item_name.setData(0, QtCore.Qt.UserRole, image_file)
657- if image_file.group_id == 0:
658+ item_name.setToolTip(0, str(image.file_path))
659+ item_name.setData(0, QtCore.Qt.UserRole, image)
660+ if image.group_id == 0:
661 self.list_view.addTopLevelItem(item_name)
662 else:
663- group_items[image_file.group_id].addChild(item_name)
664+ group_items[image.group_id].addChild(item_name)
665 if not initial_load:
666 self.main_window.increment_progress_bar()
667 if not initial_load:
668 self.main_window.finished_progress_bar()
669 self.application.set_normal_cursor()
670
671- def validate_and_load(self, files, target_group=None):
672+ def validate_and_load(self, file_paths, target_group=None):
673 """
674 Process a list for files either from the File Dialog or from Drag and Drop.
675 This method is overloaded from MediaManagerItem.
676@@ -388,15 +389,15 @@
677 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
678 """
679 self.application.set_normal_cursor()
680- self.load_list(files, target_group)
681- last_dir = os.path.split(files[0])[0]
682- Settings().setValue(self.settings_section + '/last directory', Path(last_dir))
683+ self.load_list(file_paths, target_group)
684+ last_dir = file_paths[0].parent
685+ Settings().setValue(self.settings_section + '/last directory', last_dir)
686
687- def load_list(self, images, target_group=None, initial_load=False):
688+ def load_list(self, image_paths, target_group=None, initial_load=False):
689 """
690 Add new images to the database. This method is called when adding images using the Add button or DnD.
691
692- :param images: A List of strings containing the filenames of the files to be loaded
693+ :param list[openlp.core.common.Path] image_paths: A list of file paths to the images to be loaded
694 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
695 :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
696 """
697@@ -429,7 +430,7 @@
698 else:
699 self.choose_group_form.existing_radio_button.setDisabled(False)
700 self.choose_group_form.group_combobox.setDisabled(False)
701- # Ask which group the images should be saved in
702+ # Ask which group the image_paths should be saved in
703 if self.choose_group_form.exec(selected_group=preselect_group):
704 if self.choose_group_form.nogroup_radio_button.isChecked():
705 # User chose 'No group'
706@@ -461,33 +462,33 @@
707 return
708 # Initialize busy cursor and progress bar
709 self.application.set_busy_cursor()
710- self.main_window.display_progress_bar(len(images))
711- # Save the new images in the database
712- self.save_new_images_list(images, group_id=parent_group.id, reload_list=False)
713- self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),
714+ self.main_window.display_progress_bar(len(image_paths))
715+ # Save the new image_paths in the database
716+ self.save_new_images_list(image_paths, group_id=parent_group.id, reload_list=False)
717+ self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path),
718 initial_load=initial_load, open_group=parent_group)
719 self.application.set_normal_cursor()
720
721- def save_new_images_list(self, images_list, group_id=0, reload_list=True):
722+ def save_new_images_list(self, image_paths, group_id=0, reload_list=True):
723 """
724 Convert a list of image filenames to ImageFilenames objects and save them in the database.
725
726- :param images_list: A List of strings containing image filenames
727+ :param list[Path] image_paths: A List of file paths to image
728 :param group_id: The ID of the group to save the images in
729 :param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
730 the new images
731 """
732- for filename in images_list:
733- if not isinstance(filename, str):
734+ for image_path in image_paths:
735+ if not isinstance(image_path, Path):
736 continue
737- log.debug('Adding new image: {name}'.format(name=filename))
738+ log.debug('Adding new image: {name}'.format(name=image_path))
739 image_file = ImageFilenames()
740 image_file.group_id = group_id
741- image_file.filename = str(filename)
742+ image_file.file_path = image_path
743 self.manager.save_object(image_file)
744 self.main_window.increment_progress_bar()
745- if reload_list and images_list:
746- self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename))
747+ if reload_list and image_paths:
748+ self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path))
749
750 def dnd_move_internal(self, target):
751 """
752@@ -581,8 +582,8 @@
753 return False
754 # Find missing files
755 for image in images:
756- if not os.path.exists(image.filename):
757- missing_items_file_names.append(image.filename)
758+ if not image.file_path.exists():
759+ missing_items_file_names.append(str(image.file_path))
760 # We cannot continue, as all images do not exist.
761 if not images:
762 if not remote:
763@@ -601,9 +602,9 @@
764 return False
765 # Continue with the existing images.
766 for image in images:
767- name = os.path.split(image.filename)[1]
768- thumbnail = self.generate_thumbnail_path(image)
769- service_item.add_from_image(image.filename, name, background, thumbnail)
770+ name = image.file_path.name
771+ thumbnail_path = self.generate_thumbnail_path(image)
772+ service_item.add_from_image(str(image.file_path), name, background, str(thumbnail_path))
773 return True
774
775 def check_group_exists(self, new_group):
776@@ -640,7 +641,7 @@
777 if not self.check_group_exists(new_group):
778 if self.manager.save_object(new_group):
779 self.load_full_list(self.manager.get_all_objects(
780- ImageFilenames, order_by_ref=ImageFilenames.filename))
781+ ImageFilenames, order_by_ref=ImageFilenames.file_path))
782 self.expand_group(new_group.id)
783 self.fill_groups_combobox(self.choose_group_form.group_combobox)
784 self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
785@@ -675,9 +676,9 @@
786 if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
787 # Only continue when an image is selected.
788 return
789- filename = bitem.data(0, QtCore.Qt.UserRole).filename
790- if os.path.exists(filename):
791- if self.live_controller.display.direct_image(filename, background):
792+ file_path = bitem.data(0, QtCore.Qt.UserRole).file_path
793+ if file_path.exists():
794+ if self.live_controller.display.direct_image(str(file_path), background):
795 self.reset_action.setVisible(True)
796 else:
797 critical_error_message_box(
798@@ -687,22 +688,22 @@
799 critical_error_message_box(
800 UiStrings().LiveBGError,
801 translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
802- 'the image file "{name}" no longer exists.').format(name=filename))
803+ 'the image file "{name}" no longer exists.').format(name=file_path))
804
805 def search(self, string, show_error=True):
806 """
807 Perform a search on the image file names.
808
809- :param string: The glob to search for
810- :param show_error: Unused.
811+ :param str string: The glob to search for
812+ :param bool show_error: Unused.
813 """
814 files = self.manager.get_all_objects(
815- ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
816- order_by_ref=ImageFilenames.filename)
817+ ImageFilenames, filter_clause=ImageFilenames.file_path.contains(string),
818+ order_by_ref=ImageFilenames.file_path)
819 results = []
820 for file_object in files:
821- filename = os.path.split(str(file_object.filename))[1]
822- results.append([file_object.filename, filename])
823+ file_name = file_object.file_path.name
824+ results.append([str(file_object.file_path), file_name])
825 return results
826
827 def create_item_from_id(self, item_id):
828@@ -711,8 +712,9 @@
829
830 :param item_id: Id to make live
831 """
832+ item_id = Path(item_id)
833 item = QtWidgets.QTreeWidgetItem()
834- item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.filename == item_id)
835- item.setText(0, os.path.basename(item_data.filename))
836+ item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.file_path == item_id)
837+ item.setText(0, item_data.file_path.name)
838 item.setData(0, QtCore.Qt.UserRole, item_data)
839 return item
840
841=== added file 'openlp/plugins/images/lib/upgrade.py'
842--- openlp/plugins/images/lib/upgrade.py 1970-01-01 00:00:00 +0000
843+++ openlp/plugins/images/lib/upgrade.py 2017-09-25 17:10:35 +0000
844@@ -0,0 +1,70 @@
845+# -*- coding: utf-8 -*-
846+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
847+
848+###############################################################################
849+# OpenLP - Open Source Lyrics Projection #
850+# --------------------------------------------------------------------------- #
851+# Copyright (c) 2008-2017 OpenLP Developers #
852+# --------------------------------------------------------------------------- #
853+# This program is free software; you can redistribute it and/or modify it #
854+# under the terms of the GNU General Public License as published by the Free #
855+# Software Foundation; version 2 of the License. #
856+# #
857+# This program is distributed in the hope that it will be useful, but WITHOUT #
858+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
859+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
860+# more details. #
861+# #
862+# You should have received a copy of the GNU General Public License along #
863+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
864+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
865+###############################################################################
866+"""
867+The :mod:`upgrade` module provides the migration path for the OLP Paths database
868+"""
869+import json
870+import logging
871+
872+from sqlalchemy import Column, Table
873+
874+from openlp.core.common import AppLocation
875+from openlp.core.common.db import drop_columns
876+from openlp.core.common.json import OpenLPJsonEncoder
877+from openlp.core.common.path import Path
878+from openlp.core.lib.db import PathType, get_upgrade_op
879+
880+log = logging.getLogger(__name__)
881+__version__ = 2
882+
883+
884+def upgrade_1(session, metadata):
885+ """
886+ Version 1 upgrade - old db might/might not be versioned.
887+ """
888+ log.debug('Skipping upgrade_1 of files DB - not used')
889+
890+
891+def upgrade_2(session, metadata):
892+ """
893+ Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev
894+ """
895+ # TODO: Update tests
896+ log.debug('Starting upgrade_2 for file_path to JSON')
897+ old_table = Table('image_filenames', metadata, autoload=True)
898+ if 'file_path' not in [col.name for col in old_table.c.values()]:
899+ op = get_upgrade_op(session)
900+ op.add_column('image_filenames', Column('file_path', PathType()))
901+ conn = op.get_bind()
902+ results = conn.execute('SELECT * FROM image_filenames')
903+ data_path = AppLocation.get_data_path()
904+ for row in results.fetchall():
905+ file_path_json = json.dumps(Path(row.filename), cls=OpenLPJsonEncoder, base_path=data_path)
906+ sql = 'UPDATE image_filenames SET file_path = \'{file_path_json}\' WHERE id = {id}'.format(
907+ file_path_json=file_path_json, id=row.id)
908+ conn.execute(sql)
909+ # Drop old columns
910+ if metadata.bind.url.get_dialect().name == 'sqlite':
911+ drop_columns(op, 'image_filenames', ['filename', ])
912+ else:
913+ op.drop_constraint('image_filenames', 'foreignkey')
914+ op.drop_column('image_filenames', 'filenames')
915
916=== modified file 'openlp/plugins/songusage/forms/songusagedetaildialog.py'
917--- openlp/plugins/songusage/forms/songusagedetaildialog.py 2017-06-09 06:06:49 +0000
918+++ openlp/plugins/songusage/forms/songusagedetaildialog.py 2017-09-25 17:10:35 +0000
919@@ -19,7 +19,6 @@
920 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
921 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
922 ###############################################################################
923-
924 from PyQt5 import QtCore, QtWidgets
925
926 from openlp.core.common import translate
927
928=== modified file 'openlp/plugins/songusage/forms/songusagedetailform.py'
929--- openlp/plugins/songusage/forms/songusagedetailform.py 2017-08-26 15:06:11 +0000
930+++ openlp/plugins/songusage/forms/songusagedetailform.py 2017-09-25 17:10:35 +0000
931@@ -19,7 +19,6 @@
932 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
933 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
934 ###############################################################################
935-
936 import logging
937 import os
938
939@@ -60,7 +59,7 @@
940
941 def on_report_path_edit_path_changed(self, file_path):
942 """
943- Called when the path in the `PathEdit` has changed
944+ Handle the `pathEditChanged` signal from report_path_edit
945
946 :param openlp.core.common.path.Path file_path: The new path.
947 :rtype: None
948@@ -72,7 +71,7 @@
949 Ok was triggered so lets save the data and run the report
950 """
951 log.debug('accept')
952- path = path_to_str(self.report_path_edit.path)
953+ path = self.report_path_edit.path
954 if not path:
955 self.main_window.error_message(
956 translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
957@@ -80,7 +79,7 @@
958 ' song usage report. \nPlease select an existing path on your computer.')
959 )
960 return
961- check_directory_exists(Path(path))
962+ check_directory_exists(path)
963 file_name = translate('SongUsagePlugin.SongUsageDetailForm',
964 'usage_detail_{old}_{new}.txt'
965 ).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'),
966@@ -91,29 +90,25 @@
967 SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(),
968 SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()),
969 [SongUsageItem.usagedate, SongUsageItem.usagetime])
970- report_file_name = os.path.join(path, file_name)
971- file_handle = None
972+ report_file_name = path / file_name
973 try:
974- file_handle = open(report_file_name, 'wb')
975- for instance in usage:
976- record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'
977- '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,
978- title=instance.title, copyright=instance.copyright,
979- ccli=instance.ccl_number, authors=instance.authors,
980- name=instance.plugin_name, source=instance.source)
981- file_handle.write(record.encode('utf-8'))
982- self.main_window.information_message(
983- translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
984- translate('SongUsagePlugin.SongUsageDetailForm',
985- 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
986- )
987+ with report_file_name.open('wb') as file_handle:
988+ for instance in usage:
989+ record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'
990+ '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,
991+ title=instance.title, copyright=instance.copyright,
992+ ccli=instance.ccl_number, authors=instance.authors,
993+ name=instance.plugin_name, source=instance.source)
994+ file_handle.write(record.encode('utf-8'))
995+ self.main_window.information_message(
996+ translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
997+ translate('SongUsagePlugin.SongUsageDetailForm',
998+ 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
999+ )
1000 except OSError as ose:
1001 log.exception('Failed to write out song usage records')
1002 critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
1003 translate('SongUsagePlugin.SongUsageDetailForm',
1004 'An error occurred while creating the report: {error}'
1005 ).format(error=ose.strerror))
1006- finally:
1007- if file_handle:
1008- file_handle.close()
1009 self.close()
1010
1011=== modified file 'tests/functional/openlp_core_ui/test_exceptionform.py'
1012--- tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-07 21:52:39 +0000
1013+++ tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-25 17:10:35 +0000
1014@@ -22,11 +22,11 @@
1015 """
1016 Package to test the openlp.core.ui.exeptionform package.
1017 """
1018-
1019 import os
1020 import tempfile
1021+
1022 from unittest import TestCase
1023-from unittest.mock import mock_open, patch
1024+from unittest.mock import call, patch
1025
1026 from openlp.core.common import Registry
1027 from openlp.core.common.path import Path
1028@@ -142,15 +142,15 @@
1029 test_form = exceptionform.ExceptionForm()
1030 test_form.file_attachment = None
1031
1032- with patch.object(test_form, '_pyuno_import') as mock_pyuno:
1033- with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback:
1034- with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
1035- mock_pyuno.return_value = 'UNO Bridge Test'
1036- mock_traceback.return_value = 'openlp: Traceback Test'
1037- mock_description.return_value = 'Description Test'
1038+ with patch.object(test_form, '_pyuno_import') as mock_pyuno, \
1039+ patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \
1040+ patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
1041+ mock_pyuno.return_value = 'UNO Bridge Test'
1042+ mock_traceback.return_value = 'openlp: Traceback Test'
1043+ mock_description.return_value = 'Description Test'
1044
1045- # WHEN: on_save_report_button_clicked called
1046- test_form.on_send_report_button_clicked()
1047+ # WHEN: on_save_report_button_clicked called
1048+ test_form.on_send_report_button_clicked()
1049
1050 # THEN: Verify strings were formatted properly
1051 mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT)
1052@@ -182,25 +182,24 @@
1053 mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'
1054 mocked_is_linux.return_value = False
1055 mocked_application_version.return_value = 'Trunk Test'
1056- mocked_save_filename.return_value = (Path('testfile.txt'), 'filter')
1057-
1058- test_form = exceptionform.ExceptionForm()
1059- test_form.file_attachment = None
1060-
1061- with patch.object(test_form, '_pyuno_import') as mock_pyuno:
1062- with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback:
1063- with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
1064- with patch("openlp.core.ui.exceptionform.open", mock_open(), create=True) as mocked_open:
1065- mock_pyuno.return_value = 'UNO Bridge Test'
1066- mock_traceback.return_value = 'openlp: Traceback Test'
1067- mock_description.return_value = 'Description Test'
1068-
1069- # WHEN: on_save_report_button_clicked called
1070- test_form.on_save_report_button_clicked()
1071+
1072+ with patch.object(Path, 'open') as mocked_path_open:
1073+ test_path = Path('testfile.txt')
1074+ mocked_save_filename.return_value = test_path, 'ext'
1075+
1076+ test_form = exceptionform.ExceptionForm()
1077+ test_form.file_attachment = None
1078+
1079+ with patch.object(test_form, '_pyuno_import') as mock_pyuno, \
1080+ patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \
1081+ patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
1082+ mock_pyuno.return_value = 'UNO Bridge Test'
1083+ mock_traceback.return_value = 'openlp: Traceback Test'
1084+ mock_description.return_value = 'Description Test'
1085+
1086+ # WHEN: on_save_report_button_clicked called
1087+ test_form.on_save_report_button_clicked()
1088
1089 # THEN: Verify proper calls to save file
1090 # self.maxDiff = None
1091- check_text = "call().write({text})".format(text=MAIL_ITEM_TEXT.__repr__())
1092- write_text = "{text}".format(text=mocked_open.mock_calls[1])
1093- mocked_open.assert_called_with('testfile.txt', 'w')
1094- self.assertEquals(check_text, write_text, "Saved information should match test text")
1095+ mocked_path_open.assert_has_calls([call().__enter__().write(MAIL_ITEM_TEXT)])
1096
1097=== modified file 'tests/functional/openlp_plugins/images/test_lib.py'
1098--- tests/functional/openlp_plugins/images/test_lib.py 2017-08-26 15:06:11 +0000
1099+++ tests/functional/openlp_plugins/images/test_lib.py 2017-09-25 17:10:35 +0000
1100@@ -58,7 +58,7 @@
1101 Test that the validate_and_load_test() method when called without a group
1102 """
1103 # GIVEN: A list of files
1104- file_list = ['/path1/image1.jpg', '/path2/image2.jpg']
1105+ file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
1106
1107 # WHEN: Calling validate_and_load with the list of files
1108 self.media_item.validate_and_load(file_list)
1109@@ -66,7 +66,7 @@
1110 # THEN: load_list should have been called with the file list and None,
1111 # the directory should have been saved to the settings
1112 mocked_load_list.assert_called_once_with(file_list, None)
1113- mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1'))
1114+ mocked_settings().setValue.assert_called_once_with(ANY, Path('path1'))
1115
1116 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')
1117 @patch('openlp.plugins.images.lib.mediaitem.Settings')
1118@@ -75,7 +75,7 @@
1119 Test that the validate_and_load_test() method when called with a group
1120 """
1121 # GIVEN: A list of files
1122- file_list = ['/path1/image1.jpg', '/path2/image2.jpg']
1123+ file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
1124
1125 # WHEN: Calling validate_and_load with the list of files and a group
1126 self.media_item.validate_and_load(file_list, 'group')
1127@@ -83,7 +83,7 @@
1128 # THEN: load_list should have been called with the file list and the group name,
1129 # the directory should have been saved to the settings
1130 mocked_load_list.assert_called_once_with(file_list, 'group')
1131- mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1'))
1132+ mocked_settings().setValue.assert_called_once_with(ANY, Path('path1'))
1133
1134 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
1135 def test_save_new_images_list_empty_list(self, mocked_load_full_list):
1136@@ -107,8 +107,8 @@
1137 Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
1138 """
1139 # GIVEN: A list with 1 image and a mocked out manager
1140- image_list = ['test_image.jpg']
1141- ImageFilenames.filename = ''
1142+ image_list = [Path('test_image.jpg')]
1143+ ImageFilenames.file_path = None
1144 self.media_item.manager = MagicMock()
1145
1146 # WHEN: We run save_new_images_list with reload_list=True
1147@@ -118,7 +118,7 @@
1148 self.assertEquals(mocked_load_full_list.call_count, 1, 'load_full_list() should have been called')
1149
1150 # CLEANUP: Remove added attribute from ImageFilenames
1151- delattr(ImageFilenames, 'filename')
1152+ delattr(ImageFilenames, 'file_path')
1153
1154 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
1155 def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list):
1156@@ -126,7 +126,7 @@
1157 Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
1158 """
1159 # GIVEN: A list with 1 image and a mocked out manager
1160- image_list = ['test_image.jpg']
1161+ image_list = [Path('test_image.jpg')]
1162 self.media_item.manager = MagicMock()
1163
1164 # WHEN: We run save_new_images_list with reload_list=False
1165@@ -141,7 +141,7 @@
1166 Test that the save_new_images_list() saves all images in the list
1167 """
1168 # GIVEN: A list with 3 images
1169- image_list = ['test_image_1.jpg', 'test_image_2.jpg', 'test_image_3.jpg']
1170+ image_list = [Path('test_image_1.jpg'), Path('test_image_2.jpg'), Path('test_image_3.jpg')]
1171 self.media_item.manager = MagicMock()
1172
1173 # WHEN: We run save_new_images_list with the list of 3 images
1174@@ -157,7 +157,7 @@
1175 Test that the save_new_images_list() ignores everything in the provided list except strings
1176 """
1177 # GIVEN: A list with images and objects
1178- image_list = ['test_image_1.jpg', None, True, ImageFilenames(), 'test_image_2.jpg']
1179+ image_list = [Path('test_image_1.jpg'), None, True, ImageFilenames(), Path('test_image_2.jpg')]
1180 self.media_item.manager = MagicMock()
1181
1182 # WHEN: We run save_new_images_list with the list of images and objects
1183@@ -191,7 +191,7 @@
1184 ImageGroups.parent_id = 1
1185 self.media_item.manager = MagicMock()
1186 self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
1187- self.media_item.service_path = ''
1188+ self.media_item.service_path = Path()
1189 test_group = ImageGroups()
1190 test_group.id = 1
1191
1192@@ -215,13 +215,13 @@
1193 # Create some fake objects that should be removed
1194 returned_object1 = ImageFilenames()
1195 returned_object1.id = 1
1196- returned_object1.filename = '/tmp/test_file_1.jpg'
1197+ returned_object1.file_path = Path('/', 'tmp', 'test_file_1.jpg')
1198 returned_object2 = ImageFilenames()
1199 returned_object2.id = 2
1200- returned_object2.filename = '/tmp/test_file_2.jpg'
1201+ returned_object2.file_path = Path('/', 'tmp', 'test_file_2.jpg')
1202 returned_object3 = ImageFilenames()
1203 returned_object3.id = 3
1204- returned_object3.filename = '/tmp/test_file_3.jpg'
1205+ returned_object3.file_path = Path('/', 'tmp', 'test_file_3.jpg')
1206 return [returned_object1, returned_object2, returned_object3]
1207 if args[1] == ImageGroups and args[2]:
1208 # Change the parent_id that is matched so we don't get into an endless loop
1209@@ -243,9 +243,9 @@
1210 test_image = ImageFilenames()
1211 test_image.id = 1
1212 test_image.group_id = 1
1213- test_image.filename = 'imagefile.png'
1214+ test_image.file_path = Path('imagefile.png')
1215 self.media_item.manager = MagicMock()
1216- self.media_item.service_path = ''
1217+ self.media_item.service_path = Path()
1218 self.media_item.list_view = MagicMock()
1219 mocked_row_item = MagicMock()
1220 mocked_row_item.data.return_value = test_image
1221@@ -265,13 +265,13 @@
1222 # GIVEN: An ImageFilenames that already exists in the database
1223 image_file = ImageFilenames()
1224 image_file.id = 1
1225- image_file.filename = '/tmp/test_file_1.jpg'
1226+ image_file.file_path = Path('/', 'tmp', 'test_file_1.jpg')
1227 self.media_item.manager = MagicMock()
1228 self.media_item.manager.get_object_filtered.return_value = image_file
1229- ImageFilenames.filename = ''
1230+ ImageFilenames.file_path = None
1231
1232 # WHEN: create_item_from_id() is called
1233- item = self.media_item.create_item_from_id(1)
1234+ item = self.media_item.create_item_from_id('1')
1235
1236 # THEN: A QTreeWidgetItem should be created with the above model object as it's data
1237 self.assertIsInstance(item, QtWidgets.QTreeWidgetItem)
1238@@ -279,4 +279,4 @@
1239 item_data = item.data(0, QtCore.Qt.UserRole)
1240 self.assertIsInstance(item_data, ImageFilenames)
1241 self.assertEqual(1, item_data.id)
1242- self.assertEqual('/tmp/test_file_1.jpg', item_data.filename)
1243+ self.assertEqual(Path('/', 'tmp', 'test_file_1.jpg'), item_data.file_path)
1244
1245=== added file 'tests/functional/openlp_plugins/images/test_upgrade.py'
1246--- tests/functional/openlp_plugins/images/test_upgrade.py 1970-01-01 00:00:00 +0000
1247+++ tests/functional/openlp_plugins/images/test_upgrade.py 2017-09-25 17:10:35 +0000
1248@@ -0,0 +1,83 @@
1249+# -*- coding: utf-8 -*-
1250+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
1251+
1252+###############################################################################
1253+# OpenLP - Open Source Lyrics Projection #
1254+# --------------------------------------------------------------------------- #
1255+# Copyright (c) 2008-2017 OpenLP Developers #
1256+# --------------------------------------------------------------------------- #
1257+# This program is free software; you can redistribute it and/or modify it #
1258+# under the terms of the GNU General Public License as published by the Free #
1259+# Software Foundation; version 2 of the License. #
1260+# #
1261+# This program is distributed in the hope that it will be useful, but WITHOUT #
1262+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1263+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1264+# more details. #
1265+# #
1266+# You should have received a copy of the GNU General Public License along #
1267+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1268+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1269+###############################################################################
1270+"""
1271+This module contains tests for the lib submodule of the Images plugin.
1272+"""
1273+import os
1274+import shutil
1275+from tempfile import mkdtemp
1276+from unittest import TestCase
1277+from unittest.mock import patch
1278+
1279+from openlp.core.common import AppLocation, Settings
1280+from openlp.core.common.path import Path
1281+from openlp.core.lib.db import Manager
1282+from openlp.plugins.images.lib import upgrade
1283+from openlp.plugins.images.lib.db import ImageFilenames, init_schema
1284+
1285+from tests.helpers.testmixin import TestMixin
1286+from tests.utils.constants import TEST_RESOURCES_PATH
1287+
1288+__default_settings__ = {
1289+ 'images/db type': 'sqlite',
1290+ 'images/background color': '#000000',
1291+}
1292+
1293+
1294+class TestImageDBUpgrade(TestCase, TestMixin):
1295+ """
1296+ Test that the image database is upgraded correctly
1297+ """
1298+ def setUp(self):
1299+ self.build_settings()
1300+ Settings().extend_default_settings(__default_settings__)
1301+ self.tmp_folder = mkdtemp()
1302+
1303+ def tearDown(self):
1304+ """
1305+ Delete all the C++ objects at the end so that we don't have a segfault
1306+ """
1307+ self.destroy_settings()
1308+ # Ignore errors since windows can have problems with locked files
1309+ shutil.rmtree(self.tmp_folder, ignore_errors=True)
1310+
1311+ def test_image_filenames_table(self):
1312+ """
1313+ Test that the ImageFilenames table is correctly upgraded to the latest version
1314+ """
1315+ # GIVEN: An unversioned image database
1316+ temp_db_name = os.path.join(self.tmp_folder, 'image-v0.sqlite')
1317+ shutil.copyfile(os.path.join(TEST_RESOURCES_PATH, 'images', 'image-v0.sqlite'), temp_db_name)
1318+
1319+ with patch.object(AppLocation, 'get_data_path', return_value=Path('/', 'test', 'dir')):
1320+ # WHEN: Initalising the database manager
1321+ manager = Manager('images', init_schema, db_file_path=temp_db_name, upgrade_mod=upgrade)
1322+
1323+ # THEN: The database should have been upgraded and image_filenames.file_path should return Path objects
1324+ upgraded_results = manager.get_all_objects(ImageFilenames)
1325+
1326+ expected_result_data = {1: Path('/', 'test', 'image1.jpg'),
1327+ 2: Path('/', 'test', 'dir', 'image2.jpg'),
1328+ 3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
1329+
1330+ for result in upgraded_results:
1331+ self.assertEqual(expected_result_data[result.id], result.file_path)
1332
1333=== added directory 'tests/resources/images'
1334=== added file 'tests/resources/images/image-v0.sqlite'
1335Binary files tests/resources/images/image-v0.sqlite 1970-01-01 00:00:00 +0000 and tests/resources/images/image-v0.sqlite 2017-09-25 17:10:35 +0000 differ