← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~trb143/openlp/reporting into lp:openlp

 

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')


Follow ups