"Convert strings from python2 to python3 in plugins part 3

- Convert strings in plugins/remote
- Convert strings in plugins/songs
- Update projectordb test
- Fix oops in format tag - missed escaping brace(s)

--------------------------------
lp:~alisonken1/openlp/strings-plugins3 (revision 2670)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1576/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1487/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1425/
[SUCCESS]..."

bzr-revno: 2670
This commit is contained in:
Ken Roberts 2016-05-31 16:56:23 +02:00 committed by Raoul Snyman
commit 1782cd8274
45 changed files with 400 additions and 291 deletions

View File

@ -141,7 +141,8 @@ class HttpRouter(RegistryProperties):
""" """
Initialise the router stack and any other variables. Initialise the router stack and any other variables.
""" """
auth_code = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password')) auth_code = "{user}:{password}".format(user=Settings().value('remotes/user id'),
password=Settings().value('remotes/password'))
try: try:
self.auth = base64.b64encode(auth_code) self.auth = base64.b64encode(auth_code)
except TypeError: except TypeError:
@ -189,7 +190,7 @@ class HttpRouter(RegistryProperties):
if self.headers['Authorization'] is None: if self.headers['Authorization'] is None:
self.do_authorisation() self.do_authorisation()
self.wfile.write(bytes('no auth header received', 'UTF-8')) self.wfile.write(bytes('no auth header received', 'UTF-8'))
elif self.headers['Authorization'] == 'Basic %s' % self.auth: elif self.headers['Authorization'] == 'Basic {auth}'.format(auth=self.auth):
self.do_http_success() self.do_http_success()
self.call_function(function, *args) self.call_function(function, *args)
else: else:
@ -231,7 +232,7 @@ class HttpRouter(RegistryProperties):
for route, func in self.routes: for route, func in self.routes:
match = re.match(route, url_path_split.path) match = re.match(route, url_path_split.path)
if match: if match:
log.debug('Route "%s" matched "%s"', route, url_path) log.debug('Route "{route}" matched "{path}"'.format(route=route, path=url_path))
args = [] args = []
for param in match.groups(): for param in match.groups():
args.append(param) args.append(param)
@ -319,9 +320,9 @@ class HttpRouter(RegistryProperties):
stage = translate('RemotePlugin.Mobile', 'Stage View') stage = translate('RemotePlugin.Mobile', 'Stage View')
live = translate('RemotePlugin.Mobile', 'Live View') live = translate('RemotePlugin.Mobile', 'Live View')
self.template_vars = { self.template_vars = {
'app_title': "%s %s" % (UiStrings().OLPV2x, remote), 'app_title': "{main} {remote}".format(main=UiStrings().OLPV2x, remote=remote),
'stage_title': "%s %s" % (UiStrings().OLPV2x, stage), 'stage_title': "{main} {stage}".format(main=UiStrings().OLPV2x, stage=stage),
'live_title': "%s %s" % (UiStrings().OLPV2x, live), 'live_title': "{main} {live}".format(main=UiStrings().OLPV2x, live=live),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'), 'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
@ -354,7 +355,7 @@ class HttpRouter(RegistryProperties):
:param file_name: file name with path :param file_name: file name with path
:return: :return:
""" """
log.debug('serve file request %s' % file_name) log.debug('serve file request {name}'.format(name=file_name))
parts = file_name.split('/') parts = file_name.split('/')
if len(parts) == 1: if len(parts) == 1:
file_name = os.path.join(parts[0], 'stage.html') file_name = os.path.join(parts[0], 'stage.html')
@ -381,10 +382,10 @@ class HttpRouter(RegistryProperties):
content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
else: else:
file_handle = open(path, 'rb') file_handle = open(path, 'rb')
log.debug('Opened %s' % path) log.debug('Opened {path}'.format(path=path))
content = file_handle.read() content = file_handle.read()
except IOError: except IOError:
log.exception('Failed to open %s' % path) log.exception('Failed to open {path}'.format(path=path))
return self.do_not_found() return self.do_not_found()
finally: finally:
if file_handle: if file_handle:
@ -402,7 +403,7 @@ class HttpRouter(RegistryProperties):
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html. Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en' where xx is the language, e.g. 'en'
""" """
log.debug('serve file request %s' % file_name) log.debug('serve file request {name}'.format(name=file_name))
if not file_name: if not file_name:
file_name = 'index.html' file_name = 'index.html'
if '.' not in file_name: if '.' not in file_name:
@ -433,7 +434,9 @@ class HttpRouter(RegistryProperties):
:param dimensions: image size :param dimensions: image size
:param controller_name: controller to be called :param controller_name: controller to be called
""" """
log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name)) log.debug('serve thumbnail {cname}/thumbnails{dim}/{fname}'.format(cname=controller_name,
dim=dimensions,
fname=file_name))
supported_controllers = ['presentations', 'images'] supported_controllers = ['presentations', 'images']
# -1 means use the default dimension in ImageManager # -1 means use the default dimension in ImageManager
width = -1 width = -1
@ -539,7 +542,7 @@ class HttpRouter(RegistryProperties):
:param var: variable - not used :param var: variable - not used
""" """
log.debug("controller_text var = %s" % var) log.debug("controller_text var = {var}".format(var=var))
current_item = self.live_controller.service_item current_item = self.live_controller.service_item
data = [] data = []
if current_item: if current_item:
@ -594,7 +597,8 @@ class HttpRouter(RegistryProperties):
:param display_type: This is the type of slide controller, either ``preview`` or ``live``. :param display_type: This is the type of slide controller, either ``preview`` or ``live``.
:param action: The action to perform. :param action: The action to perform.
""" """
event = getattr(self.live_controller, 'slidecontroller_%s_%s' % (display_type, action)) event = getattr(self.live_controller, 'slidecontroller_{display}_{action}'.format(display=display_type,
action=action))
if self.request_data: if self.request_data:
try: try:
data = json.loads(self.request_data)['request']['id'] data = json.loads(self.request_data)['request']['id']
@ -623,7 +627,7 @@ class HttpRouter(RegistryProperties):
:param action: The action to perform. :param action: The action to perform.
""" """
event = getattr(self.service_manager, 'servicemanager_%s_item' % action) event = getattr(self.service_manager, 'servicemanager_{action}_item'.format(action=action))
if self.request_data: if self.request_data:
try: try:
data = int(json.loads(self.request_data)['request']['id']) data = int(json.loads(self.request_data)['request']['id'])
@ -680,7 +684,7 @@ class HttpRouter(RegistryProperties):
return self.do_http_error() return self.do_http_error()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name) plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item: if plugin.status == PluginStatus.Active and plugin.media_item:
getattr(plugin.media_item, '%s_go_live' % plugin_name).emit([request_id, True]) getattr(plugin.media_item, '{name}_go_live'.format(name=plugin_name)).emit([request_id, True])
return self.do_http_success() return self.do_http_success()
def add_to_service(self, plugin_name): def add_to_service(self, plugin_name):
@ -696,5 +700,5 @@ class HttpRouter(RegistryProperties):
plugin = self.plugin_manager.get_plugin_by_name(plugin_name) plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item: if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(request_id) item_id = plugin.media_item.create_item_from_id(request_id)
getattr(plugin.media_item, '%s_add_to_service' % plugin_name).emit([item_id, True]) getattr(plugin.media_item, '{name}_add_to_service'.format(name=plugin_name)).emit([item_id, True])
self.do_http_success() self.do_http_success()

View File

@ -136,11 +136,13 @@ class OpenLPServer(RegistryProperties):
while loop < 4: while loop < 4:
try: try:
self.httpd = server_class((address, port), CustomHandler) self.httpd = server_class((address, port), CustomHandler)
log.debug("Server started for class %s %s %d" % (server_class, address, port)) log.debug("Server started for class {name} {address} {port:d}".format(name=server_class,
address=address,
port=port))
break break
except OSError: except OSError:
log.debug("failed to start http server thread state %d %s" % log.debug("failed to start http server thread state "
(loop, self.http_thread.isRunning())) "{loop:d} {running}".format(loop=loop, running=self.http_thread.isRunning()))
loop += 1 loop += 1
time.sleep(0.1) time.sleep(0.1)
except: except:

View File

@ -192,14 +192,14 @@ class RemoteTab(SettingsTab):
'Show thumbnails of non-text slides in remote and stage view.')) 'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App')) self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.android_qr_description_label.setText( self.android_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the ' translate('RemotePlugin.RemoteTab',
'Android app from Google Play.') % 'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
'https://play.google.com/store/apps/details?id=org.openlp.android2') 'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App')) self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
self.ios_qr_description_label.setText( self.ios_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the ' translate('RemotePlugin.RemoteTab',
'iOS app from the App Store.') % 'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
'https://itunes.apple.com/app/id1096218725') 'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server')) self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
self.https_error_label.setText( self.https_error_label.setText(
translate('RemotePlugin.RemoteTab', 'Could not find an SSL certificate. The HTTPS server will not be ' translate('RemotePlugin.RemoteTab', 'Could not find an SSL certificate. The HTTPS server will not be '
@ -217,18 +217,18 @@ class RemoteTab(SettingsTab):
Update the display based on the data input on the screen Update the display based on the data input on the screen
""" """
ip_address = self.get_ip_address(self.address_edit.text()) ip_address = self.get_ip_address(self.address_edit.text())
http_url = 'http://%s:%s/' % (ip_address, self.port_spin_box.value()) http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value())
https_url = 'https://%s:%s/' % (ip_address, self.https_port_spin_box.value()) https_url = 'https://{url}:{text}/'.format(url=ip_address, text=self.https_port_spin_box.value())
self.remote_url.setText('<a href="%s">%s</a>' % (http_url, http_url)) self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
self.remote_https_url.setText('<a href="%s">%s</a>' % (https_url, https_url)) self.remote_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url))
http_url_temp = http_url + 'stage' http_url_temp = http_url + 'stage'
https_url_temp = https_url + 'stage' https_url_temp = https_url + 'stage'
self.stage_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp)) self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.stage_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp)) self.stage_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
http_url_temp = http_url + 'main' http_url_temp = http_url + 'main'
https_url_temp = https_url + 'main' https_url_temp = https_url + 'main'
self.live_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp)) self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.live_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp)) self.live_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
def get_ip_address(self, ip_address): def get_ip_address(self, ip_address):
""" """

View File

