diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index b23f00295..2fde4a821 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -102,7 +102,6 @@ class MediaManagerItem(QtGui.QWidget): self.setupUi() self.retranslateUi() self.auto_select_id = -1 - Registry().register_function(u'%s_service_load' % self.plugin.name, self.service_load) # Need to use event as called across threads and UI is updated QtCore.QObject.connect(self, QtCore.SIGNAL(u'%s_go_live' % self.plugin.name), self.go_live_remote) QtCore.QObject.connect(self, QtCore.SIGNAL(u'%s_add_to_service' % self.plugin.name), self.add_to_service_remote) @@ -585,12 +584,15 @@ class MediaManagerItem(QtGui.QWidget): else: return None - def service_load(self, message): + def service_load(self, item): """ Method to add processing when a service has been loaded and individual service items need to be processed by the plugins. + + ``item`` + The item to be processed and returned. """ - pass + return item def check_search_result(self): """ diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 1929f444c..8ddd4135e 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -715,13 +715,10 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog): else: service_item.set_from_service(item, self.servicePath) service_item.validate_item(self.suffixes) - self.load_item_unique_identifier = 0 if service_item.is_capable(ItemCapabilities.OnLoadUpdate): - Registry().execute(u'%s_service_load' % service_item.name.lower(), service_item) - # if the item has been processed - if service_item.unique_identifier == self.load_item_unique_identifier: - service_item.edit_id = int(self.load_item_edit_id) - service_item.temporary_edit = self.load_item_temporary + new_item = Registry().get(service_item.name).service_load(service_item) + if new_item: + service_item = new_item self.add_service_item(service_item, repaint=False) delete_file(p_file) self.main_window.add_recent_file(file_name) @@ -1260,14 +1257,6 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog): self.repaint_service_list(-1, -1) self.application.set_normal_cursor() - def service_item_update(self, edit_id, unique_identifier, temporary=False): - """ - Triggered from plugins to update service items. Save the values as they will be used as part of the service load - """ - self.load_item_unique_identifier = unique_identifier - self.load_item_edit_id = int(edit_id) - self.load_item_temporary = str_to_bool(temporary) - def replace_service_item(self, newItem): """ Using the service item passed replace the one with the same edit id if found. diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index f51db8e6b..c2c67c304 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -40,6 +40,7 @@ from openlp.plugins.custom.lib.db import CustomSlide log = logging.getLogger(__name__) + class CustomSearch(object): """ An enumeration for custom search methods. @@ -214,7 +215,6 @@ class CustomMediaItem(MediaManagerItem): Settings().setValue(u'%s/last search type' % self.settings_section, self.search_text_edit.current_search_type()) # Reload the list considering the new search type. search_keywords = self.search_text_edit.displayText() - search_results = [] search_type = self.search_text_edit.current_search_type() if search_type == CustomSearch.Titles: log.debug(u'Titles Search') @@ -252,7 +252,8 @@ class CustomMediaItem(MediaManagerItem): and_(CustomSlide.title == item.title, CustomSlide.theme_name == item.theme, CustomSlide.credits == item.raw_footer[0][len(item.title) + 1:])) if custom: - self.service_manager.service_item_update(custom.id, item.unique_identifier) + item.edit_id = custom.id + return item else: if self.add_custom_from_service: self.create_from_service_item(item) @@ -281,8 +282,6 @@ class CustomMediaItem(MediaManagerItem): custom.text = unicode(custom_xml.extract_xml(), u'utf-8') self.plugin.manager.save_object(custom) self.on_search_text_button_clicked() - if item.name.lower() == u'custom': - Registry().execute(u'service_item_update', u'%s:%s:%s' % (custom.id, item.unique_identifier, False)) def on_clear_text_button_click(self): """ diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index 3cbe65366..10bc9e328 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -147,7 +147,7 @@ window.OpenLP = { }, pollServer: function () { $.getJSON( - "/stage/api/poll", + "/stage/poll", function (data, status) { var prevItem = OpenLP.currentItem; OpenLP.currentSlide = data.results.slide; diff --git a/openlp/plugins/remotes/html/stage.js b/openlp/plugins/remotes/html/stage.js index dff51537c..42b7712f9 100644 --- a/openlp/plugins/remotes/html/stage.js +++ b/openlp/plugins/remotes/html/stage.js @@ -26,7 +26,7 @@ window.OpenLP = { loadService: function (event) { $.getJSON( - "/stage/api/service/list", + "/stage/service/list", function (data, status) { OpenLP.nextSong = ""; $("#notes").html(""); @@ -46,7 +46,7 @@ window.OpenLP = { }, loadSlides: function (event) { $.getJSON( - "/stage/api/controller/live/text", + "/stage/controller/live/text", function (data, status) { OpenLP.currentSlides = data.results.slides; OpenLP.currentSlide = 0; @@ -137,7 +137,7 @@ window.OpenLP = { }, pollServer: function () { $.getJSON( - "/stage/api/poll", + "/stage/poll", function (data, status) { OpenLP.updateClock(data); if (OpenLP.currentItem != data.results.item || diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 878b197b3..eedc30102 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -267,11 +267,11 @@ class HttpRouter(object): (u'^/(stage)$', self.serve_file), (r'^/files/(.*)$', self.serve_file), (r'^/api/poll$', self.poll), - (r'^/stage/api/poll$', self.poll), + (r'^/stage/poll$', self.poll), (r'^/api/controller/(live|preview)/(.*)$', self.controller), - (r'^/stage/api/controller/(live|preview)/(.*)$', self.controller), + (r'^/stage/controller/(live|preview)/(.*)$', self.controller), (r'^/api/service/(.*)$', self.service), - (r'^/stage/api/service/(.*)$', self.service), + (r'^/stage/service/(.*)$', self.service), (r'^/api/display/(hide|show|blank|theme|desktop)$', self.display), (r'^/api/alert$', self.alert), (r'^/api/plugin/(search)$', self.plugin_info), diff --git a/openlp/plugins/songs/forms/__init__.py b/openlp/plugins/songs/forms/__init__.py index a1c177a9d..588f359f6 100644 --- a/openlp/plugins/songs/forms/__init__.py +++ b/openlp/plugins/songs/forms/__init__.py @@ -52,3 +52,4 @@ This allows OpenLP to use ``self.object`` for all the GUI elements while keeping them separate from the functionality, so that it is easier to recreate the GUI from the .ui files later if necessary. """ +from editsongform import EditSongForm diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index e8e6901ea..5d0bc086c 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -72,10 +72,7 @@ class SongMediaItem(MediaManagerItem): def __init__(self, parent, plugin): self.icon_path = u'songs/song' MediaManagerItem.__init__(self, parent, plugin) - self.edit_song_form = EditSongForm(self, self.main_window, self.plugin.manager) - self.openLyrics = OpenLyrics(self.plugin.manager) self.single_service_item = False - self.song_maintenance_form = SongMaintenanceForm(self.plugin.manager, self) # Holds information about whether the edit is remotely triggered and which Song is required. self.remote_song = -1 self.edit_item = None @@ -132,6 +129,12 @@ class SongMediaItem(MediaManagerItem): 'Maintain the lists of authors, topics and books.')) def initialise(self): + """ + Initialise variables when they cannot be initialised in the constructor. + """ + self.songMaintenanceForm = SongMaintenanceForm(self.plugin.manager, self) + self.editSongForm = EditSongForm(self, self.main_window, self.plugin.manager) + self.openLyrics = OpenLyrics(self.plugin.manager) self.search_text_edit.set_search_types([ (SongSearch.Entire, u':/songs/song_search_all.png', translate('SongsPlugin.MediaItem', 'Entire Song'), @@ -157,7 +160,6 @@ class SongMediaItem(MediaManagerItem): Settings().setValue(u'%s/last search type' % self.settings_section, self.search_text_edit.current_search_type()) # Reload the list considering the new search type. search_keywords = unicode(self.search_text_edit.displayText()) - search_results = [] search_type = self.search_text_edit.current_search_type() if search_type == SongSearch.Entire: log.debug(u'Entire Song Search') @@ -457,14 +459,7 @@ class SongMediaItem(MediaManagerItem): for slide in verses: service_item.add_from_text(unicode(slide)) service_item.title = song.title - author_list = [unicode(author.display_name) for author in song.authors] - service_item.raw_footer.append(song.title) - service_item.raw_footer.append(create_separated_list(author_list)) - service_item.raw_footer.append(song.copyright) - if Settings().value(u'core/ccli number'): - service_item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + - Settings().value(u'core/ccli number')) - service_item.audit = [song.title, author_list, song.copyright, unicode(song.ccli_number)] + author_list = self.generate_footer(service_item, song) service_item.data_string = {u'title': song.search_title, u'authors': u', '.join(author_list)} service_item.xml_version = self.openLyrics.song_to_xml(song) # Add the audio file to the service item. @@ -473,6 +468,30 @@ class SongMediaItem(MediaManagerItem): service_item.background_audio = [m.file_name for m in song.media_files] return True + def generate_footer(self, item, song): + """ + Generates the song footer based on a song and adds details to a service item. + author_list is only required for initial song generation. + + ``item`` + The service item to be amended + + ``song`` + The song to be used to generate the footer + """ + author_list = [unicode(author.display_name) for author in song.authors] + item.audit = [ + song.title, author_list, song.copyright, unicode(song.ccli_number) + ] + item.raw_footer = [] + item.raw_footer.append(song.title) + item.raw_footer.append(create_separated_list(author_list)) + item.raw_footer.append(song.copyright) + if Settings().value(u'core/ccli number'): + item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + + Settings().value(u'core/ccli number')) + return author_list + def service_load(self, item): """ Triggered by a song being loaded by the service manager. @@ -490,9 +509,8 @@ class SongMediaItem(MediaManagerItem): else: search_results = self.plugin.manager.get_all_objects(Song, Song.search_title == item.data_string[u'title'], Song.search_title.asc()) - editId = 0 + edit_id = 0 add_song = True - temporary = False if search_results: for song in search_results: author_list = item.data_string[u'authors'] @@ -505,7 +523,7 @@ class SongMediaItem(MediaManagerItem): break if same_authors and author_list.strip(u', ') == u'': add_song = False - editId = song.id + edit_id = song.id break # If there's any backing tracks, copy them over. if item.background_audio: @@ -523,11 +541,11 @@ class SongMediaItem(MediaManagerItem): # If there's any backing tracks, copy them over. if item.background_audio: self._update_background_audio(song, item) - editId = song.id - temporary = True - # Update service with correct song id. - if editId: - self.service_manager.service_item_update(editId, item.unique_identifier, temporary) + edit_id = song.id + # Update service with correct song id and return it to caller. + item.edit_id = edit_id + self.generate_footer(item, song) + return item def search(self, string, showError): """ diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 65056ee5b..4355bc7e5 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -235,8 +235,7 @@ class SongsPlugin(Plugin): u'delete': translate('SongsPlugin', 'Delete the selected song.'), u'preview': translate('SongsPlugin', 'Preview the selected song.'), u'live': translate('SongsPlugin', 'Send the selected song live.'), - u'service': translate('SongsPlugin', - 'Add the selected song to the service.') + u'service': translate('SongsPlugin', 'Add the selected song to the service.') } self.set_plugin_ui_text_strings(tooltips) diff --git a/tests/functional/openlp_core_lib/test_screen.py b/tests/functional/openlp_core_lib/test_screen.py index 007889a12..7c1bcaf24 100644 --- a/tests/functional/openlp_core_lib/test_screen.py +++ b/tests/functional/openlp_core_lib/test_screen.py @@ -1,7 +1,7 @@ """ Package to test the openlp.core.lib.screenlist package. """ -import copy + from unittest import TestCase from mock import MagicMock diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py new file mode 100644 index 000000000..37f1c76e4 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -0,0 +1,125 @@ +""" +This module contains tests for the lib submodule of the Songs plugin. +""" +import os +from tempfile import mkstemp +from unittest import TestCase + +from mock import patch, MagicMock + +from PyQt4 import QtGui + +from openlp.core.lib import Registry, ServiceItem, Settings + +from openlp.plugins.songs.lib.mediaitem import SongMediaItem + + +class TestMediaItem(TestCase): + """ + Test the functions in the :mod:`lib` module. + """ + def setUp(self): + """ + Set up the components need for all tests. + """ + Registry.create() + Registry().register(u'service_list', MagicMock()) + Registry().register(u'main_window', MagicMock()) + with patch('openlp.core.lib.mediamanageritem.MediaManagerItem.__init__'), \ + patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'): + self.media_item = SongMediaItem(MagicMock(), MagicMock()) + + fd, self.ini_file = mkstemp(u'.ini') + Settings().set_filename(self.ini_file) + self.application = QtGui.QApplication.instance() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.application + # Not all tests use settings! + try: + os.unlink(self.ini_file) + os.unlink(Settings().fileName()) + except Exception: + pass + + def build_song_footer_one_author_test(self): + """ + Test build songs footer with basic song and one author + """ + # GIVEN: A Song and a Service Item + mock_song = MagicMock() + mock_song.title = u'My Song' + mock_author = MagicMock() + mock_author.display_name = u'my author' + mock_song.authors = [] + mock_song.authors.append(mock_author) + mock_song.copyright = u'My copyright' + service_item = ServiceItem(None) + + # WHEN: I generate the Footer with default settings + author_list = self.media_item.generate_footer(service_item, mock_song) + + # THEN: I get the following Array returned + self.assertEqual(service_item.raw_footer, [u'My Song', u'my author', u'My copyright'], + u'The array should be returned correctly with a song, one author and copyright') + self.assertEqual(author_list, [u'my author'], + u'The author list should be returned correctly with one author') + + def build_song_footer_two_authors_test(self): + """ + Test build songs footer with basic song and two authors + """ + # GIVEN: A Song and a Service Item + mock_song = MagicMock() + mock_song.title = u'My Song' + mock_author = MagicMock() + mock_author.display_name = u'my author' + mock_song.authors = [] + mock_song.authors.append(mock_author) + mock_author = MagicMock() + mock_author.display_name = u'another author' + mock_song.authors.append(mock_author) + mock_song.copyright = u'My copyright' + service_item = ServiceItem(None) + + # WHEN: I generate the Footer with default settings + author_list = self.media_item.generate_footer(service_item, mock_song) + + # THEN: I get the following Array returned + self.assertEqual(service_item.raw_footer, [u'My Song', u'my author and another author', u'My copyright'], + u'The array should be returned correctly with a song, two authors and copyright') + self.assertEqual(author_list, [u'my author', u'another author'], + u'The author list should be returned correctly with two authors') + + def build_song_footer_base_ccli_test(self): + """ + Test build songs footer with basic song and two authors + """ + # GIVEN: A Song and a Service Item and a configured CCLI license + mock_song = MagicMock() + mock_song.title = u'My Song' + mock_author = MagicMock() + mock_author.display_name = u'my author' + mock_song.authors = [] + mock_song.authors.append(mock_author) + mock_song.copyright = u'My copyright' + service_item = ServiceItem(None) + Settings().setValue(u'core/ccli number', u'1234') + + # WHEN: I generate the Footer with default settings + self.media_item.generate_footer(service_item, mock_song) + + # THEN: I get the following Array returned + self.assertEqual(service_item.raw_footer, [u'My Song', u'my author', u'My copyright', u'CCLI License: 1234'], + u'The array should be returned correctly with a song, an author, copyright and ccli') + + # WHEN: I amend the CCLI value + Settings().setValue(u'core/ccli number', u'4321') + self.media_item.generate_footer(service_item, mock_song) + + # THEN: I would get an amended footer string + self.assertEqual(service_item.raw_footer, [u'My Song', u'my author', u'My copyright', u'CCLI License: 4321'], + u'The array should be returned correctly with a song, an author, copyright and amended ccli')