merge trunk

This commit is contained in:
Ken Roberts 2016-05-05 08:51:07 -07:00
commit 72fc30d600
23 changed files with 841 additions and 149 deletions

View File

@ -45,3 +45,4 @@ cover
*.kdev4
coverage
tags
output

View File

@ -45,6 +45,7 @@ class WizardStrings(object):
OS = 'OpenSong'
OSIS = 'OSIS'
ZEF = 'Zefania'
SWORD = 'Sword'
# These strings should need a good reason to be retranslated elsewhere.
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
FormatLabel = translate('OpenLP.Ui', 'Format:')
@ -113,7 +114,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
Set up the wizard UI.
:param image: path to start up image
"""
self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
self.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
self.setModal(True)
self.setOptions(QtWidgets.QWizard.IndependentPages |
QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.NoBackButtonOnLastPage)

View File

@ -247,7 +247,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
"""
Set up and build the output screen
"""
self.log_debug('Start MainDisplay setup (live = %s)' % self.is_live)
self.log_debug('Start MainDisplay setup (live = {islive})'.format(islive=self.is_live))
self.screen = self.screens.current
self.setVisible(False)
Display.setup(self)
@ -288,7 +288,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
self.application.process_events()
self.setGeometry(self.screen['size'])
if animate:
self.frame.evaluateJavaScript('show_text("%s")' % slide.replace('\\', '\\\\').replace('\"', '\\\"'))
# NOTE: Verify this works with ''.format()
_text = slide.replace('\\', '\\\\').replace('\"', '\\\"')
self.frame.evaluateJavaScript('show_text("{text}")'.format(text=_text))
else:
# This exists for https://bugs.launchpad.net/openlp/+bug/1016843
# For unknown reasons if evaluateJavaScript is called
@ -309,10 +311,10 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
text_prepared = expand_tags(html.escape(text)).replace('\\', '\\\\').replace('\"', '\\\"')
if self.height() != self.screen['size'].height() or not self.isVisible():
shrink = True
js = 'show_alert("%s", "%s")' % (text_prepared, 'top')
js = 'show_alert("{text}", "{top}")'.format(text=text_prepared, top='top')
else:
shrink = False
js = 'show_alert("%s", "")' % text_prepared
js = 'show_alert("{text}", "")'.format(text=text_prepared)
height = self.frame.evaluateJavaScript(js)
if shrink:
if text:
@ -368,7 +370,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
"""
self.setGeometry(self.screen['size'])
if image:
js = 'show_image("data:image/png;base64,%s");' % image
js = 'show_image("data:image/png;base64,{image}");'.format(image=image)
else:
js = 'show_image("");'
self.frame.evaluateJavaScript(js)
@ -492,7 +494,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
:param mode: How the screen is to be hidden
"""
self.log_debug('hide_display mode = %d' % mode)
self.log_debug('hide_display mode = {mode:d}'.format(mode=mode))
if self.screens.display_count == 1:
# Only make visible if setting enabled.
if not Settings().value('core/display on monitor'):

View File

@ -622,11 +622,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
:param version: The Version to be displayed.
"""
log.debug('version_notice')
version_text = translate('OpenLP.MainWindow', 'Version %s of OpenLP is now available for download (you are '
'currently running version %s). \n\nYou can download the latest version from '
'http://openlp.org/.')
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'),
version_text % (version, get_application_version()[u'full']))
version_text = translate('OpenLP.MainWindow', 'Version {new} of OpenLP is now available for download (you are '
'currently running version {current}). \n\nYou can download the latest version from '
'http://openlp.org/.').format(new=version, current=get_application_version()[u'full'])
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text)
def show(self):
"""
@ -642,7 +641,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
self.service_manager_contents.load_last_file()
# This will store currently used layout preset so it remains enabled on next startup.
# If any panel is enabled/disabled after preset is set, this setting is not saved.
view_mode = Settings().value('%s/view mode' % self.general_settings_section)
view_mode = Settings().value('{section}/view mode'.format(section=self.general_settings_section))
if view_mode == 'default' and Settings().value('user interface/is preset layout'):
self.mode_default_item.setChecked(True)
elif view_mode == 'setup' and Settings().value('user interface/is preset layout'):
@ -731,8 +730,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
settings = Settings()
self.live_controller.main_display_set_background()
if settings.value('%s/screen blank' % self.general_settings_section):
if settings.value('%s/blank warning' % self.general_settings_section):
if settings.value('{section}/screen blank'.format(section=self.general_settings_section)):
if settings.value('{section}/blank warning'.format(section=self.general_settings_section)):
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Main Display Blanked'),
translate('OpenLP.MainWindow', 'The Main Display has been blanked out'))
@ -924,9 +923,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
try:
value = import_settings.value(section_key)
except KeyError:
log.warning('The key "%s" does not exist (anymore), so it will be skipped.' % section_key)
log.warning('The key "{key}" does not exist (anymore), so it will be skipped.'.format(key=section_key))
if value is not None:
settings.setValue('%s' % (section_key), value)
settings.setValue('{key}'.format(key=section_key), value)
now = datetime.now()
settings.beginGroup(self.header_section)
settings.setValue('file_imported', import_file_name)
@ -1003,9 +1002,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
key_value = settings.value(section_key)
except KeyError:
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
translate('OpenLP.MainWindow', 'The key "%s" does not have a default '
translate('OpenLP.MainWindow', 'The key "{key}" does not have a default '
'value so it will be skipped in this '
'export.') % section_key,
'export.').format(key=section_key),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
key_value = None
if key_value is not None:
@ -1027,8 +1026,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
os.remove(temp_file)
except OSError as ose:
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
translate('OpenLP.MainWindow', 'An error occurred while exporting the '
'settings: %s') % ose.strerror,
translate('OpenLP.MainWindow',
'An error occurred while exporting the '
'settings: {err}').format(err=ose.strerror),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
def on_mode_default_item_clicked(self):
@ -1061,7 +1061,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
if mode:
settings = Settings()
settings.setValue('%s/view mode' % self.general_settings_section, mode)
settings.setValue('{section}/view mode'.format(section=self.general_settings_section), mode)
self.media_manager_dock.setVisible(media)
self.service_manager_dock.setVisible(service)
self.theme_manager_dock.setVisible(theme)
@ -1168,9 +1168,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
:param file_name: The file name of the service file.
"""
if modified:
title = '%s - %s*' % (UiStrings().OLPV2x, file_name)
title = '{title} - {name}*'.format(title=UiStrings().OLPV2x, name=file_name)
else:
title = '%s - %s' % (UiStrings().OLPV2x, file_name)
title = '{title} - {name}'.format(title=UiStrings().OLPV2x, name=file_name)
self.setWindowTitle(title)
def show_status_message(self, message):
@ -1183,8 +1183,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
Update the default theme indicator in the status bar
"""
self.default_theme_label.setText(translate('OpenLP.MainWindow', 'Default Theme: %s') %
Settings().value('themes/global theme'))
theme_name = Settings().value('themes/global theme')
self.default_theme_label.setText(translate('OpenLP.MainWindow',
'Default Theme: {theme}').format(theme=theme_name))
def toggle_media_manager(self):
"""
@ -1331,7 +1332,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
recent_files_to_display = existing_recent_files[0:recent_file_count]
self.recent_files_menu.clear()
for file_id, filename in enumerate(recent_files_to_display):
log.debug('Recent file name: %s', filename)
log.debug('Recent file name: {name}'.format(name=filename))
# TODO: Verify ''.format() before committing
action = create_action(self, '', text='&%d %s' % (file_id + 1,
os.path.splitext(os.path.basename(str(filename)))[0]), data=filename,
triggers=self.service_manager_contents.on_recent_service_clicked)
@ -1424,7 +1426,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
Change the data directory.
"""
log.info('Changing data path to %s' % self.new_data_path)
log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))
old_data_path = str(AppLocation.get_data_path())
# Copy OpenLP data to new location if requested.
self.application.set_busy_cursor()
@ -1432,17 +1434,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
log.info('Copying data to new path')
try:
self.show_status_message(
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - %s '
'- Please wait for copy to finish').replace('%s', self.new_data_path))
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
'- Please wait for copy to finish').format(path=self.new_data_path))
dir_util.copy_tree(old_data_path, self.new_data_path)
log.info('Copy successful')
except (IOError, os.error, DistutilsFileError) as why:
self.application.set_normal_cursor()
log.exception('Data copy failed %s' % str(why))
log.exception('Data copy failed {err}'.format(err=str(why)))
err_text = translate('OpenLP.MainWindow',
'OpenLP Data directory copy failed\n\n{err}').format(err=str(why)),
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'New Data Directory Error'),
translate('OpenLP.MainWindow',
'OpenLP Data directory copy failed\n\n%s').
replace('%s', str(why)),
err_text,
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
return False
else:

View File

@ -27,6 +27,11 @@ import os
import urllib.error
from PyQt5 import QtWidgets
try:
from pysword import modules
PYSWORD_AVAILABLE = True
except:
PYSWORD_AVAILABLE = False
from openlp.core.common import AppLocation, Settings, UiStrings, translate, clean_filename
from openlp.core.lib.db import delete_database
@ -34,7 +39,7 @@ from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.core.common.languagemanager import get_locale_key
from openlp.plugins.bibles.lib.manager import BibleFormat
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
from openlp.plugins.bibles.lib.db import clean_filename
from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract
log = logging.getLogger(__name__)
@ -94,6 +99,19 @@ class BibleImportForm(OpenLPWizard):
self.manager.set_process_dialog(self)
self.restart()
self.select_stack.setCurrentIndex(0)
if PYSWORD_AVAILABLE:
self.pysword_folder_modules = modules.SwordModules()
try:
self.pysword_folder_modules_json = self.pysword_folder_modules.parse_modules()
except FileNotFoundError:
log.debug('No installed SWORD modules found in the default location')
self.sword_bible_combo_box.clear()
return
bible_keys = self.pysword_folder_modules_json.keys()
for key in bible_keys:
self.sword_bible_combo_box.addItem(self.pysword_folder_modules_json[key]['description'], key)
else:
self.sword_tab_widget.setDisabled(True)
def custom_signals(self):
"""
@ -106,6 +124,8 @@ class BibleImportForm(OpenLPWizard):
self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked)
self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked)
self.web_update_button.clicked.connect(self.on_web_update_button_clicked)
self.sword_browse_button.clicked.connect(self.on_sword_browse_button_clicked)
self.sword_zipbrowse_button.clicked.connect(self.on_sword_zipbrowse_button_clicked)
def add_custom_pages(self):
"""
@ -121,7 +141,7 @@ class BibleImportForm(OpenLPWizard):
self.format_label = QtWidgets.QLabel(self.select_page)
self.format_label.setObjectName('FormatLabel')
self.format_combo_box = QtWidgets.QComboBox(self.select_page)
self.format_combo_box.addItems(['', '', '', '', ''])
self.format_combo_box.addItems(['', '', '', '', '', ''])
self.format_combo_box.setObjectName('FormatComboBox')
self.format_layout.addRow(self.format_label, self.format_combo_box)
self.spacer = QtWidgets.QSpacerItem(10, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
@ -275,6 +295,64 @@ class BibleImportForm(OpenLPWizard):
self.zefania_layout.addRow(self.zefania_file_label, self.zefania_file_layout)
self.zefania_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.select_stack.addWidget(self.zefania_widget)
self.sword_widget = QtWidgets.QWidget(self.select_page)
self.sword_widget.setObjectName('SwordWidget')
self.sword_layout = QtWidgets.QVBoxLayout(self.sword_widget)
self.sword_layout.setObjectName('SwordLayout')
self.sword_tab_widget = QtWidgets.QTabWidget(self.sword_widget)
self.sword_tab_widget.setObjectName('SwordTabWidget')
self.sword_folder_tab = QtWidgets.QWidget(self.sword_tab_widget)
self.sword_folder_tab.setObjectName('SwordFolderTab')
self.sword_folder_tab_layout = QtWidgets.QGridLayout(self.sword_folder_tab)
self.sword_folder_tab_layout.setObjectName('SwordTabFolderLayout')
self.sword_folder_label = QtWidgets.QLabel(self.sword_folder_tab)
self.sword_folder_label.setObjectName('SwordSourceLabel')
self.sword_folder_tab_layout.addWidget(self.sword_folder_label, 0, 0)
self.sword_folder_label.setObjectName('SwordFolderLabel')
self.sword_folder_edit = QtWidgets.QLineEdit(self.sword_folder_tab)
self.sword_folder_edit.setObjectName('SwordFolderEdit')
self.sword_browse_button = QtWidgets.QToolButton(self.sword_folder_tab)
self.sword_browse_button.setIcon(self.open_icon)
self.sword_browse_button.setObjectName('SwordBrowseButton')
self.sword_folder_tab_layout.addWidget(self.sword_folder_edit, 0, 1)
self.sword_folder_tab_layout.addWidget(self.sword_browse_button, 0, 2)
self.sword_bible_label = QtWidgets.QLabel(self.sword_folder_tab)
self.sword_bible_label.setObjectName('SwordBibleLabel')
self.sword_folder_tab_layout.addWidget(self.sword_bible_label, 1, 0)
self.sword_bible_combo_box = QtWidgets.QComboBox(self.sword_folder_tab)
self.sword_bible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.sword_bible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
self.sword_bible_combo_box.setObjectName('SwordBibleComboBox')
self.sword_folder_tab_layout.addWidget(self.sword_bible_combo_box, 1, 1)
self.sword_tab_widget.addTab(self.sword_folder_tab, '')
self.sword_zip_tab = QtWidgets.QWidget(self.sword_tab_widget)
self.sword_zip_tab.setObjectName('SwordZipTab')
self.sword_zip_layout = QtWidgets.QGridLayout(self.sword_zip_tab)
self.sword_zip_layout.setObjectName('SwordZipLayout')
self.sword_zipfile_label = QtWidgets.QLabel(self.sword_zip_tab)
self.sword_zipfile_label.setObjectName('SwordZipFileLabel')
self.sword_zipfile_edit = QtWidgets.QLineEdit(self.sword_zip_tab)
self.sword_zipfile_edit.setObjectName('SwordZipFileEdit')
self.sword_zipbrowse_button = QtWidgets.QToolButton(self.sword_zip_tab)
self.sword_zipbrowse_button.setIcon(self.open_icon)
self.sword_zipbrowse_button.setObjectName('SwordZipBrowseButton')
self.sword_zipbible_label = QtWidgets.QLabel(self.sword_folder_tab)
self.sword_zipbible_label.setObjectName('SwordZipBibleLabel')
self.sword_zipbible_combo_box = QtWidgets.QComboBox(self.sword_zip_tab)
self.sword_zipbible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.sword_zipbible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
self.sword_zipbible_combo_box.setObjectName('SwordZipBibleComboBox')
self.sword_zip_layout.addWidget(self.sword_zipfile_label, 0, 0)
self.sword_zip_layout.addWidget(self.sword_zipfile_edit, 0, 1)
self.sword_zip_layout.addWidget(self.sword_zipbrowse_button, 0, 2)
self.sword_zip_layout.addWidget(self.sword_zipbible_label, 1, 0)
self.sword_zip_layout.addWidget(self.sword_zipbible_combo_box, 1, 1)
self.sword_tab_widget.addTab(self.sword_zip_tab, '')
self.sword_layout.addWidget(self.sword_tab_widget)
self.sword_disabled_label = QtWidgets.QLabel(self.sword_widget)
self.sword_disabled_label.setObjectName('SwordDisabledLabel')
self.sword_layout.addWidget(self.sword_disabled_label)
self.select_stack.addWidget(self.sword_widget)
self.select_page_layout.addLayout(self.select_stack)
self.addPage(self.select_page)
# License Page
@ -323,6 +401,7 @@ class BibleImportForm(OpenLPWizard):
self.format_combo_box.setItemText(BibleFormat.WebDownload, translate('BiblesPlugin.ImportWizardForm',
'Web Download'))
self.format_combo_box.setItemText(BibleFormat.Zefania, WizardStrings.ZEF)
self.format_combo_box.setItemText(BibleFormat.SWORD, WizardStrings.SWORD)
self.osis_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
self.csv_books_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Books file:'))
self.csv_verses_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Verses file:'))
@ -346,6 +425,22 @@ class BibleImportForm(OpenLPWizard):
self.web_tab_widget.setTabText(
self.web_tab_widget.indexOf(self.web_proxy_tab), translate('BiblesPlugin.ImportWizardForm',
'Proxy Server (Optional)'))
self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:'))
self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:'))
self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:'))
self.sword_folder_edit.setPlaceholderText(translate('BiblesPlugin.ImportWizardForm',
'Defaults to the standard SWORD data folder'))
self.sword_zipbible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:'))
self.sword_tab_widget.setTabText(self.sword_tab_widget.indexOf(self.sword_folder_tab),
translate('BiblesPlugin.ImportWizardForm', 'Import from folder'))
self.sword_tab_widget.setTabText(self.sword_tab_widget.indexOf(self.sword_zip_tab),
translate('BiblesPlugin.ImportWizardForm', 'Import from Zip-file'))
if PYSWORD_AVAILABLE:
self.sword_disabled_label.setText('')
else:
self.sword_disabled_label.setText(translate('BiblesPlugin.ImportWizardForm',
'To import SWORD bibles the pysword python module must be '
'installed. Please read the manual for instructions.'))
self.license_details_page.setTitle(
translate('BiblesPlugin.ImportWizardForm', 'License Details'))
self.license_details_page.setSubTitle(translate('BiblesPlugin.ImportWizardForm',
@ -374,6 +469,9 @@ class BibleImportForm(OpenLPWizard):
if self.currentPage() == self.welcome_page:
return True
elif self.currentPage() == self.select_page:
self.version_name_edit.clear()
self.permissions_edit.clear()
self.copyright_edit.clear()
if self.field('source_format') == BibleFormat.OSIS:
if not self.field('osis_location'):
critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OSIS)
@ -410,6 +508,31 @@ class BibleImportForm(OpenLPWizard):
return False
else:
self.version_name_edit.setText(self.web_translation_combo_box.currentText())
elif self.field('source_format') == BibleFormat.SWORD:
# Test the SWORD tab that is currently active
if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab):
if not self.field('sword_folder_path') and self.sword_bible_combo_box.count() == 0:
critical_error_message_box(UiStrings().NFSs,
WizardStrings.YouSpecifyFolder % WizardStrings.SWORD)
self.sword_folder_edit.setFocus()
return False
key = self.sword_bible_combo_box.itemData(self.sword_bible_combo_box.currentIndex())
if 'description' in self.pysword_folder_modules_json[key]:
self.version_name_edit.setText(self.pysword_folder_modules_json[key]['description'])
if 'distributionlicense' in self.pysword_folder_modules_json[key]:
self.permissions_edit.setText(self.pysword_folder_modules_json[key]['distributionlicense'])
if 'copyright' in self.pysword_folder_modules_json[key]:
self.copyright_edit.setText(self.pysword_folder_modules_json[key]['copyright'])
elif self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_zip_tab):
if not self.field('sword_zip_path'):
critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.SWORD)
self.sword_zipfile_edit.setFocus()
return False
key = self.sword_zipbible_combo_box.itemData(self.sword_zipbible_combo_box.currentIndex())
if 'description' in self.pysword_zip_modules_json[key]:
self.version_name_edit.setText(self.pysword_zip_modules_json[key]['description'])
if 'distributionlicense' in self.pysword_zip_modules_json[key]:
self.permissions_edit.setText(self.pysword_zip_modules_json[key]['distributionlicense'])
return True
elif self.currentPage() == self.license_details_page:
license_version = self.field('license_version')
@ -531,6 +654,40 @@ class BibleImportForm(OpenLPWizard):
self.web_update_button.setEnabled(True)
self.web_progress_bar.setVisible(False)
def on_sword_browse_button_clicked(self):
"""
Show the file open dialog for the SWORD folder.
"""
self.get_folder(WizardStrings.OpenTypeFolder % WizardStrings.SWORD, self.sword_folder_edit,
'last directory import')
if self.sword_folder_edit.text():
try:
self.pysword_folder_modules = modules.SwordModules(self.sword_folder_edit.text())
self.pysword_folder_modules_json = self.pysword_folder_modules.parse_modules()
bible_keys = self.pysword_folder_modules_json.keys()
self.sword_bible_combo_box.clear()
for key in bible_keys:
self.sword_bible_combo_box.addItem(self.pysword_folder_modules_json[key]['description'], key)
except:
self.sword_bible_combo_box.clear()
def on_sword_zipbrowse_button_clicked(self):
"""
Show the file open dialog for a SWORD zip-file.
"""
self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.SWORD, self.sword_zipfile_edit,
'last directory import')
if self.sword_zipfile_edit.text():
try:
self.pysword_zip_modules = modules.SwordModules(self.sword_zipfile_edit.text())
self.pysword_zip_modules_json = self.pysword_zip_modules.parse_modules()
bible_keys = self.pysword_zip_modules_json.keys()
self.sword_zipbible_combo_box.clear()
for key in bible_keys:
self.sword_zipbible_combo_box.addItem(self.pysword_zip_modules_json[key]['description'], key)
except:
self.sword_zipbible_combo_box.clear()
def register_fields(self):
"""
Register the bible import wizard fields.
@ -543,6 +700,8 @@ class BibleImportForm(OpenLPWizard):
self.select_page.registerField('zefania_file', self.zefania_file_edit)
self.select_page.registerField('web_location', self.web_source_combo_box)
self.select_page.registerField('web_biblename', self.web_translation_combo_box)
self.select_page.registerField('sword_folder_path', self.sword_folder_edit)
self.select_page.registerField('sword_zip_path', self.sword_zipfile_edit)
self.select_page.registerField('proxy_server', self.web_server_edit)
self.select_page.registerField('proxy_username', self.web_user_edit)
self.select_page.registerField('proxy_password', self.web_password_edit)
@ -565,6 +724,8 @@ class BibleImportForm(OpenLPWizard):
self.setField('csv_versefile', '')
self.setField('opensong_file', '')
self.setField('zefania_file', '')
self.setField('sword_folder_path', '')
self.setField('sword_zip_path', '')
self.setField('web_location', WebDownload.Crosswalk)
self.setField('web_biblename', self.web_translation_combo_box.currentIndex())
self.setField('proxy_server', settings.value('proxy address'))
@ -626,9 +787,21 @@ class BibleImportForm(OpenLPWizard):
language_id=language_id
)
elif bible_type == BibleFormat.Zefania:
# Import an Zefania bible.
# Import a Zefania bible.
importer = self.manager.import_bible(BibleFormat.Zefania, name=license_version,
filename=self.field('zefania_file'))
elif bible_type == BibleFormat.SWORD:
# Import a SWORD bible.
if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab):
importer = self.manager.import_bible(BibleFormat.SWORD, name=license_version,
sword_path=self.field('sword_folder_path'),
sword_key=self.sword_bible_combo_box.itemData(
self.sword_bible_combo_box.currentIndex()))
else:
importer = self.manager.import_bible(BibleFormat.SWORD, name=license_version,
sword_path=self.field('sword_zip_path'),
sword_key=self.sword_zipbible_combo_box.itemData(
self.sword_zipbible_combo_box.currentIndex()))
if importer.do_import(license_version):
self.manager.save_meta_data(license_version, license_version, license_copyright, license_permissions)
self.manager.reload_bibles()

