This commit is contained in:
Tim Bentley 2016-06-01 19:34:31 +01:00
commit e78fd2f503
51 changed files with 511 additions and 309 deletions

View File

@ -58,7 +58,7 @@ SocketSTate = QAbstractSocket.SocketState
PJLINK_PREFIX = '%'
PJLINK_CLASS = '1'
PJLINK_HEADER = '%s%s' % (PJLINK_PREFIX, PJLINK_CLASS)
PJLINK_HEADER = '{prefix}{linkclass}'.format(prefix=PJLINK_PREFIX, linkclass=PJLINK_CLASS)
PJLINK_SUFFIX = CR
@ -160,8 +160,10 @@ class PJLink1(QTcpSocket):
self.source = None
self.other_info = None
if hasattr(self, 'timer'):
log.debug('({ip}): Calling timer.stop()'.format(ip=self.ip))
self.timer.stop()
if hasattr(self, 'socket_timer'):
log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
self.socket_timer.stop()
self.send_queue = []
self.send_busy = False

View File

@ -134,6 +134,7 @@ def format_milliseconds(milliseconds):
:param milliseconds: Milliseconds to format
:return: Time string in format: hh.mm.ss,ttt
"""
milliseconds = int(milliseconds)
seconds, millis = divmod(milliseconds, 1000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)

View File

@ -407,7 +407,7 @@ class BibleMediaItem(MediaManagerItem):
self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id'])
def initialise_chapter_verse(self, bible, book, book_ref_id):
log.debug('initialise_chapter_verse {bible}, {book), {ref}'.format(bible=bible, book=book, ref=book_ref_id))
log.debug('initialise_chapter_verse {bible}, {book}, {ref}'.format(bible=bible, book=book, ref=book_ref_id))
book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)

View File

@ -141,7 +141,8 @@ class HttpRouter(RegistryProperties):
"""
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:
self.auth = base64.b64encode(auth_code)
except TypeError:
@ -189,7 +190,7 @@ class HttpRouter(RegistryProperties):
if self.headers['Authorization'] is None:
self.do_authorisation()
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.call_function(function, *args)
else:
@ -231,7 +232,7 @@ class HttpRouter(RegistryProperties):
for route, func in self.routes:
match = re.match(route, url_path_split.path)
if match:
log.debug('Route "%s" matched "%s"', route, url_path)
log.debug('Route "{route}" matched "{path}"'.format(route=route, path=url_path))
args = []
for param in match.groups():
args.append(param)
@ -319,9 +320,9 @@ class HttpRouter(RegistryProperties):
stage = translate('RemotePlugin.Mobile', 'Stage View')
live = translate('RemotePlugin.Mobile', 'Live View')
self.template_vars = {
'app_title': "%s %s" % (UiStrings().OLPV2x, remote),
'stage_title': "%s %s" % (UiStrings().OLPV2x, stage),
'live_title': "%s %s" % (UiStrings().OLPV2x, live),
'app_title': "{main} {remote}".format(main=UiStrings().OLPV2x, remote=remote),
'stage_title': "{main} {stage}".format(main=UiStrings().OLPV2x, stage=stage),
'live_title': "{main} {live}".format(main=UiStrings().OLPV2x, live=live),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
@ -354,7 +355,7 @@ class HttpRouter(RegistryProperties):
:param file_name: file name with path
:return:
"""
log.debug('serve file request %s' % file_name)
log.debug('serve file request {name}'.format(name=file_name))
parts = file_name.split('/')
if len(parts) == 1:
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)
else:
file_handle = open(path, 'rb')
log.debug('Opened %s' % path)
log.debug('Opened {path}'.format(path=path))
content = file_handle.read()
except IOError:
log.exception('Failed to open %s' % path)
log.exception('Failed to open {path}'.format(path=path))
return self.do_not_found()
finally:
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.
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:
file_name = 'index.html'
if '.' not in file_name:
@ -433,7 +434,9 @@ class HttpRouter(RegistryProperties):
:param dimensions: image size
: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']
# -1 means use the default dimension in ImageManager
width = -1
@ -539,7 +542,7 @@ class HttpRouter(RegistryProperties):
: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
data = []
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 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:
try:
data = json.loads(self.request_data)['request']['id']
@ -623,7 +627,7 @@ class HttpRouter(RegistryProperties):
: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:
try:
data = int(json.loads(self.request_data)['request']['id'])
@ -680,7 +684,7 @@ class HttpRouter(RegistryProperties):
return self.do_http_error()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
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()
def add_to_service(self, plugin_name):
@ -696,5 +700,5 @@ class HttpRouter(RegistryProperties):
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
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()

View File

@ -136,11 +136,13 @@ class OpenLPServer(RegistryProperties):
while loop < 4:
try:
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
except OSError:
log.debug("failed to start http server thread state %d %s" %
(loop, self.http_thread.isRunning()))
log.debug("failed to start http server thread state "
"{loop:d} {running}".format(loop=loop, running=self.http_thread.isRunning()))
loop += 1
time.sleep(0.1)
except:

View File

@ -192,14 +192,14 @@ class RemoteTab(SettingsTab):
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.android_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the '
'Android app from Google Play.') %
'https://play.google.com/store/apps/details?id=org.openlp.android2')
translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
'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_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the '
'iOS app from the App Store.') %
'https://itunes.apple.com/app/id1096218725')
translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
self.https_error_label.setText(
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
"""
ip_address = self.get_ip_address(self.address_edit.text())
http_url = 'http://%s:%s/' % (ip_address, self.port_spin_box.value())
https_url = 'https://%s:%s/' % (ip_address, self.https_port_spin_box.value())
self.remote_url.setText('<a href="%s">%s</a>' % (http_url, http_url))
self.remote_https_url.setText('<a href="%s">%s</a>' % (https_url, https_url))
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.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="{url}">{url}</a>'.format(url=http_url))
self.remote_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url))
http_url_temp = http_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_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.stage_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
http_url_temp = http_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_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.live_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
def get_ip_address(self, ip_address):
"""

View File

@ -130,6 +130,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
Song wizard localisation.
"""
self.setWindowTitle(translate('Wizard', 'Wizard'))
# TODO: Check format() using template strings
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
'Welcome to the Duplicate Song Removal Wizard'))
self.information_label.setText(
@ -148,8 +149,8 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
Set the wizard review page header text.
"""
self.review_page.setTitle(
translate('Wizard', 'Review duplicate songs (%s/%s)') %
(self.review_current_count, self.review_total_count))
translate('Wizard', 'Review duplicate songs ({current}/{total})').format(current=self.review_current_count,
total=self.review_total_count))
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
"""
log.info('%s EditSongForm loaded', __name__)
log.info('{name} EditSongForm loaded'.format(name=__name__))
def __init__(self, media_item, parent, manager):
"""
@ -185,20 +185,23 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
verse = verse.data(QtCore.Qt.UserRole)
if verse not in verse_names:
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):
if item not in verses:
invalid_verses.append(order_names[count])
if invalid_verses:
valid = create_separated_list(verse_names)
if len(invalid_verses) > 1:
msg = translate('SongsPlugin.EditSongForm', 'There are no verses corresponding to "%(invalid)s". '
'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \
{'invalid': ', '.join(invalid_verses), 'valid': valid}
msg = translate('SongsPlugin.EditSongForm',
'There are no verses corresponding to "{invalid}". Valid entries are {valid}.\n'
'Please enter the verses separated by spaces.'
).format(invalid=', '.join(invalid_verses), valid=valid)
else:
msg = translate('SongsPlugin.EditSongForm', 'There is no verse corresponding to "%(invalid)s".'
'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \
{'invalid': invalid_verses[0], 'valid': valid}
msg = translate('SongsPlugin.EditSongForm',
'There is no verse corresponding to "{invalid}". Valid entries are {valid}.\n'
'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'),
message=msg)
return len(invalid_verses) == 0
@ -242,23 +245,24 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
field = item.data(QtCore.Qt.UserRole)
verse_tags.append(field)
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:
critical_error_message_box(
message=translate('SongsPlugin.EditSongForm',
'There are misplaced formatting tags in the following verses:\n\n%s\n\n'
'Please correct these tags before continuing.' % ', '.join(misplaced_tags)))
'There are misplaced formatting tags in the following verses:\n\n{tag}\n\n'
'Please correct these tags before continuing.').format(tag=', '.join(misplaced_tags)))
return False
for tag in verse_tags:
if verse_tags.count(tag) > 26:
# lp#1310523: OpenLyrics allows only a-z variants of one verse:
# http://openlyrics.info/dataformat.html#verse-name
critical_error_message_box(message=translate(
'SongsPlugin.EditSongForm', 'You have %(count)s verses named %(name)s %(number)s. '
'You can have at most 26 verses with the same name' %
{'count': verse_tags.count(tag),
'name': VerseType.translated_name(tag[0]),
'number': tag[1:]}))
'SongsPlugin.EditSongForm',
'You have {count} verses named {name} {number}. You can have at most '
'26 verses with the same name').format(count=verse_tags.count(tag),
name=VerseType.translated_name(tag[0]),
number=tag[1:]))
return False
return True
@ -313,7 +317,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)',
r'\g<1>1\2', self.song.verse_order)
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
def keyPressEvent(self, event):
@ -492,7 +496,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
verse[0]['type'] = VerseType.tags[index]
if verse[0]['label'] == '':
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.setData(QtCore.Qt.UserRole, verse_def)
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):
self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1)
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)
self.verse_list_widget.setItem(count, 0, item)
if self.song.verse_order:
@ -514,7 +518,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
if verse_index is None:
verse_index = VerseType.from_tag(verse_def[0])
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))
else:
self.verse_order_edit.setText('')
@ -554,7 +558,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
item = self.verse_list_widget.item(row, 0)
verse_def = item.data(QtCore.Qt.UserRole)
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)
self.verse_list_widget.setVerticalHeaderLabels(row_label)
self.verse_list_widget.resizeRowsToContents()
@ -742,7 +746,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.verse_form.set_verse('', True)
if self.verse_form.exec():
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.setData(QtCore.Qt.UserRole, verse_def)
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)
if self.verse_form.exec():
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.setText(after_text)
# 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)
verse_tag = VerseType.translated_name(field[0])
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 += '\n'
self.verse_form.set_verse(verse_list)
@ -828,7 +832,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
verse_num = match.group(1)
else:
verse_num = '1'
verse_def = '%s%s' % (verse_tag, verse_num)
verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num)
else:
if parts.endswith('\n'):
parts = parts.rstrip('\n')
@ -919,7 +923,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
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)'), '',
filters)
for filename in file_names:
@ -1027,7 +1031,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
for item in order_text.split():
verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])]
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.ccli_number = self.ccli_number_edit.text()
theme_name = self.theme_combo_box.currentText()
@ -1082,12 +1086,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
try:
os.remove(audio)
except:
log.exception('Could not remove file: %s', audio)
log.exception('Could not remove file: {audio}'.format(audio=audio))
if not files:
try:
os.rmdir(save_path)
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)
self.manager.save_object(self.song)
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:
self.verse_text_edit.insertPlainText('\n')
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()
def on_split_button_clicked(self):
@ -107,7 +107,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
self.verse_type_combo_box.currentIndex()]
if not text:
return
position = text.rfind('---[%s' % verse_name, 0, position)
position = text.rfind('---[{verse}'.format(verse=verse_name), 0, position)
if position == -1:
self.verse_number_box.setValue(1)
return
@ -124,7 +124,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
verse_num = 1
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
@ -142,7 +142,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
self.insert_button.setVisible(False)
else:
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_number_box.setValue(1)
self.insert_button.setVisible(True)
@ -167,5 +167,5 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
"""
text = self.verse_text_edit.toPlainText()
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

View File

@ -34,7 +34,7 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
"""
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):
super(MediaFilesForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)

View File

@ -143,6 +143,7 @@ class SongExportForm(OpenLPWizard):
Song wizard localisation.
"""
self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard'))
# TODO: Verify format() with template variables
self.title_label.setText(WizardStrings.HeaderStyle %
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
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.setSubTitle(translate('SongsPlugin.ExportWizardForm',
'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.check_button.setText(translate('SongsPlugin.ExportWizardForm', 'Check All'))
self.export_song_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory'))
@ -223,7 +224,7 @@ class SongExportForm(OpenLPWizard):
if song.temporary:
continue
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.setData(QtCore.Qt.UserRole, song)
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.'))
except OSError as ose:
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):
"""

View File

@ -132,6 +132,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
Song wizard localisation.
"""
self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard'))
# TODO: Verify format() with template variables
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
'Welcome to the Song Import Wizard'))
self.information_label.setText(
@ -236,7 +237,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
"""
if filters:
filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
file_names = FileDialog.getOpenFileNames(
self, title,
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')
file_path_edit = self.format_widgets[this_format]['file_path_edit']
if select_mode == SongFormatSelect.SingleFile:
# TODO: Verify format() with template variables
self.get_file_name(
WizardStrings.OpenTypeFile % format_name, file_path_edit, 'last directory import', ext_filter)
elif select_mode == SongFormatSelect.SingleFolder:
# TODO: Verify format() with template variables
self.get_folder(WizardStrings.OpenTypeFolder % format_name, file_path_edit, 'last directory import')
def on_add_button_clicked(self):
@ -283,6 +286,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
this_format = self.current_format
select_mode, format_name, ext_filter, custom_title = \
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
if select_mode == SongFormatSelect.MultipleFiles:
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.sort(key=get_book_key)
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)
self.song_books_list_widget.addItem(book_name)
@ -310,11 +311,12 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
else:
critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box(message=translate(
'SongsPlugin.SongMaintenanceForm', 'The author %s already exists. Would you like to make songs with '
'author %s use the existing author %s?') %
(author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \
QtWidgets.QMessageBox.Yes:
elif critical_error_message_box(
message=translate(
'SongsPlugin.SongMaintenanceForm',
'The author {original} already exists. Would you like to make songs with author {new} use the '
'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)
else:
# 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(
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm',
'The topic %s already exists. Would you like to make songs with topic %s use the '
'existing topic %s?') % (topic.name, temp_name, topic.name),
message=translate('SongsPlugin.SongMaintenanceForm',
'The topic {original} already exists. Would you like to make songs with '
'topic {new} use the existing topic {original}?').format(original=topic.name,
new=temp_name),
parent=self, question=True) == QtWidgets.QMessageBox.Yes:
self._merge_objects(topic, self.merge_topics, self.reset_topics)
else:
@ -384,9 +387,10 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm',
'The book %s already exists. Would you like to make '
'songs with book %s use the existing book %s?') % (book.name, temp_name, book.name),
message=translate('SongsPlugin.SongMaintenanceForm',
'The book {original} already exists. Would you like to make songs with '
'book {new} use the existing book {original}?').format(original=book.name,
new=temp_name),
parent=self, question=True) == QtWidgets.QMessageBox.Yes:
self._merge_objects(book, self.merge_song_books, self.reset_song_books)
else:

View File

@ -242,7 +242,8 @@ class Ui_SongSelectDialog(object):
self.search_label.setText(translate('SongsPlugin.SongSelectForm', 'Search Text:'))
self.search_button.setText(translate('SongsPlugin.SongSelectForm', 'Search'))
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.view_button.setText(translate('SongsPlugin.SongSelectForm', 'View'))
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.set_progress_visible(True)
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.song_count = 0
search_history = self.search_combobox.getItems()
@ -343,7 +344,8 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
:param song:
"""
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']) + ')'
song_item = QtWidgets.QListWidgetItem(item_title, self.search_results_widget)
song_item.setData(QtCore.Qt.UserRole, song)

View File

@ -534,11 +534,11 @@ def delete_song(song_id, song_plugin):
try:
os.remove(media_file.file_name)
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:
save_path = os.path.join(AppLocation.get_section_data_path(song_plugin.name), 'audio', str(song_id))
if os.path.exists(save_path):
os.rmdir(save_path)
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)