@ -130,6 +130,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('Wizard', 'Wizard')) self.setWindowTitle(translate('Wizard', 'Wizard'))
# TODO: Check format() using template strings
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
'Welcome to the Duplicate Song Removal Wizard')) 'Welcome to the Duplicate Song Removal Wizard'))
self.information_label.setText( self.information_label.setText(
@ -148,8 +149,8 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
Set the wizard review page header text. Set the wizard review page header text.
""" """
self.review_page.setTitle( self.review_page.setTitle(
translate('Wizard', 'Review duplicate songs (%s/%s)') % translate('Wizard', 'Review duplicate songs ({current}/{total})').format(current=self.review_current_count,
(self.review_current_count, self.review_total_count)) total=self.review_total_count))
def custom_page_changed(self, page_id): def custom_page_changed(self, page_id):
""" """

View File

@ -50,7 +50,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
""" """
Class to manage the editing of a song Class to manage the editing of a song
""" """
log.info('%s EditSongForm loaded', __name__) log.info('{name} EditSongForm loaded'.format(name=__name__))
def __init__(self, media_item, parent, manager): def __init__(self, media_item, parent, manager):
""" """
@ -185,20 +185,23 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
verse = verse.data(QtCore.Qt.UserRole) verse = verse.data(QtCore.Qt.UserRole)
if verse not in verse_names: if verse not in verse_names:
verses.append(verse) verses.append(verse)
verse_names.append('%s%s' % (VerseType.translated_tag(verse[0]), verse[1:])) verse_names.append('{verse1}{verse2}'.format(verse1=VerseType.translated_tag(verse[0]),
verse2=verse[1:]))
for count, item in enumerate(order): for count, item in enumerate(order):
if item not in verses: if item not in verses:
invalid_verses.append(order_names[count]) invalid_verses.append(order_names[count])
if invalid_verses: if invalid_verses:
valid = create_separated_list(verse_names) valid = create_separated_list(verse_names)
if len(invalid_verses) > 1: if len(invalid_verses) > 1:
msg = translate('SongsPlugin.EditSongForm', 'There are no verses corresponding to "%(invalid)s". ' msg = translate('SongsPlugin.EditSongForm',
'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \ 'There are no verses corresponding to "{invalid}". Valid entries are {valid}.\n'
{'invalid': ', '.join(invalid_verses), 'valid': valid} 'Please enter the verses separated by spaces.'
).format(invalid=', '.join(invalid_verses), valid=valid)
else: else:
msg = translate('SongsPlugin.EditSongForm', 'There is no verse corresponding to "%(invalid)s".' msg = translate('SongsPlugin.EditSongForm',
'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \ 'There is no verse corresponding to "{invalid}". Valid entries are {valid}.\n'
{'invalid': invalid_verses[0], 'valid': valid} 'Please enter the verses separated by spaces.').format(invalid=invalid_verses[0],
valid=valid)
critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'), critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'),
message=msg) message=msg)
return len(invalid_verses) == 0 return len(invalid_verses) == 0
@ -242,23 +245,24 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
field = item.data(QtCore.Qt.UserRole) field = item.data(QtCore.Qt.UserRole)
verse_tags.append(field) verse_tags.append(field)
if not self._validate_tags(tags): if not self._validate_tags(tags):
misplaced_tags.append('%s %s' % (VerseType.translated_name(field[0]), field[1:])) misplaced_tags.append('{field1} {field2}'.format(field1=VerseType.translated_name(field[0]),
field2=field[1:]))
if misplaced_tags: if misplaced_tags:
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.EditSongForm', message=translate('SongsPlugin.EditSongForm',
'There are misplaced formatting tags in the following verses:\n\n%s\n\n' 'There are misplaced formatting tags in the following verses:\n\n{tag}\n\n'
'Please correct these tags before continuing.' % ', '.join(misplaced_tags))) 'Please correct these tags before continuing.').format(tag=', '.join(misplaced_tags)))
return False return False
for tag in verse_tags: for tag in verse_tags:
if verse_tags.count(tag) > 26: if verse_tags.count(tag) > 26:
# lp#1310523: OpenLyrics allows only a-z variants of one verse: # lp#1310523: OpenLyrics allows only a-z variants of one verse:
# http://openlyrics.info/dataformat.html#verse-name # http://openlyrics.info/dataformat.html#verse-name
critical_error_message_box(message=translate( critical_error_message_box(message=translate(
'SongsPlugin.EditSongForm', 'You have %(count)s verses named %(name)s %(number)s. ' 'SongsPlugin.EditSongForm',
'You can have at most 26 verses with the same name' % 'You have {count} verses named {name} {number}. You can have at most '
{'count': verse_tags.count(tag), '26 verses with the same name').format(count=verse_tags.count(tag),
'name': VerseType.translated_name(tag[0]), name=VerseType.translated_name(tag[0]),
'number': tag[1:]})) number=tag[1:]))
return False return False
return True return True
@ -313,7 +317,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)', self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)',
r'\g<1>1\2', self.song.verse_order) r'\g<1>1\2', self.song.verse_order)
except: except:
log.exception('Problem processing song Lyrics \n%s', sxml.dump_xml()) log.exception('Problem processing song Lyrics \n{xml}'.forma(xml=sxml.dump_xml()))
raise raise
def keyPressEvent(self, event): def keyPressEvent(self, event):
@ -492,7 +496,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
verse[0]['type'] = VerseType.tags[index] verse[0]['type'] = VerseType.tags[index]
if verse[0]['label'] == '': if verse[0]['label'] == '':
verse[0]['label'] = '1' verse[0]['label'] = '1'
verse_def = '%s%s' % (verse[0]['type'], verse[0]['label']) verse_def = '{verse}{label}'.format(verse=verse[0]['type'], label=verse[0]['label'])
item = QtWidgets.QTableWidgetItem(verse[1]) item = QtWidgets.QTableWidgetItem(verse[1])
item.setData(QtCore.Qt.UserRole, verse_def) item.setData(QtCore.Qt.UserRole, verse_def)
self.verse_list_widget.setItem(count, 0, item) self.verse_list_widget.setItem(count, 0, item)
@ -501,7 +505,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
for count, verse in enumerate(verses): for count, verse in enumerate(verses):
self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1) self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1)
item = QtWidgets.QTableWidgetItem(verse) item = QtWidgets.QTableWidgetItem(verse)
verse_def = '%s%s' % (VerseType.tags[VerseType.Verse], str(count + 1)) verse_def = '{verse}{count:d}'.format(verse=VerseType.tags[VerseType.Verse], count=(count + 1))
item.setData(QtCore.Qt.UserRole, verse_def) item.setData(QtCore.Qt.UserRole, verse_def)
self.verse_list_widget.setItem(count, 0, item) self.verse_list_widget.setItem(count, 0, item)
if self.song.verse_order: if self.song.verse_order:
@ -514,7 +518,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
if verse_index is None: if verse_index is None:
verse_index = VerseType.from_tag(verse_def[0]) verse_index = VerseType.from_tag(verse_def[0])
verse_tag = VerseType.translated_tags[verse_index].upper() verse_tag = VerseType.translated_tags[verse_index].upper()
translated.append('%s%s' % (verse_tag, verse_def[1:])) translated.append('{tag}{verse}'.format(tag=verse_tag, verse=verse_def[1:]))
self.verse_order_edit.setText(' '.join(translated)) self.verse_order_edit.setText(' '.join(translated))
else: else:
self.verse_order_edit.setText('') self.verse_order_edit.setText('')
@ -554,7 +558,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
item = self.verse_list_widget.item(row, 0) item = self.verse_list_widget.item(row, 0)
verse_def = item.data(QtCore.Qt.UserRole) verse_def = item.data(QtCore.Qt.UserRole)
verse_tag = VerseType.translated_tag(verse_def[0]) verse_tag = VerseType.translated_tag(verse_def[0])
row_def = '%s%s' % (verse_tag, verse_def[1:]) row_def = '{tag}{verse}'.format(tag=verse_tag, verse=verse_def[1:])
row_label.append(row_def) row_label.append(row_def)
self.verse_list_widget.setVerticalHeaderLabels(row_label) self.verse_list_widget.setVerticalHeaderLabels(row_label)
self.verse_list_widget.resizeRowsToContents() self.verse_list_widget.resizeRowsToContents()
@ -742,7 +746,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.verse_form.set_verse('', True) self.verse_form.set_verse('', True)
if self.verse_form.exec(): if self.verse_form.exec():
after_text, verse_tag, verse_num = self.verse_form.get_verse() after_text, verse_tag, verse_num = self.verse_form.get_verse()
verse_def = '%s%s' % (verse_tag, verse_num) verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num)
item = QtWidgets.QTableWidgetItem(after_text) item = QtWidgets.QTableWidgetItem(after_text)
item.setData(QtCore.Qt.UserRole, verse_def) item.setData(QtCore.Qt.UserRole, verse_def)
item.setText(after_text) item.setText(after_text)
@ -760,7 +764,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.verse_form.set_verse(temp_text, True, verse_id) self.verse_form.set_verse(temp_text, True, verse_id)
if self.verse_form.exec(): if self.verse_form.exec():
after_text, verse_tag, verse_num = self.verse_form.get_verse() after_text, verse_tag, verse_num = self.verse_form.get_verse()
verse_def = '%s%s' % (verse_tag, verse_num) verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num)
item.setData(QtCore.Qt.UserRole, verse_def) item.setData(QtCore.Qt.UserRole, verse_def)
item.setText(after_text) item.setText(after_text)
# number of lines has changed, repaint the list moving the data # number of lines has changed, repaint the list moving the data
@ -793,7 +797,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
field = item.data(QtCore.Qt.UserRole) field = item.data(QtCore.Qt.UserRole)
verse_tag = VerseType.translated_name(field[0]) verse_tag = VerseType.translated_name(field[0])
verse_num = field[1:] verse_num = field[1:]
verse_list += '---[%s:%s]---\n' % (verse_tag, verse_num) verse_list += '---[{tag}:{number}]---\n'.format(tag=verse_tag, number=verse_num)
verse_list += item.text() verse_list += item.text()
verse_list += '\n' verse_list += '\n'
self.verse_form.set_verse(verse_list) self.verse_form.set_verse(verse_list)
@ -828,7 +832,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
verse_num = match.group(1) verse_num = match.group(1)
else: else:
verse_num = '1' verse_num = '1'
verse_def = '%s%s' % (verse_tag, verse_num) verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num)
else: else:
if parts.endswith('\n'): if parts.endswith('\n'):
parts = parts.rstrip('\n') parts = parts.rstrip('\n')
@ -919,7 +923,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
""" """
Loads file(s) from the filesystem. Loads file(s) from the filesystem.
""" """
filters = '%s (*)' % UiStrings().AllFiles filters = '{text} (*)'.format(text=UiStrings().AllFiles)
file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '',
filters) filters)
for filename in file_names: for filename in file_names:
@ -1027,7 +1031,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
for item in order_text.split(): for item in order_text.split():
verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])] verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])]
verse_num = item[1:].lower() verse_num = item[1:].lower()
order.append('%s%s' % (verse_tag, verse_num)) order.append('{tag}{number}'.format(tag=verse_tag, number=verse_num))
self.song.verse_order = ' '.join(order) self.song.verse_order = ' '.join(order)
self.song.ccli_number = self.ccli_number_edit.text() self.song.ccli_number = self.ccli_number_edit.text()
theme_name = self.theme_combo_box.currentText() theme_name = self.theme_combo_box.currentText()
@ -1082,12 +1086,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
try: try:
os.remove(audio) os.remove(audio)
except: except:
log.exception('Could not remove file: %s', audio) log.exception('Could not remove file: {audio}'.format(audio=audio))
if not files: if not files:
try: try:
os.rmdir(save_path) os.rmdir(save_path)
except OSError: except OSError:
log.exception('Could not remove directory: %s', save_path) log.exception('Could not remove directory: {path}'.format(path=save_path))
clean_song(self.manager, self.song) clean_song(self.manager, self.song)
self.manager.save_object(self.song) self.manager.save_object(self.song)
self.media_item.auto_select_id = self.song.id self.media_item.auto_select_id = self.song.id

View File

@ -59,7 +59,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
if self.verse_text_edit.textCursor().columnNumber() != 0: if self.verse_text_edit.textCursor().columnNumber() != 0:
self.verse_text_edit.insertPlainText('\n') self.verse_text_edit.insertPlainText('\n')
verse_tag = VerseType.translated_name(verse_tag) verse_tag = VerseType.translated_name(verse_tag)
self.verse_text_edit.insertPlainText('---[%s:%s]---\n' % (verse_tag, verse_num)) self.verse_text_edit.insertPlainText('---[{tag}:{number}]---\n'.format(tag=verse_tag, number=verse_num))
self.verse_text_edit.setFocus() self.verse_text_edit.setFocus()
def on_split_button_clicked(self): def on_split_button_clicked(self):
@ -107,7 +107,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
self.verse_type_combo_box.currentIndex()] self.verse_type_combo_box.currentIndex()]
if not text: if not text:
return return
position = text.rfind('---[%s' % verse_name, 0, position) position = text.rfind('---[{verse}'.format(verse=verse_name), 0, position)
if position == -1: if position == -1:
self.verse_number_box.setValue(1) self.verse_number_box.setValue(1)
return return
@ -124,7 +124,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
verse_num = 1 verse_num = 1
self.verse_number_box.setValue(verse_num) self.verse_number_box.setValue(verse_num)
def set_verse(self, text, single=False, tag='%s1' % VerseType.tags[VerseType.Verse]): def set_verse(self, text, single=False, tag='{verse}1'.format(verse=VerseType.tags[VerseType.Verse])):
""" """
Save the verse Save the verse
@ -142,7 +142,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
self.insert_button.setVisible(False) self.insert_button.setVisible(False)
else: else:
if not text: if not text:
text = '---[%s:1]---\n' % VerseType.translated_names[VerseType.Verse] text = '---[{tag}:1]---\n'.format(tag=VerseType.translated_names[VerseType.Verse])
self.verse_type_combo_box.setCurrentIndex(0) self.verse_type_combo_box.setCurrentIndex(0)
self.verse_number_box.setValue(1) self.verse_number_box.setValue(1)
self.insert_button.setVisible(True) self.insert_button.setVisible(True)
@ -167,5 +167,5 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
""" """
text = self.verse_text_edit.toPlainText() text = self.verse_text_edit.toPlainText()
if not text.startswith('---['): if not text.startswith('---['):
text = '---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text) text = '---[{tag}:1]---\n{text}'.format(tag=VerseType.translated_names[VerseType.Verse], text=text)
return text return text

View File

@ -34,7 +34,7 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
""" """
Class to show a list of files from the Class to show a list of files from the
""" """
log.info('%s MediaFilesForm loaded', __name__) log.info('{name} MediaFilesForm loaded'.format(name=__name__))
def __init__(self, parent): def __init__(self, parent):
super(MediaFilesForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(MediaFilesForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)

View File

@ -143,6 +143,7 @@ class SongExportForm(OpenLPWizard):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard'))
# TODO: Verify format() with template variables
self.title_label.setText(WizardStrings.HeaderStyle % self.title_label.setText(WizardStrings.HeaderStyle %
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard')) translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
self.information_label.setText( self.information_label.setText(
@ -151,7 +152,7 @@ class SongExportForm(OpenLPWizard):
self.available_songs_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs')) self.available_songs_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs'))
self.available_songs_page.setSubTitle(translate('SongsPlugin.ExportWizardForm', self.available_songs_page.setSubTitle(translate('SongsPlugin.ExportWizardForm',
'Check the songs you want to export.')) 'Check the songs you want to export.'))
self.search_label.setText('%s:' % UiStrings().Search) self.search_label.setText('{text}:'.format(text=UiStrings().Search))
self.uncheck_button.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All')) self.uncheck_button.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All'))
self.check_button.setText(translate('SongsPlugin.ExportWizardForm', 'Check All')) self.check_button.setText(translate('SongsPlugin.ExportWizardForm', 'Check All'))
self.export_song_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory')) self.export_song_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory'))
@ -223,7 +224,7 @@ class SongExportForm(OpenLPWizard):
if song.temporary: if song.temporary:
continue continue
authors = create_separated_list([author.display_name for author in song.authors]) authors = create_separated_list([author.display_name for author in song.authors])
title = '%s (%s)' % (str(song.title), authors) title = '{title} ({author})'.format(title=song.title, author=authors)
item = QtWidgets.QListWidgetItem(title) item = QtWidgets.QListWidgetItem(title)
item.setData(QtCore.Qt.UserRole, song) item.setData(QtCore.Qt.UserRole, song)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
@ -257,7 +258,7 @@ class SongExportForm(OpenLPWizard):
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.')) self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.'))
except OSError as ose: except OSError as ose:
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed because this ' self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed because this '
'error occurred: %s') % ose.strerror) 'error occurred: {error}').format(error=ose.strerror))
def on_search_line_edit_changed(self, text): def on_search_line_edit_changed(self, text):
""" """

View File

@ -132,6 +132,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard')) self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard'))
# TODO: Verify format() with template variables
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
'Welcome to the Song Import Wizard')) 'Welcome to the Song Import Wizard'))
self.information_label.setText( self.information_label.setText(
@ -236,7 +237,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
""" """
if filters: if filters:
filters += ';;' filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles filters += '{text} (*)'.format(text=UiStrings().AllFiles)
file_names = FileDialog.getOpenFileNames( file_names = FileDialog.getOpenFileNames(
self, title, self, title,
Settings().value(self.plugin.settings_section + '/last directory import'), filters) Settings().value(self.plugin.settings_section + '/last directory import'), filters)
@ -271,9 +272,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter') select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter')
file_path_edit = self.format_widgets[this_format]['file_path_edit'] file_path_edit = self.format_widgets[this_format]['file_path_edit']
if select_mode == SongFormatSelect.SingleFile: if select_mode == SongFormatSelect.SingleFile:
# TODO: Verify format() with template variables
self.get_file_name( self.get_file_name(
WizardStrings.OpenTypeFile % format_name, file_path_edit, 'last directory import', ext_filter) WizardStrings.OpenTypeFile % format_name, file_path_edit, 'last directory import', ext_filter)
elif select_mode == SongFormatSelect.SingleFolder: elif select_mode == SongFormatSelect.SingleFolder:
# TODO: Verify format() with template variables
self.get_folder(WizardStrings.OpenTypeFolder % format_name, file_path_edit, 'last directory import') self.get_folder(WizardStrings.OpenTypeFolder % format_name, file_path_edit, 'last directory import')
def on_add_button_clicked(self): def on_add_button_clicked(self):
@ -283,6 +286,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
this_format = self.current_format this_format = self.current_format
select_mode, format_name, ext_filter, custom_title = \ select_mode, format_name, ext_filter, custom_title = \
SongFormat.get(this_format, 'selectMode', 'name', 'filter', 'getFilesTitle') SongFormat.get(this_format, 'selectMode', 'name', 'filter', 'getFilesTitle')
# TODO: Verify format() with template variables
title = custom_title if custom_title else WizardStrings.OpenTypeFile % format_name title = custom_title if custom_title else WizardStrings.OpenTypeFile % format_name
if select_mode == SongFormatSelect.MultipleFiles: if select_mode == SongFormatSelect.MultipleFiles:
self.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter) self.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter)