View File

@ -31,7 +31,10 @@ from .http import HTTPBible
from .opensong import OpenSongBible
from .osis import OSISBible
from .zefania import ZefaniaBible
try:
from .sword import SwordBible
except:
pass
log = logging.getLogger(__name__)
@ -46,6 +49,7 @@ class BibleFormat(object):
OpenSong = 2
WebDownload = 3
Zefania = 4
SWORD = 5
@staticmethod
def get_class(bible_format):
@ -64,6 +68,8 @@ class BibleFormat(object):
return HTTPBible
elif bible_format == BibleFormat.Zefania:
return ZefaniaBible
elif bible_format == BibleFormat.SWORD:
return SwordBible
else:
return None
@ -78,6 +84,7 @@ class BibleFormat(object):
BibleFormat.OpenSong,
BibleFormat.WebDownload,
BibleFormat.Zefania,
BibleFormat.SWORD
]

View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from pysword import modules
from openlp.core.common import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
log = logging.getLogger(__name__)
class SwordBible(BibleDB):
"""
SWORD Bible format importer class.
"""
def __init__(self, parent, **kwargs):
"""
Constructor to create and set up an instance of the SwordBible class. This class is used to import Bibles
from SWORD bible modules.
"""
log.debug(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
self.sword_key = kwargs['sword_key']
self.sword_path = kwargs['sword_path']
if self.sword_path == '':
self.sword_path = None
def do_import(self, bible_name=None):
"""
Loads a Bible from SWORD module.
"""
log.debug('Starting SWORD import from "%s"' % self.sword_key)
success = True
try:
pysword_modules = modules.SwordModules(self.sword_path)
pysword_module_json = pysword_modules.parse_modules()[self.sword_key]
bible = pysword_modules.get_bible_from_module(self.sword_key)
language = pysword_module_json['lang']
language = language[language.find('.') + 1:]
language_id = BiblesResourcesDB.get_language(language)['id']
self.save_meta('language_id', language_id)
books = bible.get_structure().get_books()
# Count number of books
num_books = 0
if 'ot' in books:
num_books += len(books['ot'])
if 'nt' in books:
num_books += len(books['nt'])
self.wizard.progress_bar.setMaximum(num_books)
# Import the bible
for testament in books.keys():
for book in books[testament]:
book_ref_id = self.get_book_ref_id_by_name(book.name, num_books, language_id)
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id'])
for chapter_number in range(1, book.num_chapters + 1):
if self.stop_import_flag:
break
verses = bible.get_iter(book.name, chapter_number)
verse_number = 0
for verse in verses:
verse_number += 1
self.create_verse(db_book.id, chapter_number, verse_number, verse)
self.wizard.increment_progress_bar(
translate('BiblesPlugin.Sword', 'Importing %s...') % db_book.name)
self.session.commit()
self.application.process_events()
except Exception as e:
critical_error_message_box(
message=translate('BiblesPlugin.SwordImport', 'An unexpected error happened while importing the SWORD '
'bible, please report this to the OpenLP developers.\n'
'%s' % e))
log.exception(str(e))
success = False
if self.stop_import_flag:
return False
else:
return success

View File

@ -144,18 +144,33 @@ class RemoteTab(SettingsTab):
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
self.android_app_group_box.setObjectName('android_app_group_box')
self.right_layout.addWidget(self.android_app_group_box)
self.qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
self.qr_layout.setObjectName('qr_layout')
self.qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
self.qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
self.qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.qr_code_label.setObjectName('qr_code_label')
self.qr_layout.addWidget(self.qr_code_label)
self.qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
self.qr_description_label.setObjectName('qr_description_label')
self.qr_description_label.setOpenExternalLinks(True)
self.qr_description_label.setWordWrap(True)
self.qr_layout.addWidget(self.qr_description_label)
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
self.android_qr_layout.setObjectName('android_qr_layout')
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.android_qr_code_label.setObjectName('android_qr_code_label')
self.android_qr_layout.addWidget(self.android_qr_code_label)
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
self.android_qr_description_label.setObjectName('android_qr_description_label')
self.android_qr_description_label.setOpenExternalLinks(True)
self.android_qr_description_label.setWordWrap(True)
self.android_qr_layout.addWidget(self.android_qr_description_label)
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
self.ios_app_group_box.setObjectName('ios_app_group_box')
self.right_layout.addWidget(self.ios_app_group_box)
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
self.ios_qr_layout.setObjectName('ios_qr_layout')
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
self.ios_qr_description_label.setOpenExternalLinks(True)
self.ios_qr_description_label.setWordWrap(True)
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
self.left_layout.addStretch()
self.right_layout.addStretch()
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
@ -176,10 +191,15 @@ class RemoteTab(SettingsTab):
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.qr_description_label.setText(
self.android_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the '
'Android app from Google Play.') %
'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')
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 '