View File

@ -39,7 +39,7 @@ class Author(BaseModel):
"""
def get_display_name(self, author_type=None):
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
@ -105,7 +105,9 @@ class Book(BaseModel):
Book model
"""
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):
@ -187,7 +189,7 @@ class SongBookEntry(BaseModel):
@staticmethod
def get_display_name(songbook_name, entry):
if entry:
return "%s #%s" % (songbook_name, entry)
return "{name} #{entry}".format(name=songbook_name, entry=entry)
return songbook_name

View File

@ -56,13 +56,13 @@ try:
from .importers.songsoffellowship import SongsOfFellowshipImport
HAS_SOF = True
except ImportError:
log.exception('Error importing %s', 'SongsOfFellowshipImport')
log.exception('Error importing {text}'.format(text='SongsOfFellowshipImport'))
HAS_SOF = False
try:
from .importers.openoffice import OpenOfficeImport
HAS_OOO = True
except ImportError:
log.exception('Error importing %s', 'OooImport')
log.exception('Error importing {text}'.format(text='OooImport'))
HAS_OOO = False
HAS_MEDIASHOUT = False
if is_win():
@ -70,21 +70,21 @@ if is_win():
from .importers.mediashout import MediaShoutImport
HAS_MEDIASHOUT = True
except ImportError:
log.exception('Error importing %s', 'MediaShoutImport')
log.exception('Error importing {text}'.format(text='MediaShoutImport'))
HAS_WORSHIPCENTERPRO = False
if is_win():
try:
from .importers.worshipcenterpro import WorshipCenterProImport
HAS_WORSHIPCENTERPRO = True
except ImportError:
log.exception('Error importing %s', 'WorshipCenterProImport')
log.exception('Error importing {text}'.format(text='WorshipCenterProImport'))
HAS_OPSPRO = False
if is_win():
try:
from .importers.opspro import OPSProImport
HAS_OPSPRO = True
except ImportError:
log.exception('Error importing %s', 'OPSProImport')
log.exception('Error importing {text}'.format(text='OPSProImport'))
class SongFormatSelect(object):
@ -198,7 +198,7 @@ class SongFormat(object):
'class': OpenLyricsImport,
'name': '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')
},
OpenLP2: {
@ -206,7 +206,7 @@ class SongFormat(object):
'name': UiStrings().OLPV2,
'prefix': 'openLP2',
'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.sqlite)' % (translate('SongsPlugin.ImportWizardForm', 'OpenLP 2 Databases'))
'filter': '{text} (*.sqlite)'.format(text=translate('SongsPlugin.ImportWizardForm', 'OpenLP 2 Databases'))
},
Generic: {
'name': translate('SongsPlugin.ImportWizardForm', 'Generic Document/Presentation'),
@ -221,46 +221,50 @@ class SongFormat(object):
'class': CCLIFileImport,
'name': 'CCLI/SongSelect',
'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: {
'class': DreamBeamImport,
'name': 'DreamBeam',
'prefix': 'dreamBeam',
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'DreamBeam Song Files')
'filter': '{text} (*.xml)'.format(text=translate('SongsPlugin.ImportWizardForm', 'DreamBeam Song Files'))
},
EasySlides: {
'class': EasySlidesImport,
'name': 'EasySlides',
'prefix': 'easySlides',
'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'EasySlides XML File')
'filter': '{text} (*.xml)'.format(text=translate('SongsPlugin.ImportWizardForm', 'EasySlides XML File'))
},
EasyWorshipDB: {
'class': EasyWorshipSongImport,
'name': 'EasyWorship Song Database',
'prefix': 'ew',
'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.db)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Song Database')
'filter': '{text} (*.db)'.format(text=translate('SongsPlugin.ImportWizardForm',
'EasyWorship Song Database'))
},
EasyWorshipService: {
'class': EasyWorshipSongImport,
'name': 'EasyWorship Service File',
'prefix': 'ew',
'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.ews)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Service File')
'filter': '{text} (*.ews)'.format(text=translate('SongsPlugin.ImportWizardForm',
'EasyWorship Service File'))
},
FoilPresenter: {
'class': FoilPresenterImport,
'name': 'Foilpresenter',
'prefix': 'foilPresenter',
'filter': '%s (*.foil)' % translate('SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files')
'filter': '{text} (*.foil)'.format(text=translate('SongsPlugin.ImportWizardForm',
'Foilpresenter Song Files'))
},
Lyrix: {
'class': LyrixImport,
'name': '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)')
},
MediaShout: {
@ -268,7 +272,7 @@ class SongFormat(object):
'prefix': 'mediaShout',
'canDisable': True,
'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',
'The MediaShout importer is only supported on Windows. It has '
'been disabled due to a missing Python module. If you want to '
@ -285,7 +289,7 @@ class SongFormat(object):
'prefix': 'OPSPro',
'canDisable': True,
'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',
'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 '
@ -295,7 +299,7 @@ class SongFormat(object):
'class': PowerPraiseImport,
'name': 'PowerPraise',
'prefix': 'powerPraise',
'filter': '%s (*.ppl)' % translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files')
'filter': '{text} (*.ppl)'.format(text=translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files'))
},
PowerSong: {
'class': PowerSongImport,
@ -309,26 +313,29 @@ class SongFormat(object):
'class': PresentationManagerImport,
'name': 'PresentationManager',
'prefix': 'presentationManager',
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'PresentationManager Song Files')
'filter': '{text} (*.sng)'.format(text=translate('SongsPlugin.ImportWizardForm',
'PresentationManager Song Files'))
},
ProPresenter: {
'class': ProPresenterImport,
'name': 'ProPresenter 4, 5 and 6',
'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: {
'class': SongBeamerImport,
'name': 'SongBeamer',
'prefix': 'songBeamer',
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'SongBeamer Files')
'filter': '{text} (*.sng)'.format(text=translate('SongsPlugin.ImportWizardForm',
'SongBeamer Files'))
},
SongPro: {
'class': SongProImport,
'name': 'SongPro',
'prefix': 'songPro',
'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)'),
'descriptionText': translate('SongsPlugin.ImportWizardForm',
'In SongPro, export your songs using the File -> Export menu')
@ -337,13 +344,15 @@ class SongFormat(object):
'class': SongShowPlusImport,
'name': 'SongShow Plus',
'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: {
'name': 'Songs of Fellowship',
'prefix': 'songsOfFellowship',
'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',
'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.')
@ -352,30 +361,33 @@ class SongFormat(object):
'class': SundayPlusImport,
'name': 'SundayPlus',
'prefix': 'sundayPlus',
'filter': '%s (*.ptf)' % translate('SongsPlugin.ImportWizardForm', 'SundayPlus Song Files')
'filter': '{text} (*.ptf)'.format(text=translate('SongsPlugin.ImportWizardForm', 'SundayPlus Song Files'))
},
VideoPsalm: {
'class': VideoPsalmImport,
'name': 'VideoPsalm',
'prefix': 'videopsalm',
'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'),
'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: {
'class': WordsOfWorshipImport,
'name': 'Words of Worship',
'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: {
'class': WorshipAssistantImport,
'name': 'Worship Assistant 0',
'prefix': 'worshipAssistant',
'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)'),
'descriptionText': translate('SongsPlugin.ImportWizardForm',
'In Worship Assistant, export your Database to a CSV file.')
@ -385,7 +397,8 @@ class SongFormat(object):
'prefix': 'worshipCenterPro',
'canDisable': True,
'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',
'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 '

