Tim Bentley has proposed merging lp:~trb143/openlp/reporting into lp:openlp.
Requested reviews: Raoul Snyman (raoul-snyman) For more details, see: https://code.launchpad.net/~trb143/openlp/reporting/+merge/306259 My dad needed a report of all the songs on their database, they had 1800. Made this into a reporting option and cleaned up the menu. Fixed some errors spotted as well Fixed issues and comments 1800 songs takes about 3 secs to run on my i7 lp:~trb143/openlp/reporting (revision 2699) [SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1779/ [SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1690/ [SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1628/ [SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1384/ [SUCCESS] https://ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/974/ [SUCCESS] https://ci.openlp.io/job/Branch-05a-Code_Analysis/1042/ [SUCCESS] https://ci.openlp.io/job/Branch-05b-Test_Coverage/910/ [SUCCESS] https://ci.openlp.io/job/Branch-05c-Code_Analysis2/73/ -- Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/common/settings.py' --- openlp/core/common/settings.py 2016-07-31 11:58:54 +0000 +++ openlp/core/common/settings.py 2016-09-20 18:07:02 +0000 @@ -379,6 +379,7 @@ 'shortcuts/themeScreen': [QtGui.QKeySequence(QtCore.Qt.Key_T)], 'shortcuts/toolsReindexItem': [], 'shortcuts/toolsFindDuplicates': [], + 'shortcuts/toolsSongListReport': [], 'shortcuts/toolsAlertItem': [QtGui.QKeySequence(QtCore.Qt.Key_F7)], 'shortcuts/toolsFirstTimeWizard': [], 'shortcuts/toolsOpenDataFolder': [], === modified file 'openlp/plugins/custom/lib/mediaitem.py' --- openlp/plugins/custom/lib/mediaitem.py 2016-05-21 18:19:18 +0000 +++ openlp/plugins/custom/lib/mediaitem.py 2016-09-20 18:07:02 +0000 @@ -350,7 +350,7 @@ :param string: The search string :param show_error: The error string to be show. """ - search = '%{search}%'.forma(search=string.lower()) + search = '%{search}%'.format(search=string.lower()) search_results = self.plugin.db_manager.get_all_objects(CustomSlide, or_(func.lower(CustomSlide.title).like(search), func.lower(CustomSlide.text).like(search)), === modified file 'openlp/plugins/songs/forms/editsongform.py' --- openlp/plugins/songs/forms/editsongform.py 2016-05-27 08:13:14 +0000 +++ openlp/plugins/songs/forms/editsongform.py 2016-09-20 18:07:02 +0000 @@ -317,7 +317,7 @@ 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{xml}'.forma(xml=sxml.dump_xml())) + log.exception('Problem processing song Lyrics \n{xml}'.format(xml=sxml.dump_xml())) raise def keyPressEvent(self, event): === modified file 'openlp/plugins/songs/lib/songcompare.py' --- openlp/plugins/songs/lib/songcompare.py 2015-12-31 22:46:06 +0000 +++ openlp/plugins/songs/lib/songcompare.py 2016-09-20 18:07:02 +0000 @@ -46,13 +46,13 @@ MAX_TYPO_SIZE = 3 -def songs_probably_equal(song_tupel): +def songs_probably_equal(song_tuple): """ Calculate and return whether two songs are probably equal. - :param song_tupel: A tuple of two songs to compare. + :param song_tuple: A tuple of two songs to compare. """ - song1, song2 = song_tupel + song1, song2 = song_tuple pos1, lyrics1 = song1 pos2, lyrics2 = song2 if len(lyrics1) < len(lyrics2): === added file 'openlp/plugins/songs/reporting.py' --- openlp/plugins/songs/reporting.py 1970-01-01 00:00:00 +0000 +++ openlp/plugins/songs/reporting.py 2016-09-20 18:07:02 +0000 @@ -0,0 +1,99 @@ +# -*- 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 # +############################################################################### +""" +The :mod:`db` module provides the ability to provide a csv file of all songs +""" +import csv +import logging + +from PyQt5 import QtWidgets + +from openlp.core.common import Registry, translate +from openlp.core.lib.ui import critical_error_message_box +from openlp.plugins.songs.lib.db import Song + + +log = logging.getLogger(__name__) + + +def report_song_list(): + """ + Export the song list as a CSV file. + :return: Nothing + """ + main_window = Registry().get('main_window') + plugin = Registry().get('songs').plugin + report_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName( + main_window, translate('SongPlugin.ReportSongList', 'Output File Location')) + if not report_file_name: + main_window.error_message( + translate('SongPlugin.ReportSongList', 'Output Path Not Selected'), + translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your ' + 'report. \nPlease select an existing path ' + 'on your computer.') + ) + return + if not report_file_name.endswith('csv'): + report_file_name += '.csv' + file_handle = None + try: + file_handle = open(report_file_name, 'wt') + fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic') + writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL) + headers = dict((n, n) for n in fieldnames) + writer.writerow(headers) + song_list = plugin.manager.get_all_objects(Song) + for song in song_list: + author_list = [] + for author_song in song.authors_songs: + author_list.append(author_song.author.display_name) + author_string = '{name}'.format(name=' | '.join(author_list)) + book_list = [] + for book_song in song.songbook_entries: + if hasattr(book_song, 'entry') and book_song.entry: + book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry)) + book_string = '{name}'.format(name=' | '.join(book_list)) + topic_list = [] + for topic_song in song.topics: + if hasattr(topic_song, 'name'): + topic_list.append(topic_song.name) + topic_string = '{name}'.format(name=' | '.join(topic_list)) + writer.writerow({'Title': song.title, + 'Alternative Title': song.alternate_title, + 'Copyright': song.copyright, + 'Author(s)': author_string, + 'Song Book': book_string, + 'Topic': topic_string}) + main_window.information_message( + translate('SongPlugin.ReportSongList', 'Report Creation'), + translate('SongPlugin.ReportSongList', + '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('SongPlugin.ReportSongList', 'Song Extraction Failed'), + translate('SongPlugin.ReportSongList', + 'An error occurred while extracting: {error}' + ).format(error=ose.strerror)) + finally: + if file_handle: + file_handle.close() === modified file 'openlp/plugins/songs/songsplugin.py' --- openlp/plugins/songs/songsplugin.py 2016-03-31 16:34:22 +0000 +++ openlp/plugins/songs/songsplugin.py 2016-09-20 18:07:02 +0000 @@ -36,6 +36,7 @@ from openlp.core.lib import Plugin, StringContent, build_icon from openlp.core.lib.db import Manager from openlp.core.lib.ui import create_action +from openlp.plugins.songs import reporting from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm from openlp.plugins.songs.forms.songselectform import SongSelectForm from openlp.plugins.songs.lib import clean_song, upgrade @@ -102,13 +103,13 @@ self.songselect_form.initialise() self.song_import_item.setVisible(True) self.song_export_item.setVisible(True) - self.tools_reindex_item.setVisible(True) - self.tools_find_duplicates.setVisible(True) + self.song_tools_menu.menuAction().setVisible(True) action_list = ActionList.get_instance() action_list.add_action(self.song_import_item, UiStrings().Import) action_list.add_action(self.song_export_item, UiStrings().Export) action_list.add_action(self.tools_reindex_item, UiStrings().Tools) action_list.add_action(self.tools_find_duplicates, UiStrings().Tools) + action_list.add_action(self.tools_report_song_list, UiStrings().Tools) def add_import_menu_item(self, import_menu): """ @@ -151,19 +152,37 @@ :param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent. """ log.info('add tools menu') + self.tools_menu = tools_menu + self.song_tools_menu = QtWidgets.QMenu(tools_menu) + self.song_tools_menu.setObjectName('song_tools_menu') + self.song_tools_menu.setTitle(translate('SongsPlugin', 'Songs')) self.tools_reindex_item = create_action( tools_menu, 'toolsReindexItem', text=translate('SongsPlugin', '&Re-index Songs'), icon=':/plugins/plugin_songs.png', statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'), - visible=False, triggers=self.on_tools_reindex_item_triggered) - tools_menu.addAction(self.tools_reindex_item) + triggers=self.on_tools_reindex_item_triggered) self.tools_find_duplicates = create_action( tools_menu, 'toolsFindDuplicates', text=translate('SongsPlugin', 'Find &Duplicate Songs'), statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'), - visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True) - tools_menu.addAction(self.tools_find_duplicates) + triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True) + self.tools_report_song_list = create_action( + tools_menu, 'toolsSongListReport', + text=translate('SongsPlugin', 'Song List Report'), + statustip=translate('SongsPlugin', 'Produce a CSV file of all the songs in the database.'), + triggers=self.on_tools_report_song_list_triggered) + + self.tools_menu.addAction(self.song_tools_menu.menuAction()) + self.song_tools_menu.addAction(self.tools_reindex_item) + self.song_tools_menu.addAction(self.tools_find_duplicates) + self.song_tools_menu.addAction(self.tools_report_song_list) + + self.song_tools_menu.menuAction().setVisible(False) + + @staticmethod + def on_tools_report_song_list_triggered(): + reporting.report_song_list() def on_tools_reindex_item_triggered(self): """ @@ -326,13 +345,13 @@ self.manager.finalise() self.song_import_item.setVisible(False) self.song_export_item.setVisible(False) - self.tools_reindex_item.setVisible(False) - self.tools_find_duplicates.setVisible(False) action_list = ActionList.get_instance() action_list.remove_action(self.song_import_item, UiStrings().Import) action_list.remove_action(self.song_export_item, UiStrings().Export) action_list.remove_action(self.tools_reindex_item, UiStrings().Tools) action_list.remove_action(self.tools_find_duplicates, UiStrings().Tools) + action_list.add_action(self.tools_report_song_list, UiStrings().Tools) + self.song_tools_menu.menuAction().setVisible(False) super(SongsPlugin, self).finalise() def new_service_created(self): === modified file 'tests/functional/openlp_core_ui/test_servicemanager.py' --- tests/functional/openlp_core_ui/test_servicemanager.py 2016-07-17 19:46:06 +0000 +++ tests/functional/openlp_core_ui/test_servicemanager.py 2016-09-20 18:07:02 +0000 @@ -28,6 +28,7 @@ import PyQt5 from openlp.core.common import Registry, ThemeLevel +from openlp.core.ui.lib.toolbar import OpenLPToolbar from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities from openlp.core.ui import ServiceManager @@ -544,8 +545,8 @@ self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, 'Should have be called once') - @patch(u'openlp.core.ui.servicemanager.Settings') - @patch(u'PyQt5.QtCore.QTimer.singleShot') + @patch('openlp.core.ui.servicemanager.Settings') + @patch('PyQt5.QtCore.QTimer.singleShot') def test_single_click_preview_true(self, mocked_singleShot, MockedSettings): """ Test that when "Preview items when clicked in Service Manager" enabled the preview timer starts @@ -561,8 +562,8 @@ mocked_singleShot.assert_called_with(PyQt5.QtWidgets.QApplication.instance().doubleClickInterval(), service_manager.on_single_click_preview_timeout) - @patch(u'openlp.core.ui.servicemanager.Settings') - @patch(u'PyQt5.QtCore.QTimer.singleShot') + @patch('openlp.core.ui.servicemanager.Settings') + @patch('PyQt5.QtCore.QTimer.singleShot') def test_single_click_preview_false(self, mocked_singleShot, MockedSettings): """ Test that when "Preview items when clicked in Service Manager" disabled the preview timer doesn't start @@ -577,9 +578,9 @@ # THEN: timer should not be started self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.servicemanager.Settings') - @patch(u'PyQt5.QtCore.QTimer.singleShot') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live') + @patch('openlp.core.ui.servicemanager.Settings') + @patch('PyQt5.QtCore.QTimer.singleShot') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_live') def test_single_click_preview_double(self, mocked_make_live, mocked_singleShot, MockedSettings): """ Test that when a double click has registered the preview timer doesn't start @@ -596,7 +597,7 @@ mocked_make_live.assert_called_with() self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_preview') def test_single_click_timeout_single(self, mocked_make_preview): """ Test that when a single click has been registered, the item is sent to preview @@ -609,8 +610,8 @@ self.assertEqual(mocked_make_preview.call_count, 1, 'ServiceManager.make_preview() should have been called once') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_preview') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_live') def test_single_click_timeout_double(self, mocked_make_live, mocked_make_preview): """ Test that when a double click has been registered, the item does not goes to preview @@ -623,9 +624,9 @@ # THEN: make_preview() should not have been called self.assertEqual(mocked_make_preview.call_count, 0, 'ServiceManager.make_preview() should not be called') - @patch(u'openlp.core.ui.servicemanager.shutil.copy') - @patch(u'openlp.core.ui.servicemanager.zipfile') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as') + @patch('openlp.core.ui.servicemanager.shutil.copy') + @patch('openlp.core.ui.servicemanager.zipfile') + @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as') def test_save_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): """ Test that when a PermissionError is raised when trying to save a file, it is handled correctly @@ -652,9 +653,9 @@ self.assertTrue(result) mocked_save_file_as.assert_called_with() - @patch(u'openlp.core.ui.servicemanager.shutil.copy') - @patch(u'openlp.core.ui.servicemanager.zipfile') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as') + @patch('openlp.core.ui.servicemanager.shutil.copy') + @patch('openlp.core.ui.servicemanager.zipfile') + @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as') def test_save_local_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): """ Test that when a PermissionError is raised when trying to save a local file, it is handled correctly @@ -679,3 +680,66 @@ # THEN: The "save_as" method is called to save the service self.assertTrue(result) mocked_save_file_as.assert_called_with() + + @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') + def test_theme_change_global(self, mocked_regenerate_service_items): + """ + Test that when a Toolbar theme combobox displays correctly when the theme is set to Global + """ + # GIVEN: A service manager, a service to display with a theme level in the renderer + mocked_renderer = MagicMock() + service_manager = ServiceManager(None) + Registry().register('renderer', mocked_renderer) + service_manager.toolbar = OpenLPToolbar(None) + service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock()) + service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock()) + + # WHEN: The service manager has a Global theme + mocked_renderer.theme_level = ThemeLevel.Global + result = service_manager.theme_change() + + # THEN: The the theme toolbar should not be visible + self.assertFalse(service_manager.toolbar.actions['theme_combo_box'].isVisible(), + 'The visibility should be False') + + @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') + def test_theme_change_service(self, mocked_regenerate_service_items): + """ + Test that when a Toolbar theme combobox displays correctly when the theme is set to Theme + """ + # GIVEN: A service manager, a service to display with a theme level in the renderer + mocked_renderer = MagicMock() + service_manager = ServiceManager(None) + Registry().register('renderer', mocked_renderer) + service_manager.toolbar = OpenLPToolbar(None) + service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock()) + service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock()) + + # WHEN: The service manager has a Service theme + mocked_renderer.theme_level = ThemeLevel.Service + result = service_manager.theme_change() + + # THEN: The the theme toolbar should be visible + self.assertTrue(service_manager.toolbar.actions['theme_combo_box'].isVisible(), + 'The visibility should be True') + + @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') + def test_theme_change_song(self, mocked_regenerate_service_items): + """ + Test that when a Toolbar theme combobox displays correctly when the theme is set to Song + """ + # GIVEN: A service manager, a service to display with a theme level in the renderer + mocked_renderer = MagicMock() + service_manager = ServiceManager(None) + Registry().register('renderer', mocked_renderer) + service_manager.toolbar = OpenLPToolbar(None) + service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock()) + service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock()) + + # WHEN: The service manager has a Song theme + mocked_renderer.theme_level = ThemeLevel.Song + result = service_manager.theme_change() + + # THEN: The the theme toolbar should be visible + self.assertTrue(service_manager.toolbar.actions['theme_combo_box'].isVisible(), + 'The visibility should be True')
_______________________________________________ Mailing list: https://launchpad.net/~openlp-core Post to : openlp-core@lists.launchpad.net Unsubscribe : https://launchpad.net/~openlp-core More help : https://help.launchpad.net/ListHelp