View File

@ -34,6 +34,7 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
from openlp.plugins.songs.lib.ui import SongStrings
@ -110,7 +111,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
Generically load a set of objects into a cache and a combobox.
"""
objects = self.manager.get_all_objects(cls, order_by_ref=cls.name)
def get_key(obj):
"""Get the key to sort by"""
return get_natural_key(obj.name)
objects = self.manager.get_all_objects(cls)
objects.sort(key=get_key)
combo.clear()
combo.addItem('')
for obj in objects:
@ -343,7 +349,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
Load the authors from the database into the combobox.
"""
authors = self.manager.get_all_objects(Author, order_by_ref=Author.display_name)
def get_author_key(author):
"""Get the key to sort by"""
return get_natural_key(author.display_name)
authors = self.manager.get_all_objects(Author)
authors.sort(key=get_author_key)
self.authors_combo_box.clear()
self.authors_combo_box.addItem('')
self.authors = []
@ -378,9 +389,14 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
Load the themes into a combobox.
"""
def get_theme_key(theme):
"""Get the key to sort by"""
return get_natural_key(theme)
self.theme_combo_box.clear()
self.theme_combo_box.addItem('')
self.themes = theme_list
self.themes.sort(key=get_theme_key)
self.theme_combo_box.addItems(theme_list)
set_case_insensitive_completer(self.themes, self.theme_combo_box)

View File

@ -203,6 +203,10 @@ class SongExportForm(OpenLPWizard):
"""
Set default form values for the song export wizard.
"""
def get_song_key(song):
"""Get the key to sort by"""
return song.sort_key
self.restart()
self.finish_button.setVisible(False)
self.cancel_button.setVisible(True)
@ -213,7 +217,7 @@ class SongExportForm(OpenLPWizard):
# Load the list of songs.
self.application.set_busy_cursor()
songs = self.plugin.manager.get_all_objects(Song)
songs.sort(key=lambda song: song.sort_key)
songs.sort(key=get_song_key)
for song in songs:
# No need to export temporary songs.
if song.temporary:

View File

@ -27,6 +27,7 @@ from sqlalchemy.sql import and_
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.forms.authorsform import AuthorsForm
from openlp.plugins.songs.forms.topicsform import TopicsForm
from openlp.plugins.songs.forms.songbookform import SongBookForm
@ -120,8 +121,13 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
"""
Reloads the Authors list.
"""
def get_author_key(author):
"""Get the key to sort by"""
return get_natural_key(author.display_name)
self.authors_list_widget.clear()
authors = self.manager.get_all_objects(Author, order_by_ref=Author.display_name)
authors = self.manager.get_all_objects(Author)
authors.sort(key=get_author_key)
for author in authors:
if author.display_name:
author_name = QtWidgets.QListWidgetItem(author.display_name)
@ -134,8 +140,13 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
"""
Reloads the Topics list.
"""
def get_topic_key(topic):
"""Get the key to sort by"""
return get_natural_key(topic.name)
self.topics_list_widget.clear()
topics = self.manager.get_all_objects(Topic, order_by_ref=Topic.name)
topics = self.manager.get_all_objects(Topic)
topics.sort(key=get_topic_key)
for topic in topics:
topic_name = QtWidgets.QListWidgetItem(topic.name)
topic_name.setData(QtCore.Qt.UserRole, topic.id)
@ -145,8 +156,13 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
"""
Reloads the Books list.
"""
def get_book_key(book):
"""Get the key to sort by"""
return get_natural_key(book.name)
self.song_books_list_widget.clear()
books = self.manager.get_all_objects(Book, order_by_ref=Book.name)
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.setData(QtCore.Qt.UserRole, book.id)

View File

@ -383,7 +383,7 @@ def init_schema(url):
# Use lazy='joined' to always load authors when the song is fetched from the database (bug 1366198)
'authors': relation(Author, secondary=authors_songs_table, viewonly=True, lazy='joined'),
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
'songbook_entries': relation(SongBookEntry, backref='song', cascade="all, delete-orphan"),
'songbook_entries': relation(SongBookEntry, backref='song', cascade='all, delete-orphan'),
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
})
mapper(Topic, topics_table)

View File

@ -51,7 +51,7 @@ class OpenLPSongImport(SongImport):
:param manager: The song manager for the running OpenLP installation.
:param kwargs: The database providing the data to import.
"""
SongImport.__init__(self, manager, **kwargs)
super(OpenLPSongImport, self).__init__(manager, **kwargs)
self.source_session = None
def do_import(self, progress_dialog=None):
@ -63,49 +63,61 @@ class OpenLPSongImport(SongImport):
class OldAuthor(BaseModel):
"""
Author model
Maps to the authors table
"""
pass
class OldBook(BaseModel):
"""
Book model
Maps to the songbooks table
"""
pass
class OldMediaFile(BaseModel):
"""
MediaFile model
Maps to the media_files table
"""
pass
class OldSong(BaseModel):
"""
Song model
Maps to the songs table
"""
pass
class OldTopic(BaseModel):
"""
Topic model
Maps to the topics table
"""
pass
class OldSongBookEntry(BaseModel):
"""
Maps to the songs_songbooks table
"""
pass
# Check the file type
if not self.import_source.endswith('.sqlite'):
if not isinstance(self.import_source, str) or not self.import_source.endswith('.sqlite'):
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
# Load the db file
# Load the db file and reflect it
engine = create_engine(self.import_source)
source_meta = MetaData()
source_meta.reflect(engine)
self.source_session = scoped_session(sessionmaker(bind=engine))
# Run some checks to see which version of the database we have
if 'media_files' in list(source_meta.tables.keys()):
has_media_files = True
else:
has_media_files = False
if 'songs_songbooks' in list(source_meta.tables.keys()):
has_songs_books = True
else:
has_songs_books = False
# Load up the tabls and map them out
source_authors_table = source_meta.tables['authors']
source_song_books_table = source_meta.tables['song_books']
source_songs_table = source_meta.tables['songs']
@ -113,6 +125,7 @@ class OpenLPSongImport(SongImport):
source_authors_songs_table = source_meta.tables['authors_songs']
source_songs_topics_table = source_meta.tables['songs_topics']
source_media_files_songs_table = None
# Set up media_files relations
if has_media_files:
source_media_files_table = source_meta.tables['media_files']
source_media_files_songs_table = source_meta.tables.get('media_files_songs')
@ -120,9 +133,15 @@ class OpenLPSongImport(SongImport):
class_mapper(OldMediaFile)
except UnmappedClassError:
mapper(OldMediaFile, source_media_files_table)
if has_songs_books:
source_songs_songbooks_table = source_meta.tables['songs_songbooks']
try:
class_mapper(OldSongBookEntry)
except UnmappedClassError:
mapper(OldSongBookEntry, source_songs_songbooks_table, properties={'songbook': relation(OldBook)})
# Set up the songs relationships
song_props = {
'authors': relation(OldAuthor, backref='songs', secondary=source_authors_songs_table),
'book': relation(OldBook, backref='songs'),
'topics': relation(OldTopic, backref='songs', secondary=source_songs_topics_table)
}
if has_media_files:
@ -134,6 +153,11 @@ class OpenLPSongImport(SongImport):
relation(OldMediaFile, backref='songs',
foreign_keys=[source_media_files_table.c.song_id],
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
if has_songs_books:
song_props['songbook_entries'] = relation(OldSongBookEntry, backref='song', cascade='all, delete-orphan')
else:
song_props['book'] = relation(OldBook, backref='songs')
# Map the rest of the tables
try:
class_mapper(OldAuthor)
except UnmappedClassError:
@ -163,44 +187,54 @@ class OpenLPSongImport(SongImport):
old_titles = song.search_title.split('@')
if len(old_titles) > 1:
new_song.alternate_title = old_titles[1]
# Values will be set when cleaning the song.
# Transfer the values to the new song object
new_song.search_title = ''
new_song.search_lyrics = ''
new_song.song_number = song.song_number
new_song.lyrics = song.lyrics
new_song.verse_order = song.verse_order
new_song.copyright = song.copyright
new_song.comments = song.comments
new_song.theme_name = song.theme_name
new_song.ccli_number = song.ccli_number
if hasattr(song, 'song_number') and song.song_number:
new_song.song_number = song.song_number
# Find or create all the authors and add them to the new song object
for author in song.authors:
existing_author = self.manager.get_object_filtered(Author, Author.display_name == author.display_name)
if existing_author is None:
if not existing_author:
existing_author = Author.populate(
first_name=author.first_name,
last_name=author.last_name,
display_name=author.display_name)
new_song.add_author(existing_author)
if song.book:
existing_song_book = self.manager.get_object_filtered(Book, Book.name == song.book.name)
if existing_song_book is None:
existing_song_book = Book.populate(name=song.book.name, publisher=song.book.publisher)
new_song.book = existing_song_book
# Find or create all the topics and add them to the new song object
if song.topics:
for topic in song.topics:
existing_topic = self.manager.get_object_filtered(Topic, Topic.name == topic.name)
if existing_topic is None:
if not existing_topic:
existing_topic = Topic.populate(name=topic.name)
new_song.topics.append(existing_topic)
if has_media_files:
if song.media_files:
for media_file in song.media_files:
existing_media_file = self.manager.get_object_filtered(
MediaFile, MediaFile.file_name == media_file.file_name)
if existing_media_file:
new_song.media_files.append(existing_media_file)
else:
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
# Find or create all the songbooks and add them to the new song object
if has_songs_books and song.songbook_entries:
for entry in song.songbook_entries:
existing_book = self.manager.get_object_filtered(Book, Book.name == entry.songbook.name)
if not existing_book:
existing_book = Book.populate(name=entry.songbook.name, publisher=entry.songbook.publisher)
new_song.add_songbook_entry(existing_book, entry.entry)
elif song.book:
existing_book = self.manager.get_object_filtered(Book, Book.name == song.book.name)
if not existing_book:
existing_book = Book.populate(name=song.book.name, publisher=song.book.publisher)
new_song.add_songbook_entry(existing_book, '')
# Find or create all the media files and add them to the new song object
if has_media_files and song.media_files:
for media_file in song.media_files:
existing_media_file = self.manager.get_object_filtered(
MediaFile, MediaFile.file_name == media_file.file_name)
if existing_media_file:
new_song.media_files.append(existing_media_file)
else:
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
clean_song(self.manager, new_song)
self.manager.save_object(new_song)
if progress_dialog:

View File

@ -21,7 +21,6 @@
###############################################################################
import logging
import re
import os
import shutil
@ -194,28 +193,30 @@ class SongMediaItem(MediaManagerItem):
log.debug('Authors Search')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(
Author, Author.display_name.like(search_string), Author.display_name.asc())
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_results = self.plugin.manager.get_all_objects(
Topic, Topic.name.like(search_string), Topic.name.asc())
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_results = (self.plugin.manager.session.query(SongBookEntry)
search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id)
.join(Song)
.join(Book)
.filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry)).all())
.filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry),
Song.temporary.is_(False)).all())
self.display_results_book(search_results)
elif search_type == SongSearch.Themes:
log.debug('Theme Search')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(
Song, Song.theme_name.like(search_string), Song.theme_name.asc())
Song, Song.theme_name.like(search_string))
self.display_results_themes(search_results)
elif search_type == SongSearch.Copyright:
log.debug('Copyright Search')
@ -258,10 +259,14 @@ class SongMediaItem(MediaManagerItem):
:param search_results: A list of db Song objects
:return: None
"""
def get_song_key(song):
"""Get the key to sort by"""
return song.sort_key
log.debug('display results Song')
self.save_auto_select_id()
self.list_view.clear()
search_results.sort(key=lambda song: song.sort_key)
search_results.sort(key=get_song_key)
for song in search_results:
# Do not display temporary songs
if song.temporary:
@ -283,12 +288,20 @@ class SongMediaItem(MediaManagerItem):
:param search_results: A list of db Author objects
:return: None
"""
def get_author_key(author):
"""Get the key to sort by"""
return get_natural_key(author.display_name)
def get_song_key(song):
"""Get the key to sort by"""
return song.sort_key
log.debug('display results Author')
self.list_view.clear()
search_results = sorted(search_results, key=lambda author: get_natural_key(author.display_name))
search_results.sort(key=get_author_key)
for author in search_results:
songs = sorted(author.songs, key=lambda song: song.sort_key)
for song in songs:
author.songs.sort(key=get_song_key)
for song in author.songs:
# Do not display temporary songs
if song.temporary:
continue
@ -301,19 +314,20 @@ class SongMediaItem(MediaManagerItem):
"""
Display the song search results in the media manager list, grouped by book and entry
:param search_results: A list of db SongBookEntry objects
:param search_results: A tuple containing (songbook entry, book name, song title, song id)
:return: None
"""
def get_songbook_key(result):
"""Get the key to sort by"""
return (get_natural_key(result[1]), get_natural_key(result[0]), get_natural_key(result[2]))
log.debug('display results Book')
self.list_view.clear()
search_results = sorted(search_results, key=lambda songbook_entry:
(get_natural_key(songbook_entry.songbook.name), get_natural_key(songbook_entry.entry)))
for songbook_entry in search_results:
if songbook_entry.song.temporary:
continue
song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
search_results.sort(key=get_songbook_key)
for result in search_results:
song_detail = '%s #%s: %s' % (result[1], result[0], result[2])
song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
song_name.setData(QtCore.Qt.UserRole, result[3])
self.list_view.addItem(song_name)
def display_results_topic(self, search_results):
@ -323,12 +337,20 @@ class SongMediaItem(MediaManagerItem):
:param search_results: A list of db Topic objects
:return: None
"""
def get_topic_key(topic):
"""Get the key to sort by"""
return get_natural_key(topic.name)
def get_song_key(song):
"""Get the key to sort by"""
return song.sort_key
log.debug('display results Topic')
self.list_view.clear()
search_results = sorted(search_results, key=lambda topic: get_natural_key(topic.name))
search_results.sort(key=get_topic_key)
for topic in search_results:
songs = sorted(topic.songs, key=lambda song: song.sort_key)
for song in songs:
topic.songs.sort(key=get_song_key)
for song in topic.songs:
# Do not display temporary songs
if song.temporary:
continue
@ -344,10 +366,13 @@ class SongMediaItem(MediaManagerItem):
:param search_results: A list of db Song objects
:return: None
"""
def get_theme_key(song):
"""Get the key to sort by"""
return (get_natural_key(song.theme_name), song.sort_key)
log.debug('display results Themes')
self.list_view.clear()
search_results = sorted(search_results, key=lambda song: (get_natural_key(song.theme_name),
song.sort_key))
search_results.sort(key=get_theme_key)
for song in search_results:
# Do not display temporary songs
if song.temporary:
@ -364,11 +389,14 @@ class SongMediaItem(MediaManagerItem):
:param search_results: A list of db Song objects
:return: None
"""
def get_cclinumber_key(song):
"""Get the key to sort by"""
return (get_natural_key(song.ccli_number), song.sort_key)
log.debug('display results CCLI number')
self.list_view.clear()
songs = sorted(search_results, key=lambda song: (get_natural_key(song.ccli_number),
song.sort_key))
for song in songs:
search_results.sort(key=get_cclinumber_key)
for song in search_results:
# Do not display temporary songs
if song.temporary:
continue

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

View File

@ -206,5 +206,6 @@
</qresource>
<qresource prefix="remotes">
<file>android_app_qr.png</file>
<file>ios_app_qr.png</file>
</qresource>
</RCC>

View File

@ -102,6 +102,7 @@ OPTIONAL_MODULES = [
('nose', '(testing framework)', True),
('mock', '(testing module)', sys.version_info[1] < 3),
('jenkins', '(access jenkins api - package name: jenkins-webapi)', True),
('pysword', '(import SWORD bibles)', True),
]
w = sys.stdout.write

View File

@ -197,6 +197,7 @@ FOOTER_CSS_BASE = """
"""
FOOTER_CSS = FOOTER_CSS_BASE % ('nowrap')
FOOTER_CSS_WRAP = FOOTER_CSS_BASE % ('normal')
FOOTER_CSS_INVALID = ''
class Htmbuilder(TestCase, TestMixin):
@ -359,6 +360,27 @@ class Htmbuilder(TestCase, TestMixin):
# THEN: Footer should wrap
self.assertEqual(FOOTER_CSS_WRAP, css, 'The footer strings should be equal.')
def build_footer_invalid_test(self):
"""
Test the build_footer_css() function
"""
# GIVEN: Create a theme.
css = []
item = MagicMock()
item.theme_data = None
item.footer = 'FAIL'
height = 1024
# WHEN: Settings say that footer should wrap
css.append(build_footer_css(item, height))
item.theme_data = 'TEST'
item.footer = None
css.append(build_footer_css(item, height))
# THEN: Footer should wrap
self.assertEqual(FOOTER_CSS_INVALID, css[0], 'The footer strings should be blank.')
self.assertEqual(FOOTER_CSS_INVALID, css[1], 'The footer strings should be blank.')
def webkit_version_test(self):
"""
Test the webkit_version() function