View File

@ -58,7 +58,7 @@ class CCLIFileImport(SongImport):
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source:
filename = str(filename)
log.debug('Importing CCLI File: %s', filename)
log.debug('Importing CCLI File: {name}'.format(name=filename))
if os.path.isfile(filename):
detect_file = open(filename, 'rb')
detect_content = detect_file.read(2048)
@ -76,17 +76,17 @@ class CCLIFileImport(SongImport):
infile.close()
ext = os.path.splitext(filename)[1]
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):
self.log_error(filename)
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):
self.log_error(filename)
else:
self.log_error(filename, translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid '
'extension.'))
log.info('Extension %s is not valid', filename)
log.info('Extension {name} is not valid'.format(name=filename))
if self.stop_import_flag:
return
@ -146,7 +146,7 @@ class CCLIFileImport(SongImport):
: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_topics = ''
for line in text_list:
@ -193,7 +193,7 @@ class CCLIFileImport(SongImport):
if check_first_verse_line:
if verse_lines[0].startswith('(PRE-CHORUS'):
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]
elif verse_lines[0].startswith('(BRIDGE'):
verse_type = VerseType.tags[VerseType.Bridge]
@ -248,7 +248,7 @@ class CCLIFileImport(SongImport):
# 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
check_first_verse_line = False
verse_text = ''