View File

@ -164,7 +164,8 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
books = self.manager.get_all_objects(Book) books = self.manager.get_all_objects(Book)
books.sort(key=get_book_key) books.sort(key=get_book_key)
for book in books: for book in books:
book_name = QtWidgets.QListWidgetItem('%s (%s)' % (book.name, book.publisher)) book_name = QtWidgets.QListWidgetItem('{name} ({publisher})'.format(name=book.name,
publisher=book.publisher))
book_name.setData(QtCore.Qt.UserRole, book.id) book_name.setData(QtCore.Qt.UserRole, book.id)
self.song_books_list_widget.addItem(book_name) self.song_books_list_widget.addItem(book_name)
@ -310,11 +311,12 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
else: else:
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.')) message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box(message=translate( elif critical_error_message_box(
'SongsPlugin.SongMaintenanceForm', 'The author %s already exists. Would you like to make songs with ' message=translate(
'author %s use the existing author %s?') % 'SongsPlugin.SongMaintenanceForm',
(author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \ 'The author {original} already exists. Would you like to make songs with author {new} use the '
QtWidgets.QMessageBox.Yes: 'existing author {original}?').format(original=author.display_name, new=temp_display_name),
parent=self, question=True) == QtWidgets.QMessageBox.Yes:
self._merge_objects(author, self.merge_authors, self.reset_authors) self._merge_objects(author, self.merge_authors, self.reset_authors)
else: else:
# We restore the author's old first and last name as well as # We restore the author's old first and last name as well as
@ -346,9 +348,10 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.')) message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box( elif critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', message=translate('SongsPlugin.SongMaintenanceForm',
'The topic %s already exists. Would you like to make songs with topic %s use the ' 'The topic {original} already exists. Would you like to make songs with '
'existing topic %s?') % (topic.name, temp_name, topic.name), 'topic {new} use the existing topic {original}?').format(original=topic.name,
new=temp_name),
parent=self, question=True) == QtWidgets.QMessageBox.Yes: parent=self, question=True) == QtWidgets.QMessageBox.Yes:
self._merge_objects(topic, self.merge_topics, self.reset_topics) self._merge_objects(topic, self.merge_topics, self.reset_topics)
else: else:
@ -384,9 +387,10 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.')) message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box( elif critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', message=translate('SongsPlugin.SongMaintenanceForm',
'The book %s already exists. Would you like to make ' 'The book {original} already exists. Would you like to make songs with '
'songs with book %s use the existing book %s?') % (book.name, temp_name, book.name), 'book {new} use the existing book {original}?').format(original=book.name,
new=temp_name),
parent=self, question=True) == QtWidgets.QMessageBox.Yes: parent=self, question=True) == QtWidgets.QMessageBox.Yes:
self._merge_objects(book, self.merge_song_books, self.reset_song_books) self._merge_objects(book, self.merge_song_books, self.reset_song_books)
else: else:

View File

@ -242,7 +242,8 @@ class Ui_SongSelectDialog(object):
self.search_label.setText(translate('SongsPlugin.SongSelectForm', 'Search Text:')) self.search_label.setText(translate('SongsPlugin.SongSelectForm', 'Search Text:'))
self.search_button.setText(translate('SongsPlugin.SongSelectForm', 'Search')) self.search_button.setText(translate('SongsPlugin.SongSelectForm', 'Search'))
self.stop_button.setText(translate('SongsPlugin.SongSelectForm', 'Stop')) self.stop_button.setText(translate('SongsPlugin.SongSelectForm', 'Stop'))
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % 0) self.result_count_label.setText(translate('SongsPlugin.SongSelectForm',
'Found {count:d} song(s)').format(count=0))
self.logout_button.setText(translate('SongsPlugin.SongSelectForm', 'Logout')) self.logout_button.setText(translate('SongsPlugin.SongSelectForm', 'Logout'))
self.view_button.setText(translate('SongsPlugin.SongSelectForm', 'View')) self.view_button.setText(translate('SongsPlugin.SongSelectForm', 'View'))
self.title_label.setText(translate('SongsPlugin.SongSelectForm', 'Title:')) self.title_label.setText(translate('SongsPlugin.SongSelectForm', 'Title:'))

View File

@ -305,7 +305,8 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
self.search_progress_bar.setValue(0) self.search_progress_bar.setValue(0)
self.set_progress_visible(True) self.set_progress_visible(True)
self.search_results_widget.clear() self.search_results_widget.clear()
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % self.song_count) self.result_count_label.setText(translate('SongsPlugin.SongSelectForm',
'Found {count:d} song(s)').format(count=self.song_count))
self.application.process_events() self.application.process_events()
self.song_count = 0 self.song_count = 0
search_history = self.search_combobox.getItems() search_history = self.search_combobox.getItems()
@ -343,7 +344,8 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
:param song: :param song:
""" """
self.song_count += 1 self.song_count += 1
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % self.song_count) self.result_count_label.setText(translate('SongsPlugin.SongSelectForm',
'Found {count:d} song(s)').format(count=self.song_count))
item_title = song['title'] + ' (' + ', '.join(song['authors']) + ')' item_title = song['title'] + ' (' + ', '.join(song['authors']) + ')'
song_item = QtWidgets.QListWidgetItem(item_title, self.search_results_widget) song_item = QtWidgets.QListWidgetItem(item_title, self.search_results_widget)
song_item.setData(QtCore.Qt.UserRole, song) song_item.setData(QtCore.Qt.UserRole, song)

View File

@ -534,11 +534,11 @@ def delete_song(song_id, song_plugin):
try: try:
os.remove(media_file.file_name) os.remove(media_file.file_name)
except OSError: except OSError:
log.exception('Could not remove file: %s', media_file.file_name) log.exception('Could not remove file: {name}'.format(name=media_file.file_name))
try: try:
save_path = os.path.join(AppLocation.get_section_data_path(song_plugin.name), 'audio', str(song_id)) save_path = os.path.join(AppLocation.get_section_data_path(song_plugin.name), 'audio', str(song_id))
if os.path.exists(save_path): if os.path.exists(save_path):
os.rmdir(save_path) os.rmdir(save_path)
except OSError: except OSError:
log.exception('Could not remove directory: %s', save_path) log.exception('Could not remove directory: {path}'.format(path=save_path))
song_plugin.manager.delete_object(Song, song_id) song_plugin.manager.delete_object(Song, song_id)

View File

@ -39,7 +39,7 @@ class Author(BaseModel):
""" """
def get_display_name(self, author_type=None): def get_display_name(self, author_type=None):
if author_type: if author_type:
return "%s (%s)" % (self.display_name, AuthorType.Types[author_type]) return "{name} ({author})".format(name=self.display_name, author=AuthorType.Types[author_type])
return self.display_name return self.display_name
@ -105,7 +105,9 @@ class Book(BaseModel):
Book model Book model
""" """
def __repr__(self): def __repr__(self):
return '<Book id="%s" name="%s" publisher="%s" />' % (str(self.id), self.name, self.publisher) return '<Book id="{myid:d}" name="{name}" publisher="{publisher}" />'.format(myid=self.id,
name=self.name,
publisher=self.publisher)
class MediaFile(BaseModel): class MediaFile(BaseModel):
@ -187,7 +189,7 @@ class SongBookEntry(BaseModel):
@staticmethod @staticmethod
def get_display_name(songbook_name, entry): def get_display_name(songbook_name, entry):
if entry: if entry:
return "%s #%s" % (songbook_name, entry) return "{name} #{entry}".format(name=songbook_name, entry=entry)
return songbook_name return songbook_name

View File