View File

@ -28,7 +28,7 @@ PREREQUISITE: add_record() and get_all() functions validated.
import os
from unittest import TestCase
from openlp.core.lib.projector.db import Projector, ProjectorDB, ProjectorSource
from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource
from tests.functional import MagicMock, patch
from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
@ -82,13 +82,13 @@ class TestProjectorDB(TestCase):
"""
Test case for ProjectorDB
"""
def setUp(self):
@patch('openlp.core.lib.projector.db.init_url')
def setUp(self, mocked_init_url):
"""
Set up anything necessary for all tests
"""
with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB
self.projector = ProjectorDB()
mocked_init_url.return_value = 'sqlite:///{db}'.format(db=TEST_DB)
self.projector = ProjectorDB()
def tearDown(self):
"""
@ -192,3 +192,17 @@ class TestProjectorDB(TestCase):
# THEN: Projector should have the same source entry
item = self.projector.get_projector_by_id(item_id)
self.assertTrue(compare_source(item.source_list[0], source))
def manufacturer_repr_test(self):
"""
Test manufacturer class __repr__ text
"""
# GIVEN: Test object
manufacturer = Manufacturer()
# WHEN: Name is set
manufacturer.name = 'OpenLP Test'
# THEN: __repr__ should return a proper string
self.assertEqual(str(manufacturer), '<Manufacturer(name="OpenLP Test")>',
'Manufacturer.__repr__() should have returned a proper representation string')

View File

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the SWORD Bible importer.
"""
import os
import json
from unittest import TestCase, SkipTest
from tests.functional import MagicMock, patch
try:
from openlp.plugins.bibles.lib.sword import SwordBible
except ImportError:
raise SkipTest('PySword is not installed, skipping SWORD test.')
from openlp.plugins.bibles.lib.db import BibleDB
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..', 'resources', 'bibles'))
class TestSwordImport(TestCase):
"""
Test the functions in the :mod:`swordimport` module.
"""
def setUp(self):
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
self.registry_patcher.start()
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
self.manager_patcher.start()
def tearDown(self):
self.registry_patcher.stop()
self.manager_patcher.stop()
def create_importer_test(self):
"""
Test creating an instance of the Sword file importer
"""
# GIVEN: A mocked out "manager"
mocked_manager = MagicMock()
# WHEN: An importer object is created
importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='')
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
@patch('openlp.plugins.bibles.lib.sword.SwordBible.application')
@patch('openlp.plugins.bibles.lib.sword.modules')
@patch('openlp.plugins.bibles.lib.db.BiblesResourcesDB')
def simple_import_test(self, mocked_bible_res_db, mocked_pysword_modules, mocked_application):
"""
Test that a simple SWORD import works
"""
# GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
# get_book_ref_id_by_name, create_verse, create_book, session and get_language.
# Also mocked pysword structures
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='')
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
importer.create_book = MagicMock()
importer.session = MagicMock()
mocked_bible_res_db.get_language.return_value = 'Danish'
mocked_bible = MagicMock()
mocked_genesis = MagicMock()
mocked_genesis.name = 'Genesis'
mocked_genesis.num_chapters = 1
books = {'ot': [mocked_genesis]}
mocked_structure = MagicMock()
mocked_structure.get_books.return_value = books
mocked_bible.get_structure.return_value = mocked_structure
mocked_bible.get_iter.return_value = [verse[1] for verse in test_data['verses']]
mocked_module = MagicMock()
mocked_module.get_bible_from_module.return_value = mocked_bible
mocked_pysword_modules.SwordModules.return_value = mocked_module
# WHEN: Importing bible file
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
importer.create_verse.assert_any_call(importer.create_book().id, 1, int(verse_tag), verse_text)