View File

@ -90,7 +90,7 @@ class DreamBeamImport(SongImport):
try:
parsed_file = etree.parse(open(file, 'r'), parser)
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)
continue
xml = etree.tostring(parsed_file).decode()
@ -115,15 +115,17 @@ class DreamBeamImport(SongImport):
verse_type = lyrics_item.get('Type')
verse_number = lyrics_item.get('Number')
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'):
self.song_book_name = str(song_xml.Collection.text)
if hasattr(song_xml, 'Number'):
self.song_number = str(song_xml.Number.text)
if hasattr(song_xml, 'Sequence'):
for lyrics_sequence_item in (song_xml.Sequence.iterchildren()):
self.verse_order_list.append("%s%s" % (lyrics_sequence_item.get('Type')[:1],
lyrics_sequence_item.get('Number')))
item = lyrics_sequence_item.get('Type')[:1]
self.verse_order_list.append("{item}{number}".format(item=item),
lyrics_sequence_item.get('Number'))
if hasattr(song_xml, 'Notes'):
self.comments = str(song_xml.Notes.text)
else:

View File

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

View File

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

View File

@ -121,6 +121,7 @@ class FoilPresenterImport(SongImport):
for file_path in self.import_source:
if self.stop_import_flag:
return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
try:
parsed_file = etree.parse(file_path, parser)
@ -128,7 +129,7 @@ class FoilPresenterImport(SongImport):
self.foil_presenter.xml_to_song(xml)
except etree.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):