@ -56,13 +56,13 @@ try:
from .importers.songsoffellowship import SongsOfFellowshipImport from .importers.songsoffellowship import SongsOfFellowshipImport
HAS_SOF = True HAS_SOF = True
except ImportError: except ImportError:
log.exception('Error importing %s', 'SongsOfFellowshipImport') log.exception('Error importing {text}'.format(text='SongsOfFellowshipImport'))
HAS_SOF = False HAS_SOF = False
try: try:
from .importers.openoffice import OpenOfficeImport from .importers.openoffice import OpenOfficeImport
HAS_OOO = True HAS_OOO = True
except ImportError: except ImportError:
log.exception('Error importing %s', 'OooImport') log.exception('Error importing {text}'.format(text='OooImport'))
HAS_OOO = False HAS_OOO = False
HAS_MEDIASHOUT = False HAS_MEDIASHOUT = False
if is_win(): if is_win():
@ -70,21 +70,21 @@ if is_win():
from .importers.mediashout import MediaShoutImport from .importers.mediashout import MediaShoutImport
HAS_MEDIASHOUT = True HAS_MEDIASHOUT = True
except ImportError: except ImportError:
log.exception('Error importing %s', 'MediaShoutImport') log.exception('Error importing {text}'.format(text='MediaShoutImport'))
HAS_WORSHIPCENTERPRO = False HAS_WORSHIPCENTERPRO = False
if is_win(): if is_win():
try: try:
from .importers.worshipcenterpro import WorshipCenterProImport from .importers.worshipcenterpro import WorshipCenterProImport
HAS_WORSHIPCENTERPRO = True HAS_WORSHIPCENTERPRO = True
except ImportError: except ImportError:
log.exception('Error importing %s', 'WorshipCenterProImport') log.exception('Error importing {text}'.format(text='WorshipCenterProImport'))
HAS_OPSPRO = False HAS_OPSPRO = False
if is_win(): if is_win():
try: try:
from .importers.opspro import OPSProImport from .importers.opspro import OPSProImport
HAS_OPSPRO = True HAS_OPSPRO = True
except ImportError: except ImportError:
log.exception('Error importing %s', 'OPSProImport') log.exception('Error importing {text}'.format(text='OPSProImport'))
class SongFormatSelect(object): class SongFormatSelect(object):
@ -198,7 +198,7 @@ class SongFormat(object):
'class': OpenLyricsImport, 'class': OpenLyricsImport,
'name': 'OpenLyrics', 'name': 'OpenLyrics',
'prefix': 'openLyrics', 'prefix': 'openLyrics',
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files'), 'filter': '{text} (*.xml)'.format(text=translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files')),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'OpenLyrics or OpenLP 2 Exported Song') 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'OpenLyrics or OpenLP 2 Exported Song')
}, },
OpenLP2: { OpenLP2: {
@ -206,7 +206,7 @@ class SongFormat(object):
'name': UiStrings().OLPV2, 'name': UiStrings().OLPV2,
'prefix': 'openLP2', 'prefix': 'openLP2',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.sqlite)' % (translate('SongsPlugin.ImportWizardForm', 'OpenLP 2 Databases')) 'filter': '{text} (*.sqlite)'.format(text=translate('SongsPlugin.ImportWizardForm', 'OpenLP 2 Databases'))
}, },
Generic: { Generic: {
'name': translate('SongsPlugin.ImportWizardForm', 'Generic Document/Presentation'), 'name': translate('SongsPlugin.ImportWizardForm', 'Generic Document/Presentation'),
@ -221,46 +221,50 @@ class SongFormat(object):
'class': CCLIFileImport, 'class': CCLIFileImport,
'name': 'CCLI/SongSelect', 'name': 'CCLI/SongSelect',
'prefix': 'ccli', 'prefix': 'ccli',
'filter': '%s (*.usr *.txt *.bin)' % translate('SongsPlugin.ImportWizardForm', 'CCLI SongSelect Files') 'filter': '{text} (*.usr *.txt *.bin)'.format(text=translate('SongsPlugin.ImportWizardForm',
'CCLI SongSelect Files'))
}, },
DreamBeam: { DreamBeam: {
'class': DreamBeamImport, 'class': DreamBeamImport,
'name': 'DreamBeam', 'name': 'DreamBeam',
'prefix': 'dreamBeam', 'prefix': 'dreamBeam',
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'DreamBeam Song Files') 'filter': '{text} (*.xml)'.format(text=translate('SongsPlugin.ImportWizardForm', 'DreamBeam Song Files'))
}, },
EasySlides: { EasySlides: {
'class': EasySlidesImport, 'class': EasySlidesImport,
'name': 'EasySlides', 'name': 'EasySlides',
'prefix': 'easySlides', 'prefix': 'easySlides',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'EasySlides XML File') 'filter': '{text} (*.xml)'.format(text=translate('SongsPlugin.ImportWizardForm', 'EasySlides XML File'))
}, },
EasyWorshipDB: { EasyWorshipDB: {
'class': EasyWorshipSongImport, 'class': EasyWorshipSongImport,
'name': 'EasyWorship Song Database', 'name': 'EasyWorship Song Database',
'prefix': 'ew', 'prefix': 'ew',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.db)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Song Database') 'filter': '{text} (*.db)'.format(text=translate('SongsPlugin.ImportWizardForm',
'EasyWorship Song Database'))
}, },
EasyWorshipService: { EasyWorshipService: {
'class': EasyWorshipSongImport, 'class': EasyWorshipSongImport,
'name': 'EasyWorship Service File', 'name': 'EasyWorship Service File',
'prefix': 'ew', 'prefix': 'ew',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.ews)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Service File') 'filter': '{text} (*.ews)'.format(text=translate('SongsPlugin.ImportWizardForm',
'EasyWorship Service File'))
}, },
FoilPresenter: { FoilPresenter: {
'class': FoilPresenterImport, 'class': FoilPresenterImport,
'name': 'Foilpresenter', 'name': 'Foilpresenter',
'prefix': 'foilPresenter', 'prefix': 'foilPresenter',
'filter': '%s (*.foil)' % translate('SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files') 'filter': '{text} (*.foil)'.format(text=translate('SongsPlugin.ImportWizardForm',
'Foilpresenter Song Files'))
}, },
Lyrix: { Lyrix: {
'class': LyrixImport, 'class': LyrixImport,
'name': 'LyriX', 'name': 'LyriX',
'prefix': 'lyrix', 'prefix': 'lyrix',
'filter': '%s (*.txt)' % translate('SongsPlugin.ImportWizardForm', 'LyriX Files'), 'filter': '{text} (*.txt)'.format(text=translate('SongsPlugin.ImportWizardForm', 'LyriX Files')),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'LyriX (Exported TXT-files)') 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'LyriX (Exported TXT-files)')
}, },
MediaShout: { MediaShout: {
@ -268,7 +272,7 @@ class SongFormat(object):
'prefix': 'mediaShout', 'prefix': 'mediaShout',
'canDisable': True, 'canDisable': True,
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'MediaShout Database'), 'filter': '{text} (*.mdb)'.format(text=translate('SongsPlugin.ImportWizardForm', 'MediaShout Database')),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm', 'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The MediaShout importer is only supported on Windows. It has ' 'The MediaShout importer is only supported on Windows. It has '
'been disabled due to a missing Python module. If you want to ' 'been disabled due to a missing Python module. If you want to '
@ -285,7 +289,7 @@ class SongFormat(object):
'prefix': 'OPSPro', 'prefix': 'OPSPro',
'canDisable': True, 'canDisable': True,
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'OPS Pro database'), 'filter': '{text} (*.mdb)'.format(text=translate('SongsPlugin.ImportWizardForm', 'OPS Pro database')),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm', 'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The OPS Pro importer is only supported on Windows. It has been ' 'The OPS Pro importer is only supported on Windows. It has been '
'disabled due to a missing Python module. If you want to use this ' 'disabled due to a missing Python module. If you want to use this '
@ -295,7 +299,7 @@ class SongFormat(object):
'class': PowerPraiseImport, 'class': PowerPraiseImport,
'name': 'PowerPraise', 'name': 'PowerPraise',
'prefix': 'powerPraise', 'prefix': 'powerPraise',
'filter': '%s (*.ppl)' % translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files') 'filter': '{text} (*.ppl)'.format(text=translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files'))
}, },
PowerSong: { PowerSong: {
'class': PowerSongImport, 'class': PowerSongImport,
@ -309,26 +313,29 @@ class SongFormat(object):
'class': PresentationManagerImport, 'class': PresentationManagerImport,
'name': 'PresentationManager', 'name': 'PresentationManager',
'prefix': 'presentationManager', 'prefix': 'presentationManager',
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'PresentationManager Song Files') 'filter': '{text} (*.sng)'.format(text=translate('SongsPlugin.ImportWizardForm',
'PresentationManager Song Files'))
}, },
ProPresenter: { ProPresenter: {
'class': ProPresenterImport, 'class': ProPresenterImport,
'name': 'ProPresenter 4, 5 and 6', 'name': 'ProPresenter 4, 5 and 6',
'prefix': 'proPresenter', 'prefix': 'proPresenter',
'filter': '%s (*.pro4 *.pro5 *.pro6)' % translate('SongsPlugin.ImportWizardForm', 'ProPresenter Song Files') 'filter': '{text} (*.pro4 *.pro5 *.pro6)'.format(text=translate('SongsPlugin.ImportWizardForm',
'ProPresenter Song Files'))
}, },
SongBeamer: { SongBeamer: {
'class': SongBeamerImport, 'class': SongBeamerImport,
'name': 'SongBeamer', 'name': 'SongBeamer',
'prefix': 'songBeamer', 'prefix': 'songBeamer',
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'SongBeamer Files') 'filter': '{text} (*.sng)'.format(text=translate('SongsPlugin.ImportWizardForm',
'SongBeamer Files'))
}, },
SongPro: { SongPro: {
'class': SongProImport, 'class': SongProImport,
'name': 'SongPro', 'name': 'SongPro',
'prefix': 'songPro', 'prefix': 'songPro',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.txt)' % translate('SongsPlugin.ImportWizardForm', 'SongPro Text Files'), 'filter': '{text} (*.txt)'.format(text=translate('SongsPlugin.ImportWizardForm', 'SongPro Text Files')),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'SongPro (Export File)'), 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'SongPro (Export File)'),
'descriptionText': translate('SongsPlugin.ImportWizardForm', 'descriptionText': translate('SongsPlugin.ImportWizardForm',
'In SongPro, export your songs using the File -> Export menu') 'In SongPro, export your songs using the File -> Export menu')
@ -337,13 +344,15 @@ class SongFormat(object):
'class': SongShowPlusImport, 'class': SongShowPlusImport,
'name': 'SongShow Plus', 'name': 'SongShow Plus',
'prefix': 'songShowPlus', 'prefix': 'songShowPlus',
'filter': '%s (*.sbsong)' % translate('SongsPlugin.ImportWizardForm', 'SongShow Plus Song Files') 'filter': '{text} (*.sbsong)'.format(text=translate('SongsPlugin.ImportWizardForm',
'SongShow Plus Song Files'))
}, },
SongsOfFellowship: { SongsOfFellowship: {
'name': 'Songs of Fellowship', 'name': 'Songs of Fellowship',
'prefix': 'songsOfFellowship', 'prefix': 'songsOfFellowship',
'canDisable': True, 'canDisable': True,
'filter': '%s (*.rtf)' % translate('SongsPlugin.ImportWizardForm', 'Songs Of Fellowship Song Files'), 'filter': '{text} (*.rtf)'.format(text=translate('SongsPlugin.ImportWizardForm',
'Songs Of Fellowship Song Files')),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm', 'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The Songs of Fellowship importer has been disabled because ' 'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.') 'OpenLP cannot access OpenOffice or LibreOffice.')
@ -352,30 +361,33 @@ class SongFormat(object):
'class': SundayPlusImport, 'class': SundayPlusImport,
'name': 'SundayPlus', 'name': 'SundayPlus',
'prefix': 'sundayPlus', 'prefix': 'sundayPlus',
'filter': '%s (*.ptf)' % translate('SongsPlugin.ImportWizardForm', 'SundayPlus Song Files') 'filter': '{text} (*.ptf)'.format(text=translate('SongsPlugin.ImportWizardForm', 'SundayPlus Song Files'))
}, },
VideoPsalm: { VideoPsalm: {
'class': VideoPsalmImport, 'class': VideoPsalmImport,
'name': 'VideoPsalm', 'name': 'VideoPsalm',
'prefix': 'videopsalm', 'prefix': 'videopsalm',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.json)' % translate('SongsPlugin.ImportWizardForm', 'VideoPsalm Files'), 'filter': '{text} (*.json)'.format(text=translate('SongsPlugin.ImportWizardForm', 'VideoPsalm Files')),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'VideoPsalm'), 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'VideoPsalm'),
'descriptionText': translate('SongsPlugin.ImportWizardForm', 'The VideoPsalm songbooks are normally located' 'descriptionText': translate('SongsPlugin.ImportWizardForm', 'The VideoPsalm songbooks are normally located'
' in %s') % 'C:\\Users\\Public\\Documents\\VideoPsalm\\SongBooks\\' ' in {path}').format(path='C:\\Users\\Public\\Documents\\VideoPsalm'
'\\SongBooks\\')
}, },
WordsOfWorship: { WordsOfWorship: {
'class': WordsOfWorshipImport, 'class': WordsOfWorshipImport,
'name': 'Words of Worship', 'name': 'Words of Worship',
'prefix': 'wordsOfWorship', 'prefix': 'wordsOfWorship',
'filter': '%s (*.wsg *.wow-song)' % translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files') 'filter': '{text} (*.wsg *.wow-song)'.format(text=translate('SongsPlugin.ImportWizardForm',
'Words Of Worship Song Files'))
}, },
WorshipAssistant: { WorshipAssistant: {
'class': WorshipAssistantImport, 'class': WorshipAssistantImport,
'name': 'Worship Assistant 0', 'name': 'Worship Assistant 0',
'prefix': 'worshipAssistant', 'prefix': 'worshipAssistant',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.csv)' % translate('SongsPlugin.ImportWizardForm', 'Worship Assistant Files'), 'filter': '{text} (*.csv)'.format(text=translate('SongsPlugin.ImportWizardForm',
'Worship Assistant Files')),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'Worship Assistant (CSV)'), 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'Worship Assistant (CSV)'),
'descriptionText': translate('SongsPlugin.ImportWizardForm', 'descriptionText': translate('SongsPlugin.ImportWizardForm',
'In Worship Assistant, export your Database to a CSV file.') 'In Worship Assistant, export your Database to a CSV file.')
@ -385,7 +397,8 @@ class SongFormat(object):
'prefix': 'worshipCenterPro', 'prefix': 'worshipCenterPro',
'canDisable': True, 'canDisable': True,
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'WorshipCenter Pro Song Files'), 'filter': '{text} (*.mdb)'.format(text=translate('SongsPlugin.ImportWizardForm',
'WorshipCenter Pro Song Files')),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm', 'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The WorshipCenter Pro importer is only supported on Windows. It has been ' 'The WorshipCenter Pro importer is only supported on Windows. It has been '
'disabled due to a missing Python module. If you want to use this ' 'disabled due to a missing Python module. If you want to use this '

View File

@ -58,7 +58,7 @@ class CCLIFileImport(SongImport):
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for filename in self.import_source:
filename = str(filename) filename = str(filename)
log.debug('Importing CCLI File: %s', filename) log.debug('Importing CCLI File: {name}'.format(name=filename))
if os.path.isfile(filename): if os.path.isfile(filename):
detect_file = open(filename, 'rb') detect_file = open(filename, 'rb')
detect_content = detect_file.read(2048) detect_content = detect_file.read(2048)
@ -76,17 +76,17 @@ class CCLIFileImport(SongImport):
infile.close() infile.close()
ext = os.path.splitext(filename)[1] ext = os.path.splitext(filename)[1]
if ext.lower() == '.usr' or ext.lower() == '.bin': if ext.lower() == '.usr' or ext.lower() == '.bin':
log.info('SongSelect USR format file found: %s', filename) log.info('SongSelect USR format file found: {name}'.format(name=filename))
if not self.do_import_usr_file(lines): if not self.do_import_usr_file(lines):
self.log_error(filename) self.log_error(filename)
elif ext.lower() == '.txt': elif ext.lower() == '.txt':
log.info('SongSelect TEXT format file found: %s', filename) log.info('SongSelect TEXT format file found: {name}'.format(name=filename))
if not self.do_import_txt_file(lines): if not self.do_import_txt_file(lines):
self.log_error(filename) self.log_error(filename)
else: else:
self.log_error(filename, translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid ' self.log_error(filename, translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid '
'extension.')) 'extension.'))
log.info('Extension %s is not valid', filename) log.info('Extension {name} is not valid'.format(name=filename))
if self.stop_import_flag: if self.stop_import_flag:
return return
@ -146,7 +146,7 @@ class CCLIFileImport(SongImport):
:param text_list: An array of strings containing the usr file content. :param text_list: An array of strings containing the usr file content.
""" """
log.debug('USR file text: %s', text_list) log.debug('USR file text: {text}'.format(text=text_list))
song_author = '' song_author = ''
song_topics = '' song_topics = ''
for line in text_list: for line in text_list:
@ -193,7 +193,7 @@ class CCLIFileImport(SongImport):
if check_first_verse_line: if check_first_verse_line:
if verse_lines[0].startswith('(PRE-CHORUS'): if verse_lines[0].startswith('(PRE-CHORUS'):
verse_type = VerseType.tags[VerseType.PreChorus] verse_type = VerseType.tags[VerseType.PreChorus]
log.debug('USR verse PRE-CHORUS: %s', verse_lines[0]) log.debug('USR verse PRE-CHORUS: {lines}'.format(lines=verse_lines[0]))
verse_text = verse_lines[1] verse_text = verse_lines[1]
elif verse_lines[0].startswith('(BRIDGE'): elif verse_lines[0].startswith('(BRIDGE'):
verse_type = VerseType.tags[VerseType.Bridge] verse_type = VerseType.tags[VerseType.Bridge]
@ -248,7 +248,7 @@ class CCLIFileImport(SongImport):
# e.g. CCLI-Liedlizenznummer: 14 / CCLI License No. 14 # e.g. CCLI-Liedlizenznummer: 14 / CCLI License No. 14
""" """
log.debug('TXT file text: %s', text_list) log.debug('TXT file text: {text}'.format(text=text_list))
line_number = 0 line_number = 0
check_first_verse_line = False check_first_verse_line = False
verse_text = '' verse_text = ''

View File

@ -90,7 +90,7 @@ class DreamBeamImport(SongImport):
try: try:
parsed_file = etree.parse(open(file, 'r'), parser) parsed_file = etree.parse(open(file, 'r'), parser)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
log.exception('XML syntax error in file %s' % file) log.exception('XML syntax error in file {name}'.format(name=file))
self.log_error(file, SongStrings.XMLSyntaxError) self.log_error(file, SongStrings.XMLSyntaxError)
continue continue
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
@ -115,15 +115,17 @@ class DreamBeamImport(SongImport):
verse_type = lyrics_item.get('Type') verse_type = lyrics_item.get('Type')
verse_number = lyrics_item.get('Number') verse_number = lyrics_item.get('Number')
verse_text = str(lyrics_item.text) verse_text = str(lyrics_item.text)
self.add_verse(verse_text, ('%s%s' % (verse_type[:1], verse_number))) self.add_verse(verse_text,
'{verse}{number}'.format(verse=verse_type[:1], number=verse_number))
if hasattr(song_xml, 'Collection'): if hasattr(song_xml, 'Collection'):
self.song_book_name = str(song_xml.Collection.text) self.song_book_name = str(song_xml.Collection.text)
if hasattr(song_xml, 'Number'): if hasattr(song_xml, 'Number'):
self.song_number = str(song_xml.Number.text) self.song_number = str(song_xml.Number.text)
if hasattr(song_xml, 'Sequence'): if hasattr(song_xml, 'Sequence'):
for lyrics_sequence_item in (song_xml.Sequence.iterchildren()): for lyrics_sequence_item in (song_xml.Sequence.iterchildren()):
self.verse_order_list.append("%s%s" % (lyrics_sequence_item.get('Type')[:1], item = lyrics_sequence_item.get('Type')[:1]
lyrics_sequence_item.get('Number'))) self.verse_order_list.append("{item}{number}".format(item=item),
lyrics_sequence_item.get('Number'))
if hasattr(song_xml, 'Notes'): if hasattr(song_xml, 'Notes'):
self.comments = str(song_xml.Notes.text) self.comments = str(song_xml.Notes.text)
else: else:

View File