View File

@ -23,6 +23,7 @@
This module contains tests for the lib submodule of the Songs plugin.
"""
from unittest import TestCase
from unittest.mock import call
from PyQt5 import QtCore
@ -53,6 +54,7 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.list_view.save_auto_select_id = MagicMock()
self.media_item.list_view.clear = MagicMock()
self.media_item.list_view.addItem = MagicMock()
self.media_item.list_view.setCurrentItem = MagicMock()
self.media_item.auto_select_id = -1
self.media_item.display_songbook = False
self.media_item.display_copyright_symbol = False
@ -79,13 +81,22 @@ class TestMediaItem(TestCase, TestMixin):
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.authors = []
mock_song_temp = MagicMock()
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.authors = []
mock_author = MagicMock()
mock_author.display_name = 'My Author'
mock_song.authors.append(mock_author)
mock_song_temp.authors.append(mock_author)
mock_song.temporary = False
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
self.media_item.auto_select_id = 1
# WHEN: I display song search results
self.media_item.display_results_song(mock_search_results)
@ -93,9 +104,10 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
self.media_item.save_auto_select_id.assert_called_with()
MockedQListWidgetItem.assert_called_with('My Song (My Author)')
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
MockedQListWidgetItem.assert_called_once_with('My Song (My Author)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
self.media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget)
def display_results_author_test(self):
"""
@ -107,13 +119,19 @@ class TestMediaItem(TestCase, TestMixin):
mock_search_results = []
mock_author = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_author.display_name = 'My Author'
mock_author.songs = []
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_author.songs.append(mock_song)
mock_author.songs.append(mock_song_temp)
mock_search_results.append(mock_author)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
@ -123,9 +141,9 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_with('My Author (My Song)')
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
MockedQListWidgetItem.assert_called_once_with('My Author (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def display_results_book_test(self):
"""
@ -134,19 +152,7 @@ class TestMediaItem(TestCase, TestMixin):
# GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_songbook_entry = MagicMock()
mock_songbook = MagicMock()
mock_song = MagicMock()
mock_songbook_entry.entry = '1'
mock_songbook.name = 'My Book'
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_songbook_entry.song = mock_song
mock_songbook_entry.songbook = mock_songbook
mock_search_results.append(mock_songbook_entry)
mock_search_results = [('1', 'My Book', 'My Song', 1)]
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
@ -155,9 +161,35 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_with('My Book #1: My Song')
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_songbook_entry.song.id)
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def songbook_natural_sorting_test(self):
"""
Test that songbooks are sorted naturally
"""
# GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem:
mock_search_results = [('2', 'Thy Book', 'Thy Song', 50),
('2', 'My Book', 'Your Song', 7),
('10', 'My Book', 'Our Song', 12),
('1', 'My Book', 'My Song', 1),
('2', 'Thy Book', 'A Song', 8)]
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by book
self.media_item.display_results_book(mock_search_results)
# THEN: The songbooks are inserted in the right (natural) order,
# grouped first by book, then by number, then by song title
calls = [call('My Book #1: My Song'), call().setData(QtCore.Qt.UserRole, 1),
call('My Book #2: Your Song'), call().setData(QtCore.Qt.UserRole, 7),
call('My Book #10: Our Song'), call().setData(QtCore.Qt.UserRole, 12),
call('Thy Book #2: A Song'), call().setData(QtCore.Qt.UserRole, 8),
call('Thy Book #2: Thy Song'), call().setData(QtCore.Qt.UserRole, 50)]
MockedQListWidgetItem.assert_has_calls(calls)
def display_results_topic_test(self):
"""
@ -169,13 +201,19 @@ class TestMediaItem(TestCase, TestMixin):
mock_search_results = []
mock_topic = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_topic.name = 'My Topic'
mock_topic.songs = []
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_topic.songs.append(mock_song)
mock_topic.songs.append(mock_song_temp)
mock_search_results.append(mock_topic)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
@ -185,9 +223,9 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_with('My Topic (My Song)')
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
MockedQListWidgetItem.assert_called_once_with('My Topic (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def display_results_themes_test(self):
"""
@ -198,12 +236,19 @@ class TestMediaItem(TestCase, TestMixin):
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.theme_name = 'My Theme'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.theme_name = 'My Theme'
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
@ -212,9 +257,9 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_with('My Theme (My Song)')
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
MockedQListWidgetItem.assert_called_once_with('My Theme (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def display_results_cclinumber_test(self):
"""
@ -225,12 +270,19 @@ class TestMediaItem(TestCase, TestMixin):
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.ccli_number = '12345'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.ccli_number = '12346'
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
@ -239,9 +291,9 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_with('12345 (My Song)')
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
MockedQListWidgetItem.assert_called_once_with('12345 (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def build_song_footer_one_author_test(self):
"""

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the OpenLP song importer.
"""
from unittest import TestCase
from openlp.plugins.songs.lib.importers.openlp import OpenLPSongImport
from openlp.core.common import Registry
from tests.functional import patch, MagicMock
class TestOpenLPImport(TestCase):
"""
Test the functions in the :mod:`openlp` importer module.
"""
def setUp(self):
"""
Create the registry
"""
Registry.create()
def create_importer_test(self):
"""
Test creating an instance of the OpenLP database importer
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
mocked_manager = MagicMock()
# WHEN: An importer object is created
importer = OpenLPSongImport(mocked_manager, filenames=[])
# THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none')
def invalid_import_source_test(self):
"""
Test OpenLPSongImport.do_import handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OpenLPSongImport(mocked_manager, filenames=[])
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
# WHEN: Import source is not a list
for source in ['not a list', 0]:
importer.import_source = source
# THEN: do_import should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,
'setMaximum on import_wizard.progress_bar should not have been called')

View File

@ -27,7 +27,7 @@ from unittest import TestCase
from PyQt5 import QtWidgets
from openlp.core.common import Registry
from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm, WebDownload
import openlp.plugins.bibles.forms.bibleimportform as bibleimportform
from tests.helpers.testmixin import TestMixin
from tests.functional import MagicMock, patch
@ -46,7 +46,8 @@ class TestBibleImportForm(TestCase, TestMixin):
self.setup_application()
self.main_window = QtWidgets.QMainWindow()
Registry().register('main_window', self.main_window)
self.form = BibleImportForm(self.main_window, MagicMock(), MagicMock())
bibleimportform.PYSWORD_AVAILABLE = False
self.form = bibleimportform.BibleImportForm(self.main_window, MagicMock(), MagicMock())
def tearDown(self):
"""
@ -76,3 +77,16 @@ class TestBibleImportForm(TestCase, TestMixin):
# THEN: The webbible list should still be empty
self.assertEqual(self.form.web_bible_list, {}, 'The webbible list should be empty')
def custom_init_test(self):
"""
Test that custom_init works as expected if pysword is unavailable
"""
# GIVEN: A mocked sword_tab_widget
self.form.sword_tab_widget = MagicMock()
# WHEN: Running custom_init
self.form.custom_init()
# THEN: sword_tab_widget.setDisabled(True) should have been called
self.form.sword_tab_widget.setDisabled.assert_called_with(True)