View File

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

View File

@ -23,6 +23,10 @@
The :mod:`mediashout` module provides the functionality for importing
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
from openlp.core.lib import translate
@ -47,8 +51,8 @@ class MediaShoutImport(SongImport):
Receive a single file to import.
"""
try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=6NOZ4eHK7k' %
self.import_source)
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
'PWD=6NOZ4eHK7k'.format(sorce=self.import_source))
except:
# Unfortunately no specific exception type
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
@ -61,16 +65,15 @@ class MediaShoutImport(SongImport):
for song in songs:
if self.stop_import_flag:
break
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = %s ORDER BY Type, Number'
% song.Record)
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', song.Record)
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()
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()
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()
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',
'Not a valid OpenLP 2 song database.'))
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
engine = create_engine(self.import_source)
source_meta = MetaData()
@ -239,8 +239,10 @@ class OpenLPSongImport(SongImport):
self.manager.save_object(new_song)
if progress_dialog:
progress_dialog.setValue(progress_dialog.value() + 1)
# TODO: Verify format() with template strings
progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title)
else:
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
if self.stop_import_flag:
break

View File

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

View File

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

View File

@ -254,8 +254,8 @@ class OpenSongImport(SongImport):
length = 0
while length < len(verse_num) and verse_num[length].isnumeric():
length += 1
verse_def = '%s%s' % (verse_tag, verse_num[:length])
verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num[:length])
verse_joints[verse_def] = '{verse}\n[---]\n{lines}'.format(verse=verse_joints[verse_def], lines=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
# 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_index = VerseType.from_loose_input(verse_tag)
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, {}):
self.verse_order_list.append(verse_def)
else:
log.info('Got order %s but not in verse tags, dropping this item from presentation order',
verse_def)
log.info('Got order {order} but not in verse tags, dropping this item from presentation '
'order'.format(order=verse_def))
if not self.finish():
self.log_error(file.name)

View File

@ -23,6 +23,10 @@
The :mod:`opspro` module provides the functionality for importing
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 re
import pyodbc
@ -51,10 +55,11 @@ class OPSProImport(SongImport):
"""
password = self.extract_mdb_password()
try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=%s' % (self.import_source,
password))
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
'PWD={password}'.format(source=self.import_source, password=password))
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
self.log_error(self.import_source, translate('SongsPlugin.OPSProImport',
'Unable to connect the OPS Pro database.'))
@ -68,19 +73,19 @@ class OPSProImport(SongImport):
if self.stop_import_flag:
break
# Type means: 0=Original, 1=Projection, 2=Own
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = %d AND Type < 2 '
'ORDER BY Type DESC' % song.ID)
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = ? AND Type < 2 '
'ORDER BY Type DESC', song.ID)
lyrics = cursor.fetchone()
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = %d '
'ORDER BY CategoryName' % song.ID)
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = ? '
'ORDER BY CategoryName', song.ID)
topics = cursor.fetchall()
try:
self.process_song(song, lyrics, topics)
except Exception as e:
self.log_error(self.import_source,
translate('SongsPlugin.OPSProImport', '"%s" could not be imported. %s')
% (song.Title, e))
translate('SongsPlugin.OPSProImport',
'"{title}" could not be imported. {error}').format(title=song.Title, error=e))
def process_song(self, song, lyrics, topics):
"""

View File

@ -41,6 +41,7 @@ class PowerPraiseImport(SongImport):
for file_path in self.import_source:
if self.stop_import_flag:
return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
root = objectify.parse(open(file_path, 'rb')).getroot()
self.process_song(root)
@ -66,7 +67,7 @@ class PowerPraiseImport(SongImport):
else:
verse_def = 'o'
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 = []
for slide in part.slide:
if not hasattr(slide, 'line'):

View File

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

View File

@ -44,6 +44,7 @@ class PresentationManagerImport(SongImport):
for file_path in self.import_source:
if self.stop_import_flag:
return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
try:
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
@ -90,7 +91,7 @@ class PresentationManagerImport(SongImport):
verse_def = 'o'
if not is_duplicate: # Only increment verse number if no duplicate
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
self.add_verse(str(verse).strip(), verse_def)
verse_order_list.append(verse_def)

View File

@ -46,6 +46,7 @@ class ProPresenterImport(SongImport):
for file_path in self.import_source:
if self.stop_import_flag:
return
# TODO: Verify format() with template strings
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
root = objectify.parse(open(file_path, 'rb')).getroot()
self.process_song(root, file_path)
@ -87,7 +88,7 @@ class ProPresenterImport(SongImport):
RTFData = slide.displayElements.RVTextElement.get('RTFData')
rtf = base64.standard_b64decode(RTFData)
words, encoding = strip_rtf(rtf.decode())
self.add_verse(words, "v%d" % count)
self.add_verse(words, "v{count}".format(count=count))
# ProPresenter 5
elif(self.version >= 500 and self.version < 600):
@ -103,7 +104,7 @@ class ProPresenterImport(SongImport):
RTFData = slide.displayElements.RVTextElement.get('RTFData')
rtf = base64.standard_b64decode(RTFData)
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
elif(self.version >= 600 and self.version < 700):
@ -127,7 +128,7 @@ class ProPresenterImport(SongImport):
words, encoding = strip_rtf(data.decode())
break
if words:
self.add_verse(words, "v%d" % count)
self.add_verse(words, "v{count:d}".format(count=count))
if not self.finish():
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_copy_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):
"""
@ -326,10 +326,11 @@ class SongImport(QtCore.QObject):
if not self.check_complete():
self.set_defaults()
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.title = self.title
if self.import_wizard is not None:
# TODO: Verify format() with template variables
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % song.title)
song.alternate_title = self.alternate_title
# Values will be set when cleaning the song.
@ -344,11 +345,11 @@ class SongImport(QtCore.QObject):
if verse_def[0].lower() in VerseType.tags:
verse_tag = verse_def[0].lower()
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
other_count += 1
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
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
song.lyrics = str(sxml.extract_xml(), 'utf-8')