@ -45,7 +45,7 @@ class EasySlidesImport(SongImport):
super(EasySlidesImport, self).__init__(manager, **kwargs) super(EasySlidesImport, self).__init__(manager, **kwargs)
def do_import(self): def do_import(self):
log.info('Importing EasySlides XML file %s', self.import_source) log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
parser = etree.XMLParser(remove_blank_text=True) parser = etree.XMLParser(remove_blank_text=True)
parsed_file = etree.parse(self.import_source, parser) parsed_file = etree.parse(self.import_source, parser)
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
@ -96,10 +96,10 @@ class EasySlidesImport(SongImport):
try: try:
setattr(self, self_attribute, str(import_attribute).strip()) setattr(self, self_attribute, str(import_attribute).strip())
except UnicodeDecodeError: except UnicodeDecodeError:
log.exception('UnicodeDecodeError decoding %s' % import_attribute) log.exception('UnicodeDecodeError decoding {attribute}'.format(attribute=import_attribute))
self._success = False self._success = False
except AttributeError: except AttributeError:
log.exception('No attribute %s' % import_attribute) log.exception('No attribute {attribute}'.format(attribute=import_attribute))
if mandatory: if mandatory:
self._success = False self._success = False
@ -119,7 +119,7 @@ class EasySlidesImport(SongImport):
try: try:
self.add_copyright(str(element).strip()) self.add_copyright(str(element).strip())
except UnicodeDecodeError: except UnicodeDecodeError:
log.exception('Unicode error on decoding copyright: %s' % element) log.exception('Unicode error on decoding copyright: {element}'.format(element=element))
self._success = False self._success = False
except AttributeError: except AttributeError:
pass pass
@ -157,9 +157,10 @@ class EasySlidesImport(SongImport):
separators = (separator_lines > 0) separators = (separator_lines > 0)
# the number of different regions in song - 1 # the number of different regions in song - 1
if len(region_lines) > 1: if len(region_lines) > 1:
log.info('EasySlidesImport: the file contained a song named "%s"' log.info('EasySlidesImport: the file contained a song named "{title}"'
'with more than two regions, but only two regions are tested, encountered regions were: %s', 'with more than two regions, but only two regions are tested, '
self.title, ','.join(list(region_lines.keys()))) 'encountered regions were: {keys}'.format(title=self.title,
keys=','.join(list(region_lines.keys()))))
# if the song has regions # if the song has regions
regions = (len(region_lines) > 0) regions = (len(region_lines) > 0)
# if the regions are inside verses # if the regions are inside verses
@ -232,7 +233,7 @@ class EasySlidesImport(SongImport):
for [reg, vt, vn, inst] in our_verse_order: for [reg, vt, vn, inst] in our_verse_order:
if self._list_has(verses, [reg, vt, vn, inst]): if self._list_has(verses, [reg, vt, vn, inst]):
# this is false, but needs user input # this is false, but needs user input
versetag = '%s%s' % (vt, vn) versetag = '{tag}{number}'.format(tag=vt, number=vn)
versetags.append(versetag) versetags.append(versetag)
lines = '\n'.join(verses[reg][vt][vn][inst]) lines = '\n'.join(verses[reg][vt][vn][inst])
self.add_verse(lines, versetag) self.add_verse(lines, versetag)
@ -259,7 +260,8 @@ class EasySlidesImport(SongImport):
if tag in versetags: if tag in versetags:
self.verse_order_list.append(tag) self.verse_order_list.append(tag)
else: else:
log.info('Got order item %s, which is not in versetags, dropping item from presentation order', tag) log.info('Got order item {tag}, which is not in versetags, dropping item from presentation '
'order'.format(tag=tag))
except UnicodeDecodeError: except UnicodeDecodeError:
log.exception('Unicode decode error while decoding Sequence') log.exception('Unicode decode error while decoding Sequence')
self._success = False self._success = False

View File

@ -171,15 +171,16 @@ class EasyWorshipSongImport(SongImport):
if copyright: if copyright:
self.copyright += ', ' self.copyright += ', '
self.copyright += translate('SongsPlugin.EasyWorshipSongImport', self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s') % admin 'Administered by {admin}').format(admin=admin)
# Set the SongImport object members. # Set the SongImport object members.
self.set_song_import_object(authors, inflated_content) self.set_song_import_object(authors, inflated_content)
if self.stop_import_flag: if self.stop_import_flag:
break break
if self.entry_error_log: if self.entry_error_log:
self.log_error(self.import_source, self.log_error(self.import_source,
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s') translate('SongsPlugin.EasyWorshipSongImport',
% (self.title, self.entry_error_log)) '"{title}" could not be imported. {entry}').format(title=self.title,
entry=self.entry_error_log))
self.entry_error_log = '' self.entry_error_log = ''
elif not self.finish(): elif not self.finish():
self.log_error(self.import_source) self.log_error(self.import_source)
@ -306,7 +307,7 @@ class EasyWorshipSongImport(SongImport):
if copy: if copy:
self.copyright += ', ' self.copyright += ', '
self.copyright += translate('SongsPlugin.EasyWorshipSongImport', self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s') % admin.decode(self.encoding) 'Administered by {admin}').format(admin=admin.decode(self.encoding))
if ccli: if ccli:
self.ccli_number = ccli.decode(self.encoding) self.ccli_number = ccli.decode(self.encoding)
if authors: if authors:
@ -319,15 +320,17 @@ class EasyWorshipSongImport(SongImport):
break break
if self.entry_error_log: if self.entry_error_log:
self.log_error(self.import_source, self.log_error(self.import_source,
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s') translate('SongsPlugin.EasyWorshipSongImport',
% (self.title, self.entry_error_log)) '"{title}" could not be imported. '
'{entry}').format(title=self.title, entry=self.entry_error_log))
self.entry_error_log = '' self.entry_error_log = ''
elif not self.finish(): elif not self.finish():
self.log_error(self.import_source) self.log_error(self.import_source)
except Exception as e: except Exception as e:
self.log_error(self.import_source, self.log_error(self.import_source,
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s') translate('SongsPlugin.EasyWorshipSongImport',
% (self.title, e)) '"{title}" could not be imported. {error}').format(title=self.title,
error=e))
db_file.close() db_file.close()
self.memo_file.close() self.memo_file.close()
@ -421,7 +424,7 @@ class EasyWorshipSongImport(SongImport):
fsl = ['>'] fsl = ['>']
for field_desc in field_descriptions: for field_desc in field_descriptions:
if field_desc.field_type == FieldType.String: if field_desc.field_type == FieldType.String:
fsl.append('%ds' % field_desc.size) fsl.append('{size:d}s'.format(size=field_desc.size))
elif field_desc.field_type == FieldType.Int16: elif field_desc.field_type == FieldType.Int16:
fsl.append('H') fsl.append('H')
elif field_desc.field_type == FieldType.Int32: elif field_desc.field_type == FieldType.Int32:
@ -429,13 +432,13 @@ class EasyWorshipSongImport(SongImport):
elif field_desc.field_type == FieldType.Logical: elif field_desc.field_type == FieldType.Logical:
fsl.append('B') fsl.append('B')
elif field_desc.field_type == FieldType.Memo: elif field_desc.field_type == FieldType.Memo:
fsl.append('%ds' % field_desc.size) fsl.append('{size:d}s'.format(size=field_desc.size))
elif field_desc.field_type == FieldType.Blob: elif field_desc.field_type == FieldType.Blob:
fsl.append('%ds' % field_desc.size) fsl.append('{size:d}s'.format(size=field_desc.size))
elif field_desc.field_type == FieldType.Timestamp: elif field_desc.field_type == FieldType.Timestamp:
fsl.append('Q') fsl.append('Q')
else: else:
fsl.append('%ds' % field_desc.size) fsl.append('{size:d}s'.format(size=field_desc.size))
self.record_structure = struct.Struct(''.join(fsl)) self.record_structure = struct.Struct(''.join(fsl))
self.field_descriptions = field_descriptions self.field_descriptions = field_descriptions

View File

@ -121,6 +121,7 @@ class FoilPresenterImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
try: try:
parsed_file = etree.parse(file_path, parser) parsed_file = etree.parse(file_path, parser)
@ -128,7 +129,7 @@ class FoilPresenterImport(SongImport):
self.foil_presenter.xml_to_song(xml) self.foil_presenter.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
self.log_error(file_path, SongStrings.XMLSyntaxError) self.log_error(file_path, SongStrings.XMLSyntaxError)
log.exception('XML syntax error in file %s' % file_path) log.exception('XML syntax error in file {path}'.format(path=file_path))
class FoilPresenter(object): class FoilPresenter(object):

View File

@ -102,8 +102,8 @@ class LyrixImport(SongImport):
else: else:
current_verse += '\n' + line current_verse += '\n' + line
except Exception as e: except Exception as e:
self.log_error(translate('SongsPlugin.LyrixImport', 'File %s' % file.name), self.log_error(translate('SongsPlugin.LyrixImport', 'File {name}').format(name=file.name),
translate('SongsPlugin.LyrixImport', 'Error: %s') % e) translate('SongsPlugin.LyrixImport', 'Error: {error}').format(error=e))
return return
self.title = song_title self.title = song_title
self.parse_author(author) self.parse_author(author)

View File

@ -23,6 +23,10 @@
The :mod:`mediashout` module provides the functionality for importing The :mod:`mediashout` module provides the functionality for importing
a MediaShout database into the OpenLP database. a MediaShout database into the OpenLP database.
""" """
# WARNING: See https://docs.python.org/2/library/sqlite3.html for value substitution
# in SQL statements
import pyodbc import pyodbc
from openlp.core.lib import translate from openlp.core.lib import translate
@ -47,8 +51,8 @@ class MediaShoutImport(SongImport):
Receive a single file to import. Receive a single file to import.
""" """
try: try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=6NOZ4eHK7k' % conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
self.import_source) 'PWD=6NOZ4eHK7k'.format(sorce=self.import_source))
except: except:
# Unfortunately no specific exception type # Unfortunately no specific exception type
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport', self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
@ -61,16 +65,15 @@ class MediaShoutImport(SongImport):
for song in songs: for song in songs:
if self.stop_import_flag: if self.stop_import_flag:
break break
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = %s ORDER BY Type, Number' cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', song.Record)
% song.Record)
verses = cursor.fetchall() verses = cursor.fetchall()
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = %s ORDER BY POrder' % song.Record) cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', song.Record)
verse_order = cursor.fetchall() verse_order = cursor.fetchall()
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId ' cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
'WHERE SongThemes.Record = %s' % song.Record) 'WHERE SongThemes.Record = ?', song.Record)
topics = cursor.fetchall() topics = cursor.fetchall()
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId ' cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
'WHERE SongGroups.Record = %s' % song.Record) 'WHERE SongGroups.Record = ?', song.Record)
topics += cursor.fetchall() topics += cursor.fetchall()
self.process_song(song, verses, verse_order, topics) self.process_song(song, verses, verse_order, topics)

View File

@ -102,7 +102,7 @@ class OpenLPSongImport(SongImport):
self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport', self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',
'Not a valid OpenLP 2 song database.')) 'Not a valid OpenLP 2 song database.'))
return return
self.import_source = 'sqlite:///%s' % self.import_source self.import_source = 'sqlite:///{url}'.format(url=self.import_source)
# Load the db file and reflect it # Load the db file and reflect it
engine = create_engine(self.import_source) engine = create_engine(self.import_source)
source_meta = MetaData() source_meta = MetaData()
@ -239,8 +239,10 @@ class OpenLPSongImport(SongImport):
self.manager.save_object(new_song) self.manager.save_object(new_song)
if progress_dialog: if progress_dialog:
progress_dialog.setValue(progress_dialog.value() + 1) progress_dialog.setValue(progress_dialog.value() + 1)
# TODO: Verify format() with template strings
progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title) progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title)
else: else:
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
if self.stop_import_flag: if self.stop_import_flag:
break break

View File

@ -58,6 +58,7 @@ class OpenLyricsImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
try: try:
# Pass a file object, because lxml does not cope with some # Pass a file object, because lxml does not cope with some
@ -66,9 +67,10 @@ class OpenLyricsImport(SongImport):
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
self.open_lyrics.xml_to_song(xml) self.open_lyrics.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
log.exception('XML syntax error in file %s' % file_path) log.exception('XML syntax error in file {path}'.format(file_path))
self.log_error(file_path, SongStrings.XMLSyntaxError) self.log_error(file_path, SongStrings.XMLSyntaxError)
except OpenLyricsError as exception: except OpenLyricsError as exception:
log.exception('OpenLyricsException %d in file %s: %s' % log.exception('OpenLyricsException {error:d} in file {name}: {text}'.format(error=exception.type,
(exception.type, file_path, exception.log_message)) name=file_path,
text=exception.log_message))
self.log_error(file_path, exception.display_message) self.log_error(file_path, exception.display_message)

View File

@ -161,7 +161,7 @@ class OpenOfficeImport(SongImport):
else: else:
self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0) self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0)
except AttributeError: except AttributeError:
log.exception("open_ooo_file failed: %s", url) log.exception("open_ooo_file failed: {url}".format(url=url))
return return
def create_property(self, name, value): def create_property(self, name, value):

View File

@ -254,8 +254,8 @@ class OpenSongImport(SongImport):
length = 0 length = 0
while length < len(verse_num) and verse_num[length].isnumeric(): while length < len(verse_num) and verse_num[length].isnumeric():
length += 1 length += 1
verse_def = '%s%s' % (verse_tag, verse_num[:length]) verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num[:length])
verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \ verse_joints[verse_def] = '{verse}\n[---]\n{lines}'.format(verse=verse_joints[verse_def], lines=lines) \
if verse_def in verse_joints else lines if verse_def in verse_joints else lines
# Parsing the dictionary produces the elements in a non-intuitive order. While it "works", it's not a # Parsing the dictionary produces the elements in a non-intuitive order. While it "works", it's not a
# natural layout should the user come back to edit the song. Instead we sort by the verse type, so that we # natural layout should the user come back to edit the song. Instead we sort by the verse type, so that we
@ -287,11 +287,11 @@ class OpenSongImport(SongImport):
verse_num = '1' verse_num = '1'
verse_index = VerseType.from_loose_input(verse_tag) verse_index = VerseType.from_loose_input(verse_tag)
verse_tag = VerseType.tags[verse_index] verse_tag = VerseType.tags[verse_index]
verse_def = '%s%s' % (verse_tag, verse_num) verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num)
if verse_num in verses.get(verse_tag, {}): if verse_num in verses.get(verse_tag, {}):
self.verse_order_list.append(verse_def) self.verse_order_list.append(verse_def)
else: else:
log.info('Got order %s but not in verse tags, dropping this item from presentation order', log.info('Got order {order} but not in verse tags, dropping this item from presentation '
verse_def) 'order'.format(order=verse_def))
if not self.finish(): if not self.finish():
self.log_error(file.name) self.log_error(file.name)

View File

@ -23,6 +23,10 @@
The :mod:`opspro` module provides the functionality for importing The :mod:`opspro` module provides the functionality for importing
a OPS Pro database into the OpenLP database. a OPS Pro database into the OpenLP database.
""" """
# WARNING: See https://docs.python.org/2/library/sqlite3.html for value substitution
# in SQL statements
import logging import logging
import re import re
import pyodbc import pyodbc
@ -51,10 +55,11 @@ class OPSProImport(SongImport):
""" """
password = self.extract_mdb_password() password = self.extract_mdb_password()
try: try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=%s' % (self.import_source, conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
password)) 'PWD={password}'.format(source=self.import_source, password=password))
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e: except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
log.warning('Unable to connect the OPS Pro database %s. %s', self.import_source, str(e)) log.warning('Unable to connect the OPS Pro database {source}. {error}'.format(source=self.import_source,
error=str(e)))
# Unfortunately no specific exception type # Unfortunately no specific exception type
self.log_error(self.import_source, translate('SongsPlugin.OPSProImport', self.log_error(self.import_source, translate('SongsPlugin.OPSProImport',
'Unable to connect the OPS Pro database.')) 'Unable to connect the OPS Pro database.'))
@ -68,19 +73,19 @@ class OPSProImport(SongImport):
if self.stop_import_flag: if self.stop_import_flag:
break break
# Type means: 0=Original, 1=Projection, 2=Own # Type means: 0=Original, 1=Projection, 2=Own
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = %d AND Type < 2 ' cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = ? AND Type < 2 '
'ORDER BY Type DESC' % song.ID) 'ORDER BY Type DESC', song.ID)
lyrics = cursor.fetchone() lyrics = cursor.fetchone()
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory ' cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = %d ' 'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = ? '
'ORDER BY CategoryName' % song.ID) 'ORDER BY CategoryName', song.ID)
topics = cursor.fetchall() topics = cursor.fetchall()
try: try:
self.process_song(song, lyrics, topics) self.process_song(song, lyrics, topics)
except Exception as e: except Exception as e:
self.log_error(self.import_source, self.log_error(self.import_source,
translate('SongsPlugin.OPSProImport', '"%s" could not be imported. %s') translate('SongsPlugin.OPSProImport',
% (song.Title, e)) '"{title}" could not be imported. {error}').format(title=song.Title, error=e))
def process_song(self, song, lyrics, topics): def process_song(self, song, lyrics, topics):
""" """

View File

@ -41,6 +41,7 @@ class PowerPraiseImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
root = objectify.parse(open(file_path, 'rb')).getroot() root = objectify.parse(open(file_path, 'rb')).getroot()
self.process_song(root) self.process_song(root)
@ -66,7 +67,7 @@ class PowerPraiseImport(SongImport):
else: else:
verse_def = 'o' verse_def = 'o'
verse_count[verse_def] = verse_count.get(verse_def, 0) + 1 verse_count[verse_def] = verse_count.get(verse_def, 0) + 1
verse_def = '%s%d' % (verse_def, verse_count[verse_def]) verse_def = '{verse}{count:d}'.format(verse=verse_def, count=verse_count[verse_def])
verse_text = [] verse_text = []
for slide in part.slide: for slide in part.slide:
if not hasattr(slide, 'line'): if not hasattr(slide, 'line'):

View File

@ -96,7 +96,7 @@ class PowerSongImport(SongImport):
self.import_source = '' self.import_source = ''
if not self.import_source or not isinstance(self.import_source, list): if not self.import_source or not isinstance(self.import_source, list):
self.log_error(translate('SongsPlugin.PowerSongImport', 'No songs to import.'), self.log_error(translate('SongsPlugin.PowerSongImport', 'No songs to import.'),
translate('SongsPlugin.PowerSongImport', 'No %s files found.') % ps_string) translate('SongsPlugin.PowerSongImport', 'No {text} files found.').format(text=ps_string))
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for file in self.import_source: for file in self.import_source:
@ -113,9 +113,9 @@ class PowerSongImport(SongImport):
field = self._read_string(song_data) field = self._read_string(song_data)
except ValueError: except ValueError:
parse_error = True parse_error = True
self.log_error(os.path.basename(file), str( self.log_error(os.path.basename(file),
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Unexpected byte value.')) % translate('SongsPlugin.PowerSongImport',
ps_string) 'Invalid {text} file. Unexpected byte value.').format(text=ps_string))
break break
else: else:
if label == 'TITLE': if label == 'TITLE':
@ -131,19 +131,20 @@ class PowerSongImport(SongImport):
continue continue
# Check that file had TITLE field # Check that file had TITLE field
if not self.title: if not self.title:
self.log_error(os.path.basename(file), str( self.log_error(os.path.basename(file),
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % ps_string) translate('SongsPlugin.PowerSongImport',
'Invalid {text} file. Missing "TITLE" header.').format(text=ps_string))
continue continue
# Check that file had COPYRIGHTLINE label # Check that file had COPYRIGHTLINE label
if not found_copyright: if not found_copyright:
self.log_error(self.title, str( self.log_error(self.title,
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "COPYRIGHTLINE" header.')) % translate('SongsPlugin.PowerSongImport',
ps_string) 'Invalid {text} file. Missing "COPYRIGHTLINE" header.').format(text=ps_string))
continue continue
# Check that file had at least one verse # Check that file had at least one verse
if not self.verses: if not self.verses:
self.log_error(self.title, str( self.log_error(self.title,
translate('SongsPlugin.PowerSongImport', 'Verses not found. Missing "PART" header.'))) translate('SongsPlugin.PowerSongImport', 'Verses not found. Missing "PART" header.'))
continue continue
if not self.finish(): if not self.finish():
self.log_error(self.title) self.log_error(self.title)

View File

@ -44,6 +44,7 @@ class PresentationManagerImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
try: try:
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True)) tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
@ -90,7 +91,7 @@ class PresentationManagerImport(SongImport):
verse_def = 'o' verse_def = 'o'
if not is_duplicate: # Only increment verse number if no duplicate if not is_duplicate: # Only increment verse number if no duplicate
verse_count[verse_def] = verse_count.get(verse_def, 0) + 1 verse_count[verse_def] = verse_count.get(verse_def, 0) + 1
verse_def = '%s%d' % (verse_def, verse_count[verse_def]) verse_def = '{verse}{count:d}'.format(verse=verse_def, count=verse_count[verse_def])
if not is_duplicate: # Only add verse if no duplicate if not is_duplicate: # Only add verse if no duplicate
self.add_verse(str(verse).strip(), verse_def) self.add_verse(str(verse).strip(), verse_def)
verse_order_list.append(verse_def) verse_order_list.append(verse_def)

View File

@ -46,6 +46,7 @@ class ProPresenterImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
root = objectify.parse(open(file_path, 'rb')).getroot() root = objectify.parse(open(file_path, 'rb')).getroot()
self.process_song(root, file_path) self.process_song(root, file_path)
@ -87,7 +88,7 @@ class ProPresenterImport(SongImport):
RTFData = slide.displayElements.RVTextElement.get('RTFData') RTFData = slide.displayElements.RVTextElement.get('RTFData')
rtf = base64.standard_b64decode(RTFData) rtf = base64.standard_b64decode(RTFData)
words, encoding = strip_rtf(rtf.decode()) words, encoding = strip_rtf(rtf.decode())
self.add_verse(words, "v%d" % count) self.add_verse(words, "v{count}".format(count=count))
# ProPresenter 5 # ProPresenter 5
elif(self.version >= 500 and self.version < 600): elif(self.version >= 500 and self.version < 600):
@ -103,7 +104,7 @@ class ProPresenterImport(SongImport):
RTFData = slide.displayElements.RVTextElement.get('RTFData') RTFData = slide.displayElements.RVTextElement.get('RTFData')
rtf = base64.standard_b64decode(RTFData) rtf = base64.standard_b64decode(RTFData)
words, encoding = strip_rtf(rtf.decode()) words, encoding = strip_rtf(rtf.decode())
self.add_verse(words, "v%d" % count) self.add_verse(words, "v{count:d}".format(count=count))
# ProPresenter 6 # ProPresenter 6
elif(self.version >= 600 and self.version < 700): elif(self.version >= 600 and self.version < 700):
@ -127,7 +128,7 @@ class ProPresenterImport(SongImport):
words, encoding = strip_rtf(data.decode()) words, encoding = strip_rtf(data.decode())
break break
if words: if words:
self.add_verse(words, "v%d" % count) self.add_verse(words, "v{count:d}".format(count=count))
if not self.finish(): if not self.finish():
self.log_error(self.import_source) self.log_error(self.import_source)

View File

@ -117,7 +117,7 @@ class SongImport(QtCore.QObject):
self.import_wizard.error_report_text_edit.setVisible(True) self.import_wizard.error_report_text_edit.setVisible(True)
self.import_wizard.error_copy_to_button.setVisible(True) self.import_wizard.error_copy_to_button.setVisible(True)
self.import_wizard.error_save_to_button.setVisible(True) self.import_wizard.error_save_to_button.setVisible(True)
self.import_wizard.error_report_text_edit.append('- %s (%s)' % (file_path, reason)) self.import_wizard.error_report_text_edit.append('- {path} ({error})'.format(path=file_path, error=reason))
def stop_import(self): def stop_import(self):
""" """
@ -326,10 +326,11 @@ class SongImport(QtCore.QObject):
if not self.check_complete(): if not self.check_complete():
self.set_defaults() self.set_defaults()
return False return False
log.info('committing song %s to database', self.title) log.info('committing song {title} to database'.format(title=self.title))
song = Song() song = Song()
song.title = self.title song.title = self.title
if self.import_wizard is not None: if self.import_wizard is not None:
# TODO: Verify format() with template variables
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % song.title) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % song.title)
song.alternate_title = self.alternate_title song.alternate_title = self.alternate_title
# Values will be set when cleaning the song. # Values will be set when cleaning the song.
@ -344,11 +345,11 @@ class SongImport(QtCore.QObject):
if verse_def[0].lower() in VerseType.tags: if verse_def[0].lower() in VerseType.tags:
verse_tag = verse_def[0].lower() verse_tag = verse_def[0].lower()
else: else:
new_verse_def = '%s%d' % (VerseType.tags[VerseType.Other], other_count) new_verse_def = '{tag}{count:d}'.format(tag=VerseType.tags[VerseType.Other], count=other_count)
verses_changed_to_other[verse_def] = new_verse_def verses_changed_to_other[verse_def] = new_verse_def
other_count += 1 other_count += 1
verse_tag = VerseType.tags[VerseType.Other] verse_tag = VerseType.tags[VerseType.Other]
log.info('Versetype %s changing to %s', verse_def, new_verse_def) log.info('Versetype {old} changing to {new}'.format(old=verse_def, new=new_verse_def))
verse_def = new_verse_def verse_def = new_verse_def
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang) sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
song.lyrics = str(sxml.extract_xml(), 'utf-8') song.lyrics = str(sxml.extract_xml(), 'utf-8')

View File

@ -101,6 +101,7 @@ class SongShowPlusImport(SongImport):
self.other_count = 0 self.other_count = 0
self.other_list = {} self.other_list = {}
file_name = os.path.split(file)[1] file_name = os.path.split(file)[1]
# TODO: Verify format() with template variables
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % file_name, 0) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % file_name, 0)
song_data = open(file, 'rb') song_data = open(file, 'rb')
while True: while True:
@ -145,13 +146,16 @@ class SongShowPlusImport(SongImport):
if match: if match:
self.ccli_number = int(match.group()) self.ccli_number = int(match.group())
else: else:
log.warning("Can't parse CCLI Number from string: %s" % self.decode(data)) log.warning("Can't parse CCLI Number from string: {text}".format(text=self.decode(data)))
elif block_key == VERSE: elif block_key == VERSE:
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no)) self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Verse],
number=verse_no))
elif block_key == CHORUS: elif block_key == CHORUS:
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no)) self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Chorus],
number=verse_no))
elif block_key == BRIDGE: elif block_key == BRIDGE:
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no)) self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Bridge],
number=verse_no))
elif block_key == TOPIC: elif block_key == TOPIC:
self.topics.append(self.decode(data)) self.topics.append(self.decode(data))
elif block_key == COMMENTS: elif block_key == COMMENTS:
@ -170,7 +174,7 @@ class SongShowPlusImport(SongImport):
verse_tag = self.to_openlp_verse_tag(verse_name) verse_tag = self.to_openlp_verse_tag(verse_name)
self.add_verse(self.decode(data), verse_tag) self.add_verse(self.decode(data), verse_tag)
else: else:
log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data)) log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data))
song_data.seek(next_block_starts) song_data.seek(next_block_starts)
self.verse_order_list = self.ssp_verse_order_list self.verse_order_list = self.ssp_verse_order_list
song_data.close() song_data.close()

View File

@ -141,7 +141,7 @@ class SundayPlusImport(SongImport):
if len(value): if len(value):
verse_type = VerseType.tags[VerseType.from_loose_input(value[0])] verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
verse_type = "%s%s" % (verse_type, value[-1]) verse_type = "{verse}{value}".format(verse=verse_type, value=value[-1])
elif name == 'HOTKEY': elif name == 'HOTKEY':
value = self.decode(value).strip() value = self.decode(value).strip()
# HOTKEY always appears after MARKER_NAME, so it # HOTKEY always appears after MARKER_NAME, so it

View File

@ -115,8 +115,8 @@ class VideoPsalmImport(SongImport):
for verse in song['Verses']: for verse in song['Verses']:
self.add_verse(verse['Text'], 'v') self.add_verse(verse['Text'], 'v')
if not self.finish(): if not self.finish():
self.log_error('Could not import %s' % self.title) self.log_error('Could not import {title}'.format(title=self.title))
except Exception as e: except Exception as e:
self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File %s' % file.name), self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File {name}').format(name=file.name),
translate('SongsPlugin.VideoPsalmImport', 'Error: %s') % e) translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
song_file.close() song_file.close()

View File

@ -108,8 +108,8 @@ class WordsOfWorshipImport(SongImport):
if song_data.read(19).decode() != 'WoW File\nSong Words': if song_data.read(19).decode() != 'WoW File\nSong Words':
self.log_error(source, self.log_error(source,
translate('SongsPlugin.WordsofWorshipSongImport', translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "%s" header.') 'Invalid Words of Worship song file. Missing "{text}" '
% 'WoW File\\nSong Words') 'header.').format(text='WoW File\\nSong Words'))
continue continue
# Seek to byte which stores number of blocks in the song # Seek to byte which stores number of blocks in the song
song_data.seek(56) song_data.seek(56)
@ -118,8 +118,8 @@ class WordsOfWorshipImport(SongImport):
if song_data.read(16).decode() != 'CSongDoc::CBlock': if song_data.read(16).decode() != 'CSongDoc::CBlock':
self.log_error(source, self.log_error(source,
translate('SongsPlugin.WordsofWorshipSongImport', translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "%s" string.') 'Invalid Words of Worship song file. Missing "{text}" '
% 'CSongDoc::CBlock') 'string.').format(text='CSongDoc::CBlock'))
continue continue
# Seek to the beginning of the first block # Seek to the beginning of the first block
song_data.seek(82) song_data.seek(82)

View File