View File

@ -101,6 +101,7 @@ class SongShowPlusImport(SongImport):
self.other_count = 0
self.other_list = {}
file_name = os.path.split(file)[1]
# TODO: Verify format() with template variables
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % file_name, 0)
song_data = open(file, 'rb')
while True:
@ -145,13 +146,16 @@ class SongShowPlusImport(SongImport):
if match:
self.ccli_number = int(match.group())
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:
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:
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:
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:
self.topics.append(self.decode(data))
elif block_key == COMMENTS:
@ -170,7 +174,7 @@ class SongShowPlusImport(SongImport):
verse_tag = self.to_openlp_verse_tag(verse_name)
self.add_verse(self.decode(data), verse_tag)
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)
self.verse_order_list = self.ssp_verse_order_list
song_data.close()

View File

@ -141,7 +141,7 @@ class SundayPlusImport(SongImport):
if len(value):
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']:
verse_type = "%s%s" % (verse_type, value[-1])
verse_type = "{verse}{value}".format(verse=verse_type, value=value[-1])
elif name == 'HOTKEY':
value = self.decode(value).strip()
# HOTKEY always appears after MARKER_NAME, so it

View File

@ -115,8 +115,8 @@ class VideoPsalmImport(SongImport):
for verse in song['Verses']:
self.add_verse(verse['Text'], 'v')
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:
self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File %s' % file.name),
translate('SongsPlugin.VideoPsalmImport', 'Error: %s') % e)
self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File {name}').format(name=file.name),
translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
song_file.close()

View File

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

View File

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

View File

@ -49,9 +49,11 @@ class WorshipCenterProImport(SongImport):
Receive a single file to import.
"""
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:
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
self.log_error(self.import_source, translate('SongsPlugin.WorshipCenterProImport',
'Unable to connect the WorshipCenter Pro database.'))

View File

@ -84,10 +84,11 @@ class ZionWorxImport(SongImport):
records = list(songs_reader)
except csv.Error as e:
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
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)
for index, record in enumerate(records, 1):
if self.stop_import_flag:
@ -101,12 +102,12 @@ class ZionWorxImport(SongImport):
self.add_copyright(self._decode(record['Copyright']))
lyrics = self._decode(record['Lyrics'])
except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record {index}').format(index=index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: {error}').format(error=e))
continue
except TypeError as e:
self.log_error(translate(
'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx CSV format.'), 'TypeError: %s' % e)
self.log_error(translate('SongsPlugin.ZionWorxImport', 'File not valid ZionWorx CSV format.'),
'TypeError: {error}'.format(error=e))
return
verse = ''
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')
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.maintenance_action.setText(SongStrings.SongMaintenance)
self.maintenance_action.setToolTip(translate('SongsPlugin.MediaItem',
@ -166,12 +166,14 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', '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()
def on_search_text_button_clicked(self):
# 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.
search_keywords = str(self.search_text_edit.displayText())
search_type = self.search_text_edit.current_search_type()
@ -181,31 +183,31 @@ class SongMediaItem(MediaManagerItem):
self.display_results_song(search_results)
elif search_type == SongSearch.Titles:
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))
self.display_results_song(search_results)
elif search_type == SongSearch.Lyrics:
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))
self.display_results_song(search_results)
elif search_type == SongSearch.Authors:
log.debug('Authors Search')
search_string = '%' + search_keywords + '%'
search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects(
Author, Author.display_name.like(search_string))
self.display_results_author(search_results)
elif search_type == SongSearch.Topics:
log.debug('Topics Search')
search_string = '%' + search_keywords + '%'
search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects(
Topic, Topic.name.like(search_string))
self.display_results_topic(search_results)
elif search_type == SongSearch.Books:
log.debug('Songbook Search')
search_keywords = search_keywords.rpartition(' ')
search_book = search_keywords[0] + '%'
search_entry = search_keywords[2] + '%'
search_book = '{text}%'.format(text=search_keywords[0])
search_entry = '{text}%'.format(text=search_keywords[2])
search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id)
.join(Song)
.join(Book)
@ -214,26 +216,26 @@ class SongMediaItem(MediaManagerItem):
self.display_results_book(search_results)
elif search_type == SongSearch.Themes:
log.debug('Theme Search')
search_string = '%' + search_keywords + '%'
search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects(
Song, Song.theme_name.like(search_string))
self.display_results_themes(search_results)
elif search_type == SongSearch.Copyright:
log.debug('Copyright Search')
search_string = '%' + search_keywords + '%'
search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects(
Song, and_(Song.copyright.like(search_string), Song.copyright != ''))
self.display_results_song(search_results)
elif search_type == SongSearch.CCLInumber:
log.debug('CCLI number Search')
search_string = '%' + search_keywords + '%'
search_string = '%{text}%'.format(text=search_keywords)
search_results = self.plugin.manager.get_all_objects(
Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != ''))
self.display_results_cclinumber(search_results)
self.check_search_result()
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(
Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string),
Song.comments.like(search_string)))
@ -272,7 +274,8 @@ class SongMediaItem(MediaManagerItem):
if song.temporary:
continue
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.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
@ -305,7 +308,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs
if song.temporary:
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.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
@ -325,7 +328,8 @@ class SongMediaItem(MediaManagerItem):
self.list_view.clear()
search_results.sort(key=get_songbook_key)
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.setData(QtCore.Qt.UserRole, result[3])
self.list_view.addItem(song_name)
@ -354,7 +358,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs
if song.temporary:
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.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
@ -377,7 +381,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs
if song.temporary:
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.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
@ -400,7 +404,7 @@ class SongMediaItem(MediaManagerItem):
# Do not display temporary songs
if song.temporary:
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.setData(QtCore.Qt.UserRole, song.id)
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
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)
valid = self.plugin.manager.get_object(Song, song_id)
if valid:
@ -499,7 +503,8 @@ class SongMediaItem(MediaManagerItem):
if QtWidgets.QMessageBox.question(
self, UiStrings().ConfirmDelete,
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.Yes) == QtWidgets.QMessageBox.No:
return
@ -524,8 +529,9 @@ class SongMediaItem(MediaManagerItem):
old_song = self.plugin.manager.get_object(Song, item_id)
song_xml = self.open_lyrics.song_to_xml(old_song)
new_song = self.open_lyrics.xml_to_song(song_xml)
new_song.title = '%s <%s>' % \
(new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
new_song.title = '{title} <{text}>'.format(title=new_song.title,
text=translate('SongsPlugin.MediaItem',
'copy', 'For song cloning'))
# Copy audio files from the old to the new song
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))
@ -552,7 +558,8 @@ class SongMediaItem(MediaManagerItem):
:param remote: Triggered from remote
: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)
service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.CanPreview)
@ -581,7 +588,7 @@ class SongMediaItem(MediaManagerItem):
if verse_index is None:
verse_index = VerseType.from_tag(verse_tag)
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)
else:
# Loop through the verse list and expand the song accordingly.
@ -596,7 +603,7 @@ class SongMediaItem(MediaManagerItem):
else:
verse_index = VerseType.from_tag(verse[0]['type'])
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.title = song.title
author_list = self.generate_footer(service_item, song)
@ -639,23 +646,24 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer = []
item.raw_footer.append(song.title)
if authors_none:
item.raw_footer.append("%s: %s" % (translate('OpenLP.Ui', 'Written by'),
create_separated_list(authors_none)))
item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
authors=create_separated_list(authors_none)))
if authors_words_music:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.WordsAndMusic],
create_separated_list(authors_words_music)))
item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic],
authors=create_separated_list(authors_words_music)))
if authors_words:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Words],
create_separated_list(authors_words)))
item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Words],
authors=create_separated_list(authors_words)))
if authors_music:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Music],
create_separated_list(authors_music)))
item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Music],
authors=create_separated_list(authors_music)))
if authors_translation:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation],
create_separated_list(authors_translation)))
item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation],
authors=create_separated_list(authors_translation)))
if song.copyright:
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:
item.raw_footer.append(song.copyright)
if self.display_songbook and song.songbook_entries:

View File

@ -61,18 +61,20 @@ class OpenLyricsExport(RegistryProperties):
if self.parent.stop_export_flag:
return False
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)
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)
# 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
conflicts = 0
while os.path.exists(os.path.join(self.save_path, filename_with_ext)):
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
# 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',

View File

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

View File

@ -78,7 +78,7 @@ class SongSelectImport(object):
try:
login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml')
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
if callback:
callback()
@ -92,7 +92,7 @@ class SongSelectImport(object):
try:
posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml')
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
if callback:
callback()
@ -105,7 +105,7 @@ class SongSelectImport(object):
try:
self.opener.open(LOGOUT_URL)
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):
"""
@ -127,7 +127,7 @@ class SongSelectImport(object):
results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml')
search_results = results_page.find_all('li', 'result pane')
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
if not search_results:
break
@ -158,7 +158,7 @@ class SongSelectImport(object):
try:
song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')
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
if callback:
callback()
@ -203,7 +203,7 @@ class SongSelectImport(object):
verse_type = VerseType.from_loose_input(verse_type)
verse_number = int(verse_number)
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.lyrics = song_xml.extract_xml()
clean_song(self.db_manager, db_song)

View File

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

View File