@ -91,11 +91,11 @@ class WorshipAssistantImport(SongImport):
records = list(songs_reader) records = list(songs_reader)
except csv.Error as e: except csv.Error as e:
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Error reading CSV file.'), self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Error reading CSV file.'),
translate('SongsPlugin.WorshipAssistantImport', 'Line %d: %s') % translate('SongsPlugin.WorshipAssistantImport',
(songs_reader.line_num, e)) 'Line {number:d}: {error}').format(number=songs_reader.line_num, error=e))
return return
num_records = len(records) num_records = len(records)
log.info('%s records found in CSV file' % num_records) log.info('{count} records found in CSV file'.format(count=num_records))
self.import_wizard.progress_bar.setMaximum(num_records) self.import_wizard.progress_bar.setMaximum(num_records)
# Create regex to strip html tags # Create regex to strip html tags
re_html_strip = re.compile(r'<[^>]+>') re_html_strip = re.compile(r'<[^>]+>')
@ -122,12 +122,14 @@ class WorshipAssistantImport(SongImport):
verse_order_list = [x.strip() for x in record['ROADMAP'].split(',')] verse_order_list = [x.strip() for x in record['ROADMAP'].split(',')]
lyrics = record['LYRICS2'] lyrics = record['LYRICS2']
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d' % index), self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record {count:d}').format(count=index),
translate('SongsPlugin.WorshipAssistantImport', 'Decoding error: %s') % e) translate('SongsPlugin.WorshipAssistantImport',
'Decoding error: {error}').format(error=e))
continue continue
except TypeError as e: except TypeError as e:
self.log_error(translate('SongsPlugin.WorshipAssistantImport', self.log_error(translate('SongsPlugin.WorshipAssistantImport',
'File not valid WorshipAssistant CSV format.'), 'TypeError: %s' % e) 'File not valid WorshipAssistant CSV format.'),
'TypeError: {error}'.format(error=e))
return return
verse = '' verse = ''
used_verses = [] used_verses = []
@ -180,6 +182,7 @@ class WorshipAssistantImport(SongImport):
cleaned_verse_order_list.append(verse) cleaned_verse_order_list.append(verse)
self.verse_order_list = cleaned_verse_order_list self.verse_order_list = cleaned_verse_order_list
if not self.finish(): if not self.finish():
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index + self.log_error(translate('SongsPlugin.WorshipAssistantImport',
'Record {count:d}').format(count=index) +
(': "' + self.title + '"' if self.title else '')) (': "' + self.title + '"' if self.title else ''))
songs_file.close() songs_file.close()

View File

@ -49,9 +49,11 @@ class WorshipCenterProImport(SongImport):
Receive a single file to import. Receive a single file to import.
""" """
try: try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s' % self.import_source) conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};'
'DBQ={source}'.format(source=self.import_source))
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e: except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
log.warning('Unable to connect the WorshipCenter Pro database %s. %s', self.import_source, str(e)) log.warning('Unable to connect the WorshipCenter Pro '
'database {source}. {error}'.format(source=self.import_source, error=str(e)))
# Unfortunately no specific exception type # Unfortunately no specific exception type
self.log_error(self.import_source, translate('SongsPlugin.WorshipCenterProImport', self.log_error(self.import_source, translate('SongsPlugin.WorshipCenterProImport',
'Unable to connect the WorshipCenter Pro database.')) 'Unable to connect the WorshipCenter Pro database.'))

View File

@ -84,10 +84,11 @@ class ZionWorxImport(SongImport):
records = list(songs_reader) records = list(songs_reader)
except csv.Error as e: except csv.Error as e:
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'), self.log_error(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e)) translate('SongsPlugin.ZionWorxImport',
'Line {number:d}: {error}').format(number=songs_reader.line_num, error=e))
return return
num_records = len(records) num_records = len(records)
log.info('%s records found in CSV file' % num_records) log.info('{count} records found in CSV file'.format(count=num_records))
self.import_wizard.progress_bar.setMaximum(num_records) self.import_wizard.progress_bar.setMaximum(num_records)
for index, record in enumerate(records, 1): for index, record in enumerate(records, 1):
if self.stop_import_flag: if self.stop_import_flag:
@ -101,12 +102,12 @@ class ZionWorxImport(SongImport):
self.add_copyright(self._decode(record['Copyright'])) self.add_copyright(self._decode(record['Copyright']))
lyrics = self._decode(record['Lyrics']) lyrics = self._decode(record['Lyrics'])
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index), self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record {index}').format(index=index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e) translate('SongsPlugin.ZionWorxImport', 'Decoding error: {error}').format(error=e))
continue continue
except TypeError as e: except TypeError as e:
self.log_error(translate( self.log_error(translate('SongsPlugin.ZionWorxImport', 'File not valid ZionWorx CSV format.'),
'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx CSV format.'), 'TypeError: %s' % e) 'TypeError: {error}'.format(error=e))
return return
verse = '' verse = ''
for line in lyrics.splitlines(): for line in lyrics.splitlines():

View File

@ -129,7 +129,7 @@ class SongMediaItem(MediaManagerItem):
self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol') self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol')
def retranslateUi(self): def retranslateUi(self):
self.search_text_label.setText('%s:' % UiStrings().Search) self.search_text_label.setText('{text}:'.format(text=UiStrings().Search))
self.search_text_button.setText(UiStrings().Search) self.search_text_button.setText(UiStrings().Search)
self.maintenance_action.setText(SongStrings.SongMaintenance) self.maintenance_action.setText(SongStrings.SongMaintenance)
self.maintenance_action.setToolTip(translate('SongsPlugin.MediaItem', self.maintenance_action.setToolTip(translate('SongsPlugin.MediaItem',
@ -166,12 +166,14 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', 'CCLI number'), translate('SongsPlugin.MediaItem', 'CCLI number'),
translate('SongsPlugin.MediaItem', 'Search CCLI number...')) translate('SongsPlugin.MediaItem', 'Search CCLI number...'))
]) ])
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section)) self.search_text_edit.set_current_search_type(
Settings().value('{section}/last search type'.format(section=self.settings_section)))
self.config_update() self.config_update()
def on_search_text_button_clicked(self): def on_search_text_button_clicked(self):
# Save the current search type to the configuration. # Save the current search type to the configuration.
Settings().setValue('%s/last search type' % self.settings_section, self.search_text_edit.current_search_type()) Settings().setValue('{section}/last search type'.format(section=self.settings_section),
self.search_text_edit.current_search_type())
# Reload the list considering the new search type. # Reload the list considering the new search type.
search_keywords = str(self.search_text_edit.displayText()) search_keywords = str(self.search_text_edit.displayText())
search_type = self.search_text_edit.current_search_type() search_type = self.search_text_edit.current_search_type()
@ -181,31 +183,31 @@ class SongMediaItem(MediaManagerItem):
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.Titles: elif search_type == SongSearch.Titles:
log.debug('Titles Search') log.debug('Titles Search')
search_string = '%' + clean_string(search_keywords) + '%' search_string = '%{text}%'.format(text=clean_string(search_keywords))
search_results = self.plugin.manager.get_all_objects(Song, Song.search_title.like(search_string)) search_results = self.plugin.manager.get_all_objects(Song, Song.search_title.like(search_string))
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.Lyrics: elif search_type == SongSearch.Lyrics:
log.debug('Lyrics Search') log.debug('Lyrics Search')
search_string = '%' + clean_string(search_keywords) + '%' search_string = '%{text}%'.format(text=clean_string(search_keywords))
search_results = self.plugin.manager.get_all_objects(Song, Song.search_lyrics.like(search_string)) search_results = self.plugin.manager.get_all_objects(Song, Song.search_lyrics.like(search_string))
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.Authors: elif search_type == SongSearch.Authors:
log.debug('Authors Search') log.debug('Authors Search')
search_string = '%' + search_keywords + '%' search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects( search_results = self.plugin.manager.get_all_objects(
Author, Author.display_name.like(search_string)) Author, Author.display_name.like(search_string))
self.display_results_author(search_results) self.display_results_author(search_results)
elif search_type == SongSearch.Topics: elif search_type == SongSearch.Topics:
log.debug('Topics Search') log.debug('Topics Search')
search_string = '%' + search_keywords + '%' search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects( search_results = self.plugin.manager.get_all_objects(
Topic, Topic.name.like(search_string)) Topic, Topic.name.like(search_string))
self.display_results_topic(search_results) self.display_results_topic(search_results)
elif search_type == SongSearch.Books: elif search_type == SongSearch.Books:
log.debug('Songbook Search') log.debug('Songbook Search')
search_keywords = search_keywords.rpartition(' ') search_keywords = search_keywords.rpartition(' ')
search_book = search_keywords[0] + '%' search_book = '{text}%'.format(text=search_keywords[0])
search_entry = search_keywords[2] + '%' search_entry = '{text}%'.format(text=search_keywords[2])
search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id) search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id)
.join(Song) .join(Song)
.join(Book) .join(Book)
@ -214,26 +216,26 @@ class SongMediaItem(MediaManagerItem):
self.display_results_book(search_results) self.display_results_book(search_results)
elif search_type == SongSearch.Themes: elif search_type == SongSearch.Themes:
log.debug('Theme Search') log.debug('Theme Search')
search_string = '%' + search_keywords + '%' search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects( search_results = self.plugin.manager.get_all_objects(
Song, Song.theme_name.like(search_string)) Song, Song.theme_name.like(search_string))
self.display_results_themes(search_results) self.display_results_themes(search_results)
elif search_type == SongSearch.Copyright: elif search_type == SongSearch.Copyright:
log.debug('Copyright Search') log.debug('Copyright Search')
search_string = '%' + search_keywords + '%' search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects( search_results = self.plugin.manager.get_all_objects(
Song, and_(Song.copyright.like(search_string), Song.copyright != '')) Song, and_(Song.copyright.like(search_string), Song.copyright != ''))
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.CCLInumber: elif search_type == SongSearch.CCLInumber:
log.debug('CCLI number Search') log.debug('CCLI number Search')
search_string = '%' + search_keywords + '%' search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects( search_results = self.plugin.manager.get_all_objects(
Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != '')) Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != ''))
self.display_results_cclinumber(search_results) self.display_results_cclinumber(search_results)
self.check_search_result() self.check_search_result()
def search_entire(self, search_keywords): def search_entire(self, search_keywords):
search_string = '%' + clean_string(search_keywords) + '%' search_string = '%{text}%'.format(text=clean_string(search_keywords))
return self.plugin.manager.get_all_objects( return self.plugin.manager.get_all_objects(
Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string), Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string),
Song.comments.like(search_string))) Song.comments.like(search_string)))
@ -272,7 +274,8 @@ class SongMediaItem(MediaManagerItem):
if song.temporary: if song.temporary:
continue continue
author_list = [author.display_name for author in song.authors] author_list = [author.display_name for author in song.authors]
song_detail = '%s (%s)' % (song.title, create_separated_list(author_list)) if author_list else song.title text = create_separated_list(author_list) if author_list else song.title
song_detail = '{title} ({author})'.format(title=song.title, author=text)
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
@ -305,7 +308,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
continue continue
song_detail = '%s (%s)' % (author.display_name, song.title) song_detail = '{author} ({title})'.format(author=author.display_name, title=song.title)
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
@ -325,7 +328,8 @@ class SongMediaItem(MediaManagerItem):
self.list_view.clear() self.list_view.clear()
search_results.sort(key=get_songbook_key) search_results.sort(key=get_songbook_key)
for result in search_results: for result in search_results:
song_detail = '%s #%s: %s' % (result[1], result[0], result[2]) song_detail = '{result1} #{result0}: {result2}'.format(result1=result[1], result0=result[0],
result2=result[2])
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, result[3]) song_name.setData(QtCore.Qt.UserRole, result[3])
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
@ -354,7 +358,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
continue continue
song_detail = '%s (%s)' % (topic.name, song.title) song_detail = '{topic} ({title})'.format(topic=topic.name, title=song.title)
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
@ -377,7 +381,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
continue continue
song_detail = '%s (%s)' % (song.theme_name, song.title) song_detail = '{theme} ({song})'.format(theme=song.theme_name, song=song.title)
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
@ -400,7 +404,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
continue continue
song_detail = '%s (%s)' % (song.ccli_number, song.title) song_detail = '{ccli} ({song})'.format(ccli=song.ccli_number, song=song.title)
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
@ -456,7 +460,7 @@ class SongMediaItem(MediaManagerItem):
Called by ServiceManager or SlideController by event passing the Song Id in the payload along with an indicator Called by ServiceManager or SlideController by event passing the Song Id in the payload along with an indicator
to say which type of display is required. to say which type of display is required.
""" """
log.debug('on_remote_edit for song %s' % song_id) log.debug('on_remote_edit for song {song}'.format(song=song_id))
song_id = int(song_id) song_id = int(song_id)
valid = self.plugin.manager.get_object(Song, song_id) valid = self.plugin.manager.get_object(Song, song_id)
if valid: if valid:
@ -499,7 +503,8 @@ class SongMediaItem(MediaManagerItem):
if QtWidgets.QMessageBox.question( if QtWidgets.QMessageBox.question(
self, UiStrings().ConfirmDelete, self, UiStrings().ConfirmDelete,
translate('SongsPlugin.MediaItem', translate('SongsPlugin.MediaItem',
'Are you sure you want to delete the "%d" selected song(s)?') % len(items), 'Are you sure you want to delete the "{items:d}" '
'selected song(s)?').format(items=len(items)),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No: QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
return return
@ -524,8 +529,9 @@ class SongMediaItem(MediaManagerItem):
old_song = self.plugin.manager.get_object(Song, item_id) old_song = self.plugin.manager.get_object(Song, item_id)
song_xml = self.open_lyrics.song_to_xml(old_song) song_xml = self.open_lyrics.song_to_xml(old_song)
new_song = self.open_lyrics.xml_to_song(song_xml) new_song = self.open_lyrics.xml_to_song(song_xml)
new_song.title = '%s <%s>' % \ new_song.title = '{title} <{text}>'.format(title=new_song.title,
(new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning')) text=translate('SongsPlugin.MediaItem',
'copy', 'For song cloning'))
# Copy audio files from the old to the new song # Copy audio files from the old to the new song
if len(old_song.media_files) > 0: if len(old_song.media_files) > 0:
save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(new_song.id)) save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(new_song.id))
@ -552,7 +558,8 @@ class SongMediaItem(MediaManagerItem):
:param remote: Triggered from remote :param remote: Triggered from remote
:param context: Why is it being generated :param context: Why is it being generated
""" """
log.debug('generate_slide_data: %s, %s, %s' % (service_item, item, self.remote_song)) log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item,
remote=self.remote_song))
item_id = self._get_id_of_item_to_generate(item, self.remote_song) item_id = self._get_id_of_item_to_generate(item, self.remote_song)
service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanPreview)
@ -581,7 +588,7 @@ class SongMediaItem(MediaManagerItem):
if verse_index is None: if verse_index is None:
verse_index = VerseType.from_tag(verse_tag) verse_index = VerseType.from_tag(verse_tag)
verse_tag = VerseType.translated_tags[verse_index].upper() verse_tag = VerseType.translated_tags[verse_index].upper()
verse_def = '%s%s' % (verse_tag, verse[0]['label']) verse_def = '{tag}{label}'.format(tag=verse_tag, label=verse[0]['label'])
service_item.add_from_text(str(verse[1]), verse_def) service_item.add_from_text(str(verse[1]), verse_def)
else: else:
# Loop through the verse list and expand the song accordingly. # Loop through the verse list and expand the song accordingly.
@ -596,7 +603,7 @@ class SongMediaItem(MediaManagerItem):
else: else:
verse_index = VerseType.from_tag(verse[0]['type']) verse_index = VerseType.from_tag(verse[0]['type'])
verse_tag = VerseType.translated_tags[verse_index] verse_tag = VerseType.translated_tags[verse_index]
verse_def = '%s%s' % (verse_tag, verse[0]['label']) verse_def = '{tag}{label}'.format(tzg=verse_tag, text=verse[0]['label'])
service_item.add_from_text(verse[1], verse_def) service_item.add_from_text(verse[1], verse_def)
service_item.title = song.title service_item.title = song.title
author_list = self.generate_footer(service_item, song) author_list = self.generate_footer(service_item, song)
@ -639,23 +646,24 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer = [] item.raw_footer = []
item.raw_footer.append(song.title) item.raw_footer.append(song.title)
if authors_none: if authors_none:
item.raw_footer.append("%s: %s" % (translate('OpenLP.Ui', 'Written by'), item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
create_separated_list(authors_none))) authors=create_separated_list(authors_none)))
if authors_words_music: if authors_words_music:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.WordsAndMusic], item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic],
create_separated_list(authors_words_music))) authors=create_separated_list(authors_words_music)))
if authors_words: if authors_words:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Words], item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Words],
create_separated_list(authors_words))) authors=create_separated_list(authors_words)))
if authors_music: if authors_music:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Music], item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Music],
create_separated_list(authors_music))) authors=create_separated_list(authors_music)))
if authors_translation: if authors_translation:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation], item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation],
create_separated_list(authors_translation))) authors=create_separated_list(authors_translation)))
if song.copyright: if song.copyright:
if self.display_copyright_symbol: if self.display_copyright_symbol:
item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright)) item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol,
song=song.copyright))
else: else:
item.raw_footer.append(song.copyright) item.raw_footer.append(song.copyright)
if self.display_songbook and song.songbook_entries: if self.display_songbook and song.songbook_entries:

View File

@ -61,18 +61,20 @@ class OpenLyricsExport(RegistryProperties):
if self.parent.stop_export_flag: if self.parent.stop_export_flag:
return False return False
self.parent.increment_progress_bar( self.parent.increment_progress_bar(
translate('SongsPlugin.OpenLyricsExport', 'Exporting "%s"...') % song.title) translate('SongsPlugin.OpenLyricsExport', 'Exporting "{title}"...').format(title=song.title))
xml = open_lyrics.song_to_xml(song) xml = open_lyrics.song_to_xml(song)
tree = etree.ElementTree(etree.fromstring(xml.encode())) tree = etree.ElementTree(etree.fromstring(xml.encode()))
filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors])) filename = '{title} ({author})'.format(title=song.title,
author=', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename) filename = clean_filename(filename)
# Ensure the filename isn't too long for some filesystems # Ensure the filename isn't too long for some filesystems
filename_with_ext = '%s.xml' % filename[0:250 - len(self.save_path)] filename_with_ext = '{name}.xml'.format(name=filename[0:250 - len(self.save_path)])
# Make sure we're not overwriting an existing file # Make sure we're not overwriting an existing file
conflicts = 0 conflicts = 0
while os.path.exists(os.path.join(self.save_path, filename_with_ext)): while os.path.exists(os.path.join(self.save_path, filename_with_ext)):
conflicts += 1 conflicts += 1
filename_with_ext = '%s-%d.xml' % (filename[0:247 - len(self.save_path)], conflicts) filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - len(self.save_path)],
extra=conflicts)
# Pass a file object, because lxml does not cope with some special # Pass a file object, because lxml does not cope with some special
# characters in the path (see lp:757673 and lp:744337). # characters in the path (see lp:757673 and lp:744337).
tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8', tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8',

View File

@ -70,6 +70,7 @@ from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song, Topic
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
NAMESPACE = 'http://openlyrics.info/namespace/2009/song' NAMESPACE = 'http://openlyrics.info/namespace/2009/song'
# TODO: Verify format() with template variable
NSMAP = '{' + NAMESPACE + '}' + '%s' NSMAP = '{' + NAMESPACE + '}' + '%s'
@ -126,7 +127,7 @@ class SongXML(object):
try: try:
self.song_xml = objectify.fromstring(xml) self.song_xml = objectify.fromstring(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
log.exception('Invalid xml %s', xml) log.exception('Invalid xml {text}'.format(text=xml))
xml_iter = self.song_xml.getiterator() xml_iter = self.song_xml.getiterator()
for element in xml_iter: for element in xml_iter:
if element.tag == 'verse': if element.tag == 'verse':
@ -422,7 +423,7 @@ class OpenLyrics(object):
:param tags_element: Some tag elements :param tags_element: Some tag elements
""" """
available_tags = FormattingTags.get_html_tags() available_tags = FormattingTags.get_html_tags()
start_tag = '{%s}' % tag_name start_tag = '{{{name}}}'.format(name=tag_name)
for tag in available_tags: for tag in available_tags:
if tag['start tag'] == start_tag: if tag['start tag'] == start_tag:
# Create new formatting tag in openlyrics xml. # Create new formatting tag in openlyrics xml.
@ -449,18 +450,18 @@ class OpenLyrics(object):
xml_tags = tags_element.xpath('tag/attribute::name') xml_tags = tags_element.xpath('tag/attribute::name')
# Some formatting tag has only starting part e.g. <br>. Handle this case. # Some formatting tag has only starting part e.g. <br>. Handle this case.
if tag in end_tags: if tag in end_tags:
text = text.replace('{%s}' % tag, '<tag name="%s">' % tag) text = text.replace('{{{tag}}}'.format(tag=tag), '<tag name="{tag}">'.format(tag=tag))
else: else:
text = text.replace('{%s}' % tag, '<tag name="%s"/>' % tag) text = text.replace('{{{tag}}}'.format(tag=tag), '<tag name="{tag}"/>'.format(tag=tag))
# Add tag to <format> element if tag not present. # Add tag to <format> element if tag not present.
if tag not in xml_tags: if tag not in xml_tags:
self._add_tag_to_formatting(tag, tags_element) self._add_tag_to_formatting(tag, tags_element)
# Replace end tags. # Replace end tags.
for tag in end_tags: for tag in end_tags:
text = text.replace('{/%s}' % tag, '</tag>') text = text.replace('{/{tag}}}'.format(tag=tag), '</tag>')
# Replace \n with <br/>. # Replace \n with <br/>.
text = text.replace('\n', '<br/>') text = text.replace('\n', '<br/>')
element = etree.XML('<lines>%s</lines>' % text) element = etree.XML('<lines>{text}</lines>'.format(text=text))
verse_element.append(element) verse_element.append(element)
return element return element
@ -566,9 +567,9 @@ class OpenLyrics(object):
name = tag.get('name') name = tag.get('name')
if name is None: if name is None:
continue continue
start_tag = '{%s}' % name[:5] start_tag = '{{{name}}}'.format(name=name[:5])
# Some tags have only start tag e.g. {br} # Some tags have only start tag e.g. {br}
end_tag = '{/' + name[:5] + '}' if hasattr(tag, 'close') else '' end_tag = '{{/{name}}}'.format(name=name[:5]) if hasattr(tag, 'close') else ''
openlp_tag = { openlp_tag = {
'desc': name, 'desc': name,
'start tag': start_tag, 'start tag': start_tag,
@ -604,26 +605,30 @@ class OpenLyrics(object):
text = '' text = ''
use_endtag = True use_endtag = True
# Skip <comment> elements - not yet supported. # Skip <comment> elements - not yet supported.
# TODO: Verify format() with template variables
if element.tag == NSMAP % 'comment': if element.tag == NSMAP % 'comment':
if element.tail: if element.tail:
# Append tail text at chord element. # Append tail text at chord element.
text += element.tail text += element.tail
return text return text
# Skip <chord> element - not yet supported. # Skip <chord> element - not yet supported.
# TODO: Verify format() with template variables
elif element.tag == NSMAP % 'chord': elif element.tag == NSMAP % 'chord':
if element.tail: if element.tail:
# Append tail text at chord element. # Append tail text at chord element.
text += element.tail text += element.tail
return text return text
# Convert line breaks <br/> to \n. # Convert line breaks <br/> to \n.
# TODO: Verify format() with template variables
elif newlines and element.tag == NSMAP % 'br': elif newlines and element.tag == NSMAP % 'br':
text += '\n' text += '\n'
if element.tail: if element.tail:
text += element.tail text += element.tail
return text return text
# Start formatting tag. # Start formatting tag.
# TODO: Verify format() with template variables
if element.tag == NSMAP % 'tag': if element.tag == NSMAP % 'tag':
text += '{%s}' % element.get('name') text += '{{{name}}}'.format(name=element.get('name'))
# Some formattings may have only start tag. # Some formattings may have only start tag.
# Handle this case if element has no children and contains no text. # Handle this case if element has no children and contains no text.
if not element and not element.text: if not element and not element.text:
@ -636,8 +641,9 @@ class OpenLyrics(object):
# Use recursion since nested formatting tags are allowed. # Use recursion since nested formatting tags are allowed.
text += self._process_lines_mixed_content(child, newlines) text += self._process_lines_mixed_content(child, newlines)
# Append text from tail and add formatting end tag. # Append text from tail and add formatting end tag.
# TODO: Verify format() with template variables
if element.tag == NSMAP % 'tag' and use_endtag: if element.tag == NSMAP % 'tag' and use_endtag:
text += '{/%s}' % element.get('name') text += '{/{name}}}'.format(name=element.get('name'))
# Append text from tail. # Append text from tail.
if element.tail: if element.tail:
text += element.tail text += element.tail
@ -663,6 +669,7 @@ class OpenLyrics(object):
# Loop over the "line" elements removing comments and chords. # Loop over the "line" elements removing comments and chords.
for line in element: for line in element:
# Skip comment lines. # Skip comment lines.
# TODO: Verify format() with template variables
if line.tag == NSMAP % 'comment': if line.tag == NSMAP % 'comment':
continue continue
if text: if text:

View File

@ -78,7 +78,7 @@ class SongSelectImport(object):
try: try:
login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml') login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml')
except (TypeError, URLError) as e: except (TypeError, URLError) as e:
log.exception('Could not login to SongSelect, %s', e) log.exception('Could not login to SongSelect, {error}'.format(error=e))
return False return False
if callback: if callback:
callback() callback()
@ -92,7 +92,7 @@ class SongSelectImport(object):
try: try:
posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml') posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml')
except (TypeError, URLError) as e: except (TypeError, URLError) as e:
log.exception('Could not login to SongSelect, %s', e) log.exception('Could not login to SongSelect, {error}'.format(error=e))
return False return False
if callback: if callback:
callback() callback()
@ -105,7 +105,7 @@ class SongSelectImport(object):
try: try:
self.opener.open(LOGOUT_URL) self.opener.open(LOGOUT_URL)
except (TypeError, URLError) as e: except (TypeError, URLError) as e:
log.exception('Could not log of SongSelect, %s', e) log.exception('Could not log of SongSelect, {error}'.format(error=e))
def search(self, search_text, max_results, callback=None): def search(self, search_text, max_results, callback=None):
""" """
@ -127,7 +127,7 @@ class SongSelectImport(object):
results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml') results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml')
search_results = results_page.find_all('li', 'result pane') search_results = results_page.find_all('li', 'result pane')
except (TypeError, URLError) as e: except (TypeError, URLError) as e:
log.exception('Could not search SongSelect, %s', e) log.exception('Could not search SongSelect, {error}'.format(error=e))
search_results = None search_results = None
if not search_results: if not search_results:
break break
@ -158,7 +158,7 @@ class SongSelectImport(object):
try: try:
song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml') song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')
except (TypeError, URLError) as e: except (TypeError, URLError) as e:
log.exception('Could not get song from SongSelect, %s', e) log.exception('Could not get song from SongSelect, {error}'.format(error=e))
return None return None
if callback: if callback:
callback() callback()
@ -203,7 +203,7 @@ class SongSelectImport(object):
verse_type = VerseType.from_loose_input(verse_type) verse_type = VerseType.from_loose_input(verse_type)
verse_number = int(verse_number) verse_number = int(verse_number)
song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics']) song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics'])
verse_order.append('%s%s' % (VerseType.tags[verse_type], verse_number)) verse_order.append('{tag}{number}'.format(tag=VerseType.tags[verse_type], number=verse_number))
db_song.verse_order = ' '.join(verse_order) db_song.verse_order = ' '.join(verse_order)
db_song.lyrics = song_xml.extract_xml() db_song.lyrics = song_xml.extract_xml()
clean_song(self.db_manager, db_song) clean_song(self.db_manager, db_song)

View File

@ -74,8 +74,8 @@ class SongsTab(SettingsTab):
'Import missing songs from service files')) 'Import missing songs from service files'))
self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer')) self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab', self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab',
'Display "%s" symbol before copyright info') % 'Display "{symbol}" symbol before copyright '
SongStrings.CopyrightSymbol) 'info').format(symbol=SongStrings.CopyrightSymbol))
def on_search_as_type_check_box_changed(self, check_state): def on_search_as_type_check_box_changed(self, check_state):
self.song_search = (check_state == QtCore.Qt.Checked) self.song_search = (check_state == QtCore.Qt.Checked)

View File

@ -265,3 +265,22 @@ class TestProjectorDB(TestCase):
'manufacturer="IN YOUR DREAMS", model="OpenLP", other="None", sources="None", ' 'manufacturer="IN YOUR DREAMS", model="OpenLP", other="None", sources="None", '
'source_list="[]") >', 'source_list="[]") >',
'Projector.__repr__() should have returned a proper representation string') 'Projector.__repr__() should have returned a proper representation string')
def projectorsource_repr_test(self):
"""
Test ProjectorSource.__repr__() text
"""
# GIVEN: test setup
projector1 = Projector(**TEST1_DATA)
self.projector.add_projector(projector1)
item = self.projector.get_projector_by_id(projector1.id)
item_id = item.id
# WHEN: A source entry is saved for item
source = ProjectorSource(projector_id=item_id, code='11', text='First RGB source')
self.projector.add_source(source)
# THEN: __repr__ should return a proper string
self.assertEqual(str(source),
'<ProjectorSource(id="1", code="11", text="First RGB source", projector_id="1")>',
'ProjectorSource.__repr__)_ should have returned a proper representation string')