@ -81,9 +81,10 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
)
return
check_directory_exists(path)
file_name = translate('SongUsagePlugin.SongUsageDetailForm', 'usage_detail_%s_%s.txt') % \
(self.from_date_calendar.selectedDate().toString('ddMMyyyy'),
self.to_date_calendar.selectedDate().toString('ddMMyyyy'))
file_name = translate('SongUsagePlugin.SongUsageDetailForm',
'usage_detail_{old}_{new}.txt'
).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'),
new=self.to_date_calendar.selectedDate().toString('ddMMyyyy'))
Settings().setValue(self.plugin.settings_section + '/from date', self.from_date_calendar.selectedDate())
Settings().setValue(self.plugin.settings_section + '/to date', self.to_date_calendar.selectedDate())
usage = self.plugin.manager.get_all_objects(
@ -95,21 +96,23 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
try:
file_handle = open(report_file_name, 'wb')
for instance in usage:
record = '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
'\"%s\",\"%s\"\n' % \
(instance.usagedate, instance.usagetime, instance.title, instance.copyright,
instance.ccl_number, instance.authors, instance.plugin_name, instance.source)
record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'
'\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,
title=instance.title, copyright=instance.copyright,
ccli=instance.ccl_number, authors=instance.authors,
name=instance.plugin_name, source=instance.source)
file_handle.write(record.encode('utf-8'))
self.main_window.information_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
translate('SongUsagePlugin.SongUsageDetailForm',
'Report \n%s \nhas been successfully created. ') % report_file_name
'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
)
except OSError as ose:
log.exception('Failed to write out song usage records')
critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
translate('SongUsagePlugin.SongUsageDetailForm',
'An error occurred while creating the report: %s') % ose.strerror)
'An error occurred while creating the report: {error}'
).format(error=ose.strerror))
finally:
if file_handle:
file_handle.close()

View File

@ -25,7 +25,8 @@ Test the registry properties
from unittest import TestCase
from openlp.core.common import Registry, RegistryProperties
from tests.functional import MagicMock
from tests.functional import MagicMock, patch
class TestRegistryProperties(TestCase, RegistryProperties):
@ -53,7 +54,25 @@ class TestRegistryProperties(TestCase, RegistryProperties):
"""
# GIVEN an Empty Registry
application = MagicMock()
# WHEN the application is registered
Registry().register('application', application)
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')
@patch('openlp.core.common.registryproperties.is_win')
def application_on_windows_test(self, mocked_is_win):
"""
Test property if registry value assigned on Windows
"""
# GIVEN an Empty Registry and we're on Windows
application = MagicMock()
mocked_is_win.return_value = True
# WHEN the application is registered
Registry().register('application', application)
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')

View File

@ -35,6 +35,20 @@ from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUT
pjlink_test = PJLink1(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
class DummyTimer(object):
'''
Dummy class to fake timers
'''
def __init__(self, *args, **kwargs):
pass
def start(self, *args, **kwargs):
pass
def stop(self, *args, **kwargs):
pass
class TestPJLink(TestCase):
"""
Tests for the PJLink module
@ -43,13 +57,10 @@ class TestPJLink(TestCase):
@patch.object(pjlink_test, 'send_command')
@patch.object(pjlink_test, 'waitForReadyRead')
@patch('openlp.core.common.qmd5_hash')
def authenticated_connection_call_test(self,
mock_qmd5_hash,
mock_waitForReadyRead,
mock_send_command,
def authenticated_connection_call_test(self, mock_qmd5_hash, mock_waitForReadyRead, mock_send_command,
mock_readyRead):
"""
Fix for projector connect with PJLink authentication exception. Ticket 92187.
Ticket 92187: Fix for projector connect with PJLink authentication exception.
"""
# GIVEN: Test object
pjlink = pjlink_test
@ -63,9 +74,23 @@ class TestPJLink(TestCase):
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
"Connection request should have been called with TEST_PIN"))
def projector_class_test(self):
"""
Test class version from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
# WHEN: Process class response
pjlink.process_clss('1')
# THEN: Projector class should be set to 1
self.assertEquals(pjlink.pjlink_class, '1',
'Projector should have returned class=1')
def non_standard_class_reply_test(self):
"""
bugfix 1550891 - CLSS request returns non-standard 'Class N' reply
Bugfix 1550891: CLSS request returns non-standard 'Class N' reply
"""
# GIVEN: Test object
pjlink = pjlink_test
@ -264,3 +289,46 @@ class TestPJLink(TestCase):
# THEN: Input selected should reflect current input
self.assertEquals(pjlink.source, '1', 'Input source should be set to "1"')
def projector_reset_information_test(self):
"""
Test reset_information() resets all information and stops timers
"""
# GIVEN: Test object and test data
pjlink = pjlink_test
pjlink.power = S_ON
pjlink.pjlink_name = 'OPENLPTEST'
pjlink.manufacturer = 'PJLINK'
pjlink.model = '1'
pjlink.shutter = True
pjlink.mute = True
pjlink.lamp = True
pjlink.fan = True
pjlink.source_available = True
pjlink.other_info = 'ANOTHER TEST'
pjlink.send_queue = True
pjlink.send_busy = True
pjlink.timer = DummyTimer()
pjlink.socket_timer = DummyTimer()
# WHEN: reset_information() is called
with patch.object(pjlink.timer, 'stop') as mock_timer:
with patch.object(pjlink.socket_timer, 'stop') as mock_socket_timer:
pjlink.reset_information()
# THEN: All information should be reset and timers stopped
self.assertEquals(pjlink.power, S_OFF, 'Projector power should be OFF')
self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
self.assertIsNone(pjlink.model, 'Projector model should be None')
self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
self.assertIsNone(pjlink.fan, 'Projector fan should be None')
self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
self.assertIsNone(pjlink.source, 'Projector source should be None')
self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
self.assertEquals(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')

View File

@ -265,3 +265,22 @@ class TestProjectorDB(TestCase):
'manufacturer="IN YOUR DREAMS", model="OpenLP", other="None", sources="None", '
'source_list="[]") >',
'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')