From 9f345523649b224c549f039fd899b7f96c87c5f0 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Thu, 3 Jul 2014 23:30:41 +0200 Subject: [PATCH 01/20] PowerPraise importer Fixes: https://launchpad.net/bugs/1336929 --- openlp/plugins/songs/lib/powerpraiseimport.py | 70 +++++++++++++++++++ .../songs/test_powerpraiseimport.py | 54 ++++++++++++++ .../songs/test_propresenterimport.py | 2 +- tests/helpers/songfileimport.py | 20 +++++- .../Näher, mein Gott zu Dir.json | 18 +++++ .../Näher, mein Gott zu Dir.ppl | 2 + 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 openlp/plugins/songs/lib/powerpraiseimport.py create mode 100644 tests/functional/openlp_plugins/songs/test_powerpraiseimport.py create mode 100644 tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json create mode 100644 tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl diff --git a/openlp/plugins/songs/lib/powerpraiseimport.py b/openlp/plugins/songs/lib/powerpraiseimport.py new file mode 100644 index 000000000..56ebff542 --- /dev/null +++ b/openlp/plugins/songs/lib/powerpraiseimport.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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:`powerpraiseimport` module provides the functionality for importing +Powerpraise song files into the current database. +""" + +import os +import base64 +from lxml import objectify + +from openlp.core.ui.wizard import WizardStrings +from openlp.plugins.songs.lib import strip_rtf +from .songimport import SongImport + + +class PowerpraiseImport(SongImport): + """ + The :class:`PowerpraiseImport` class provides OpenLP with the + ability to import Powerpraise song files. + """ + def do_import(self): + self.import_wizard.progress_bar.setMaximum(len(self.import_source)) + for file_path in self.import_source: + if self.stop_import_flag: + return + self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) + root = objectify.parse(open(file_path, 'rb')).getroot() + self.process_song(root) + + def process_song(self, root): + self.set_defaults() + self.title = root.general.title + count = 0; + for part in root.songtext.part: + verse_text = "" + count += 1 + for slide in part.slide: + for line in slide.line: + verse_text += line + print(verse_text) + self.add_verse(verse_text, "v%d" % count) + if not self.finish(): + self.log_error(self.import_source) diff --git a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py new file mode 100644 index 000000000..924cf127c --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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:`powerpraiseimport` module provides the functionality for importing +ProPresenter song files into the current installation database. +""" + +import os + +from tests.helpers.songfileimport import SongImportTestHelper + +TEST_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs')) + + +class TestPowerpraiseFileImport(SongImportTestHelper): + + def __init__(self, *args, **kwargs): + self.importer_class_name = 'PowerpraiseImport' + self.importer_module_name = 'powerpraiseimport' + super(TestPowerpraiseFileImport, self).__init__(*args, **kwargs) + + def test_song_import(self): + """ + Test that loading a PowerPraise file works correctly + """ + self.file_import([os.path.join(TEST_PATH, 'Näher, mein Gott zu Dir.ppl')], + self.load_external_result_data(os.path.join(TEST_PATH, 'Näher, mein Gott zu Dir.json'))) diff --git a/tests/functional/openlp_plugins/songs/test_propresenterimport.py b/tests/functional/openlp_plugins/songs/test_propresenterimport.py index 10e2defc6..9f5760849 100644 --- a/tests/functional/openlp_plugins/songs/test_propresenterimport.py +++ b/tests/functional/openlp_plugins/songs/test_propresenterimport.py @@ -48,7 +48,7 @@ class TestProPresenterFileImport(SongImportTestHelper): def test_song_import(self): """ - Test that loading an ProPresenter file works correctly + Test that loading a ProPresenter file works correctly """ self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')], self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) diff --git a/tests/helpers/songfileimport.py b/tests/helpers/songfileimport.py index 80b2ee268..afa3e48bd 100644 --- a/tests/helpers/songfileimport.py +++ b/tests/helpers/songfileimport.py @@ -31,10 +31,13 @@ The :mod:`songfileimporthelper` modules provides a helper class and methods to e song files from third party applications. """ import json +import logging from unittest import TestCase from tests.functional import patch, MagicMock, call +log = logging.getLogger(__name__) + class SongImportTestHelper(TestCase): """ @@ -107,9 +110,21 @@ class SongImportTestHelper(TestCase): topics = self._get_data(result_data, 'topics') verse_order_list = self._get_data(result_data, 'verse_order_list') - # THEN: do_import should return none, the song data should be as expected, and finish should have been - # called. + # THEN: do_import should return none, the song data should be as expected, and finish should have been called. self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed') + + # Debug information - will be displayed when the test fails + log.debug("Title imported: %s" % importer.title) + log.debug("Verses imported: %s" % self.mocked_add_verse.mock_calls) + log.debug("Verse order imported: %s" % importer.verse_order_list) + log.debug("Authors imported: %s" % self.mocked_add_author.mock_calls) + log.debug("CCLI No. imported: %s" % importer.ccli_number) + log.debug("Comments imported: %s" % importer.comments) + log.debug("Songbook imported: %s" % importer.song_book_name) + log.debug("Song number imported: %s" % importer.song_number) + log.debug("Song copyright imported: %s" % importer.song_number) + log.debug("Topics imported: %s" % importer.topics) + self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title)) for author in author_calls: self.mocked_add_author.assert_any_call(author) @@ -119,6 +134,7 @@ class SongImportTestHelper(TestCase): self.assertEqual(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s' % (source_file_name, ccli_number)) expected_calls = [] + print(self.mocked_add_verse.mock_calls) for verse_text, verse_tag in add_verse_calls: self.mocked_add_verse.assert_any_call(verse_text, verse_tag) expected_calls.append(call(verse_text, verse_tag)) diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json new file mode 100644 index 000000000..54094f748 --- /dev/null +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json @@ -0,0 +1,18 @@ +{ + "title": "Näher, mein Gott, zu Dir", + "verse_order_list": [], + "verses": [ + [ + "Näher, mein Gott, zu Dir,sei meine Bitt'!Näher, o Herr, zu Dirmit jedem Schritt.Nur an dem Herzen Deinkann ich geborgen sein;deshalb die Bitte mein:Näher zu Dir!", + "v1" + ], + [ + "Näher, mein Gott, zu Dir!Ein jeder Tagsoll es neu zeigen mir,was er vermag:Wie seiner Gnade Macht,Erlösung hat gebracht,in uns're Sündennacht.Näher zu Dir!", + "v2" + ], + [ + "Näher, mein Gott, zu Dir!Dich bet' ich an.Wie vieles hast an mir,Du doch getan!Von Banden frei und los,ruh' ich in Deinem Schoss.Ja, Deine Gnad' ist gross!Näher zu Dir!", + "v3" + ] + ] +} \ No newline at end of file diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl new file mode 100644 index 000000000..c0c7f8c19 --- /dev/null +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl @@ -0,0 +1,2 @@ + +Näher, mein Gott, zu DirAnbetungDeutschNäher, mein Gott, zu Dir,sei meine Bitt'!Näher, o Herr, zu Dirmit jedem Schritt.Nur an dem Herzen Deinkann ich geborgen sein;deshalb die Bitte mein:Näher zu Dir!Näher, mein Gott, zu Dir!Ein jeder Tagsoll es neu zeigen mir,was er vermag:Wie seiner Gnade Macht,Erlösung hat gebracht,in uns're Sündennacht.Näher zu Dir!Näher, mein Gott, zu Dir!Dich bet' ich an.Wie vieles hast an mir,Du doch getan!Von Banden frei und los,ruh' ich in Deinem Schoss.Ja, Deine Gnad' ist gross!Näher zu Dir!Teil 1Teil 2Teil 3lastslideText und Musik: Lowell Mason, 1792-1872firstslidegrünes Buch 339Times New Roman44truetrue167772153015Times New Roman20falsefalse167772153020Times New Roman14falsefalse167772153020Times New Roman30falsefalse167772153020false0true0125Blumen\Blume 3.jpg
30
20
leftcenterinline50406070302040
From 7112356c21d65e48bbbbf5b539bd2a4eb573179d Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Thu, 3 Jul 2014 23:48:52 +0200 Subject: [PATCH 02/20] Parse verse order --- openlp/plugins/songs/lib/powerpraiseimport.py | 19 +++++++++++++++---- tests/helpers/songfileimport.py | 1 - .../Näher, mein Gott zu Dir.json | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openlp/plugins/songs/lib/powerpraiseimport.py b/openlp/plugins/songs/lib/powerpraiseimport.py index 56ebff542..27b9c5c17 100644 --- a/openlp/plugins/songs/lib/powerpraiseimport.py +++ b/openlp/plugins/songs/lib/powerpraiseimport.py @@ -57,14 +57,25 @@ class PowerpraiseImport(SongImport): def process_song(self, root): self.set_defaults() self.title = root.general.title - count = 0; + verse_order_list = [] + for item in root.order.item: + verse_order_list.append(str(item)) + + count = 0 for part in root.songtext.part: - verse_text = "" count += 1 + verse_def = "v%d" % count + original_verse_def = part.get('caption') + verse_text = "" for slide in part.slide: for line in slide.line: verse_text += line - print(verse_text) - self.add_verse(verse_text, "v%d" % count) + self.add_verse(verse_text, verse_def) + # Update verse name in verse order list + for i in range(len(verse_order_list)): + if verse_order_list[i].lower() == original_verse_def.lower(): + verse_order_list[i] = verse_def + + self.verse_order_list = verse_order_list if not self.finish(): self.log_error(self.import_source) diff --git a/tests/helpers/songfileimport.py b/tests/helpers/songfileimport.py index afa3e48bd..613bb5c96 100644 --- a/tests/helpers/songfileimport.py +++ b/tests/helpers/songfileimport.py @@ -134,7 +134,6 @@ class SongImportTestHelper(TestCase): self.assertEqual(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s' % (source_file_name, ccli_number)) expected_calls = [] - print(self.mocked_add_verse.mock_calls) for verse_text, verse_tag in add_verse_calls: self.mocked_add_verse.assert_any_call(verse_text, verse_tag) expected_calls.append(call(verse_text, verse_tag)) diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json index 54094f748..b3200ba00 100644 --- a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json @@ -1,6 +1,6 @@ { "title": "Näher, mein Gott, zu Dir", - "verse_order_list": [], + "verse_order_list": ["v1", "v2", "v3"], "verses": [ [ "Näher, mein Gott, zu Dir,sei meine Bitt'!Näher, o Herr, zu Dirmit jedem Schritt.Nur an dem Herzen Deinkann ich geborgen sein;deshalb die Bitte mein:Näher zu Dir!", From 87eb8804de277b90e1e8dde0f33d8da9877ee6aa Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Fri, 4 Jul 2014 00:06:18 +0200 Subject: [PATCH 03/20] Fixes --- openlp/plugins/songs/lib/importer.py | 31 ++++++++++++------- openlp/plugins/songs/lib/powerpraiseimport.py | 12 ++++--- .../songs/test_powerpraiseimport.py | 6 ++-- .../Näher, mein Gott zu Dir.json | 6 ++-- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 6d01da309..659d64d3e 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -51,6 +51,7 @@ from .foilpresenterimport import FoilPresenterImport from .zionworximport import ZionWorxImport from .propresenterimport import ProPresenterImport from .worshipassistantimport import WorshipAssistantImport +from .powerpraiseimport import PowerPraiseImport # Imports that might fail @@ -160,17 +161,18 @@ class SongFormat(object): FoilPresenter = 8 MediaShout = 9 OpenSong = 10 - PowerSong = 11 - ProPresenter = 12 - SongBeamer = 13 - SongPro = 14 - SongShowPlus = 15 - SongsOfFellowship = 16 - SundayPlus = 17 - WordsOfWorship = 18 - WorshipAssistant = 19 - WorshipCenterPro = 20 - ZionWorx = 21 + PowerPraise = 11 + PowerSong = 12 + ProPresenter = 13 + SongBeamer = 14 + SongPro = 15 + SongShowPlus = 16 + SongsOfFellowship = 17 + SundayPlus = 18 + WordsOfWorship = 19 + WorshipAssistant = 20 + WorshipCenterPro = 21 + ZionWorx = 22 # Set optional attribute defaults __defaults__ = { @@ -266,6 +268,12 @@ class SongFormat(object): 'name': WizardStrings.OS, 'prefix': 'openSong' }, + PowerPraise: { + 'class': PowerPraiseImport, + 'name': 'PowerPraise', + 'prefix': 'powerPraise', + 'filter': '%s (*.ppl)' % translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files') + }, PowerSong: { 'class': PowerSongImport, 'name': 'PowerSong 1.0', @@ -374,6 +382,7 @@ class SongFormat(object): SongFormat.FoilPresenter, SongFormat.MediaShout, SongFormat.OpenSong, + SongFormat.PowerPraise, SongFormat.PowerSong, SongFormat.ProPresenter, SongFormat.SongBeamer, diff --git a/openlp/plugins/songs/lib/powerpraiseimport.py b/openlp/plugins/songs/lib/powerpraiseimport.py index 27b9c5c17..0e7bcd9af 100644 --- a/openlp/plugins/songs/lib/powerpraiseimport.py +++ b/openlp/plugins/songs/lib/powerpraiseimport.py @@ -40,7 +40,7 @@ from openlp.plugins.songs.lib import strip_rtf from .songimport import SongImport -class PowerpraiseImport(SongImport): +class PowerPraiseImport(SongImport): """ The :class:`PowerpraiseImport` class provides OpenLP with the ability to import Powerpraise song files. @@ -56,7 +56,7 @@ class PowerpraiseImport(SongImport): def process_song(self, root): self.set_defaults() - self.title = root.general.title + self.title = str(root.general.title) verse_order_list = [] for item in root.order.item: verse_order_list.append(str(item)) @@ -66,11 +66,13 @@ class PowerpraiseImport(SongImport): count += 1 verse_def = "v%d" % count original_verse_def = part.get('caption') - verse_text = "" + verse_text = [] for slide in part.slide: + if not hasattr(slide, 'line'): + continue # No content for line in slide.line: - verse_text += line - self.add_verse(verse_text, verse_def) + verse_text.append(str(line)) + self.add_verse('\n'.join(verse_text), verse_def) # Update verse name in verse order list for i in range(len(verse_order_list)): if verse_order_list[i].lower() == original_verse_def.lower(): diff --git a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py index 924cf127c..2d5a42e6f 100644 --- a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py +++ b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py @@ -39,12 +39,12 @@ TEST_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs')) -class TestPowerpraiseFileImport(SongImportTestHelper): +class TestPowerPraiseFileImport(SongImportTestHelper): def __init__(self, *args, **kwargs): - self.importer_class_name = 'PowerpraiseImport' + self.importer_class_name = 'PowerPraiseImport' self.importer_module_name = 'powerpraiseimport' - super(TestPowerpraiseFileImport, self).__init__(*args, **kwargs) + super(TestPowerPraiseFileImport, self).__init__(*args, **kwargs) def test_song_import(self): """ diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json index b3200ba00..7f258f9e9 100644 --- a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json @@ -3,15 +3,15 @@ "verse_order_list": ["v1", "v2", "v3"], "verses": [ [ - "Näher, mein Gott, zu Dir,sei meine Bitt'!Näher, o Herr, zu Dirmit jedem Schritt.Nur an dem Herzen Deinkann ich geborgen sein;deshalb die Bitte mein:Näher zu Dir!", + "Näher, mein Gott, zu Dir,\nsei meine Bitt'!\nNäher, o Herr, zu Dir\nmit jedem Schritt.\nNur an dem Herzen Dein\nkann ich geborgen sein;\ndeshalb die Bitte mein:\nNäher zu Dir!", "v1" ], [ - "Näher, mein Gott, zu Dir!Ein jeder Tagsoll es neu zeigen mir,was er vermag:Wie seiner Gnade Macht,Erlösung hat gebracht,in uns're Sündennacht.Näher zu Dir!", + "Näher, mein Gott, zu Dir!\nEin jeder Tag\nsoll es neu zeigen mir,\nwas er vermag:\nWie seiner Gnade Macht,\nErlösung hat gebracht,\nin uns're Sündennacht.\nNäher zu Dir!", "v2" ], [ - "Näher, mein Gott, zu Dir!Dich bet' ich an.Wie vieles hast an mir,Du doch getan!Von Banden frei und los,ruh' ich in Deinem Schoss.Ja, Deine Gnad' ist gross!Näher zu Dir!", + "Näher, mein Gott, zu Dir!\nDich bet' ich an.\nWie vieles hast an mir,\nDu doch getan!\nVon Banden frei und los,\nruh' ich in Deinem Schoss.\nJa, Deine Gnad' ist gross!\nNäher zu Dir!", "v3" ] ] From 953adf714595923601be7b288b39bec3d5a39dce Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Fri, 4 Jul 2014 10:35:24 +0200 Subject: [PATCH 04/20] A blank line! --- openlp/plugins/songs/lib/powerpraiseimport.py | 1 - tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/songs/lib/powerpraiseimport.py b/openlp/plugins/songs/lib/powerpraiseimport.py index 0e7bcd9af..ecf1c55fb 100644 --- a/openlp/plugins/songs/lib/powerpraiseimport.py +++ b/openlp/plugins/songs/lib/powerpraiseimport.py @@ -60,7 +60,6 @@ class PowerPraiseImport(SongImport): verse_order_list = [] for item in root.order.item: verse_order_list.append(str(item)) - count = 0 for part in root.songtext.part: count += 1 diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json index 7f258f9e9..630b71949 100644 --- a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json @@ -15,4 +15,4 @@ "v3" ] ] -} \ No newline at end of file +} From 60f11d91734205f3a9481d643ab72ced72313c13 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Fri, 4 Jul 2014 13:18:14 +0200 Subject: [PATCH 05/20] Always display copyright symbol in footer --- openlp/plugins/songs/forms/editsongdialog.py | 8 ++++---- openlp/plugins/songs/forms/editsongform.py | 13 ------------- openlp/plugins/songs/lib/mediaitem.py | 3 ++- openlp/plugins/songs/lib/upgrade.py | 19 ++++++++++++++++--- .../openlp_plugins/songs/test_mediaitem.py | 12 ++++++------ 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index a9ca71946..6abdf5f4f 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -218,12 +218,12 @@ class Ui_EditSongDialog(object): self.rights_layout.setObjectName('rights_layout') self.copyright_layout = QtGui.QHBoxLayout() self.copyright_layout.setObjectName('copyright_layout') + self.copyright_label = QtGui.QLabel(self.rights_group_box) + self.copyright_label.setObjectName('copyright_label') + self.copyright_layout.addWidget(self.copyright_label) self.copyright_edit = QtGui.QLineEdit(self.rights_group_box) self.copyright_edit.setObjectName('copyright_edit') self.copyright_layout.addWidget(self.copyright_edit) - self.copyright_insert_button = QtGui.QToolButton(self.rights_group_box) - self.copyright_insert_button.setObjectName('copyright_insert_button') - self.copyright_layout.addWidget(self.copyright_insert_button) self.rights_layout.addLayout(self.copyright_layout) self.ccli_layout = QtGui.QHBoxLayout() self.ccli_layout.setObjectName('ccli_layout') @@ -318,7 +318,7 @@ class Ui_EditSongDialog(object): self.theme_group_box.setTitle(UiStrings().Theme) self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme')) self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information')) - self.copyright_insert_button.setText(SongStrings.CopyrightSymbol) + self.copyright_label.setText(translate('SongsPlugin.EditSongForm', 'Copyright:')) self.ccli_label.setText(UiStrings().CCLINumberLabel) self.comments_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Comments')) self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.theme_tab), diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 2125922fe..3af7522d2 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -75,7 +75,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.topic_add_button.clicked.connect(self.on_topic_add_button_clicked) self.topic_remove_button.clicked.connect(self.on_topic_remove_button_clicked) self.topics_list_view.itemClicked.connect(self.on_topic_list_view_clicked) - self.copyright_insert_button.clicked.connect(self.on_copyright_insert_button_triggered) self.verse_add_button.clicked.connect(self.on_verse_add_button_clicked) self.verse_list_widget.doubleClicked.connect(self.on_verse_edit_button_clicked) self.verse_edit_button.clicked.connect(self.on_verse_edit_button_clicked) @@ -796,18 +795,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): label_text = self.not_all_verses_used_warning self.warning_label.setText(label_text) - def on_copyright_insert_button_triggered(self): - """ - Copyright insert button pressed - """ - text = self.copyright_edit.text() - pos = self.copyright_edit.cursorPosition() - sign = SongStrings.CopyrightSymbol - text = text[:pos] + sign + text[pos:] - self.copyright_edit.setText(text) - self.copyright_edit.setFocus() - self.copyright_edit.setCursorPosition(pos + len(sign)) - def on_maintenance_button_clicked(self): """ Maintenance button pressed diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index d57c8fbcc..c4eaa73e8 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -506,7 +506,8 @@ class SongMediaItem(MediaManagerItem): if authors_translation: item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation], create_separated_list(authors_translation))) - item.raw_footer.append(song.copyright) + if song.copyright: + item.raw_footer.append('%s %s' % (SongStrings.CopyrightSymbol, song.copyright)) if self.display_songbook and song.book: item.raw_footer.append("%s #%s" % (song.book.name, song.song_number)) if Settings().value('core/ccli number'): diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index 580ae767d..df841b39c 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -27,8 +27,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`upgrade` module provides a way for the database and schema that is the -backend for the Songs plugin +The :mod:`upgrade` module provides a way for the database and schema that is the backend for the Songs plugin """ import logging @@ -39,7 +38,7 @@ from sqlalchemy.sql.expression import func, false, null, text from openlp.core.lib.db import get_upgrade_op log = logging.getLogger(__name__) -__version__ = 4 +__version__ = 5 def upgrade_1(session, metadata): @@ -119,3 +118,17 @@ def upgrade_4(session, metadata): op.rename_table('authors_songs_tmp', 'authors_songs') except OperationalError: log.info('Upgrade 4 has already been run') + + +def upgrade_5(session, metadata): + """ + Version 5 upgrade + + This upgrade removes all hard-coded copyright symbols from the copyright field in the songs. + The copyright symbol is now being added directly in the footer. + """ + try: + op = get_upgrade_op(session) + op.execute('UPDATE songs SET copyright=TRIM(REPLACE(copyright, "©", ""))') + except OperationalError: + log.info('Upgrade 5 has already been run') diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index bc22a4577..da6ae4b71 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -58,7 +58,7 @@ class TestMediaItem(TestCase, TestMixin): author_list = self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', 'My copyright'], + self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', '© My copyright'], 'The array should be returned correctly with a song, one author and copyright') self.assertEqual(author_list, ['my author'], 'The author list should be returned correctly with one author') @@ -97,7 +97,7 @@ class TestMediaItem(TestCase, TestMixin): # THEN: I get the following Array returned self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author', - 'Translation: translator', 'My copyright'], + 'Translation: translator', '© My copyright'], 'The array should be returned correctly with a song, two authors and copyright') self.assertEqual(author_list, ['another author', 'my author', 'translator'], 'The author list should be returned correctly with two authors') @@ -117,7 +117,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'], + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 1234'], 'The array should be returned correctly with a song, an author, copyright and ccli') # WHEN: I amend the CCLI value @@ -125,7 +125,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I would get an amended footer string - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'], + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 4321'], 'The array should be returned correctly with a song, an author, copyright and amended ccli') def build_song_footer_base_songbook_test(self): @@ -145,14 +145,14 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: The songbook should not be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright']) + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright']) # WHEN: I activate the "display songbook" option self.media_item.display_songbook = True self.media_item.generate_footer(service_item, mock_song) # THEN: The songbook should be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12']) + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'My songbook #12']) def authors_match_test(self): """ From 7f9ae1374e5c2c44ffcddd1e19c1fa4fbd515d47 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Fri, 4 Jul 2014 16:16:02 +0200 Subject: [PATCH 06/20] Started authortype editing Fixes: https://launchpad.net/bugs/1336933 --- openlp/plugins/songs/forms/editsongdialog.py | 4 ++++ openlp/plugins/songs/forms/editsongform.py | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index a9ca71946..cdbac7fdb 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -138,6 +138,9 @@ class Ui_EditSongDialog(object): self.author_remove_layout = QtGui.QHBoxLayout() self.author_remove_layout.setObjectName('author_remove_layout') self.author_remove_layout.addStretch() + self.author_edit_button = QtGui.QPushButton(self.authors_group_box) + self.author_edit_button.setObjectName('author_edit_button') + self.author_remove_layout.addWidget(self.author_edit_button) self.author_remove_button = QtGui.QPushButton(self.authors_group_box) self.author_remove_button.setObjectName('author_remove_button') self.author_remove_layout.addWidget(self.author_remove_button) @@ -305,6 +308,7 @@ class Ui_EditSongDialog(object): translate('SongsPlugin.EditSongForm', 'Title && Lyrics')) self.authors_group_box.setTitle(SongStrings.Authors) self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song')) + self.author_edit_button.setText(translate('SongsPlugin.EditSongForm', '&Edit Author Type')) self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Song Books')) self.topics_group_box.setTitle(SongStrings.Topic) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 2125922fe..9c7d4a61e 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -70,6 +70,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.setupUi(self) # Connecting signals and slots self.author_add_button.clicked.connect(self.on_author_add_button_clicked) + self.author_edit_button.clicked.connect(self.on_author_edit_button_clicked) self.author_remove_button.clicked.connect(self.on_author_remove_button_clicked) self.authors_list_view.itemClicked.connect(self.on_authors_list_view_clicked) self.topic_add_button.clicked.connect(self.on_topic_add_button_clicked) @@ -334,6 +335,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): """ self.verse_edit_button.setEnabled(False) self.verse_delete_button.setEnabled(False) + self.author_edit_button.setEnabled(False) self.author_remove_button.setEnabled(False) self.topic_remove_button.setEnabled(False) @@ -596,9 +598,23 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): """ Run a set of actions when an author in the list is selected (mainly enable the delete button). """ - if self.authors_list_view.count() > 1: + count = self.authors_list_view.count() + if count > 0: + self.author_edit_button.setEnabled(True) + if count > 1: + # There must be at least one author self.author_remove_button.setEnabled(True) + def on_author_edit_button_clicked(self): + """ + Show a dialog to change the type of an author when the edit button is clicked + """ + self.author_edit_button.setEnabled(False) + item = self.authors_list_view.currentItem() + author_id, author_type = item.data(QtCore.Qt.UserRole) + + #dialog = QtGui.QDialog(self) + def on_author_remove_button_clicked(self): """ Remove the author from the list when the delete button is clicked. From cfd9631d042bbf3f29935137f1c8512ba71af823 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sat, 5 Jul 2014 18:21:43 +0200 Subject: [PATCH 07/20] Parse verse names --- openlp/plugins/songs/lib/powerpraiseimport.py | 17 +++++++++--- .../songs/test_powerpraiseimport.py | 2 ++ .../powerpraisesongs/You are so faithful.json | 26 +++++++++++++++++++ .../powerpraisesongs/You are so faithful.ppl | 2 ++ 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 tests/resources/powerpraisesongs/You are so faithful.json create mode 100644 tests/resources/powerpraisesongs/You are so faithful.ppl diff --git a/openlp/plugins/songs/lib/powerpraiseimport.py b/openlp/plugins/songs/lib/powerpraiseimport.py index ecf1c55fb..6a67619b3 100644 --- a/openlp/plugins/songs/lib/powerpraiseimport.py +++ b/openlp/plugins/songs/lib/powerpraiseimport.py @@ -58,13 +58,24 @@ class PowerPraiseImport(SongImport): self.set_defaults() self.title = str(root.general.title) verse_order_list = [] + verse_count = {} for item in root.order.item: verse_order_list.append(str(item)) - count = 0 for part in root.songtext.part: - count += 1 - verse_def = "v%d" % count original_verse_def = part.get('caption') + # There are some predefined verse defitions in PowerPraise, try to parse these + if original_verse_def.startswith("Strophe") or original_verse_def.startswith("Teil"): + verse_def = 'v' + elif original_verse_def.startswith("Refrain"): + verse_def = 'c' + elif original_verse_def.startswith("Bridge"): + verse_def = 'b' + elif original_verse_def.startswith("Schluss"): + verse_def = 'e' + else: + verse_def = 'o' + verse_count[verse_def] = verse_count.get(verse_def, 0) + 1 + verse_def = '%s%d' % (verse_def, verse_count[verse_def]) verse_text = [] for slide in part.slide: if not hasattr(slide, 'line'): diff --git a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py index 2d5a42e6f..fa2c5f5b5 100644 --- a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py +++ b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py @@ -52,3 +52,5 @@ class TestPowerPraiseFileImport(SongImportTestHelper): """ self.file_import([os.path.join(TEST_PATH, 'Näher, mein Gott zu Dir.ppl')], self.load_external_result_data(os.path.join(TEST_PATH, 'Näher, mein Gott zu Dir.json'))) + self.file_import([os.path.join(TEST_PATH, 'You are so faithful.ppl')], + self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json'))) diff --git a/tests/resources/powerpraisesongs/You are so faithful.json b/tests/resources/powerpraisesongs/You are so faithful.json new file mode 100644 index 000000000..855b52f67 --- /dev/null +++ b/tests/resources/powerpraisesongs/You are so faithful.json @@ -0,0 +1,26 @@ +{ + "title": "You are so faithful", + "verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1", "v4"], + "verses": [ + [ + "You are so faithful\nso faithful, so faithful.\nYou are so faithful\nso faithful, so faithful.", + "v1" + ], + [ + "That's why I praise you\nin the morning\nThat's why I praise you\nin the noontime.\nThat's why I praise you\nin the evening\nThat's why I praise you\nall the time.", + "c1" + ], + [ + "You are so loving\nso loving, so loving.\nYou are so loving\nso loving, so loving.", + "v2" + ], + [ + "You are so caring\nso caring, so caring.\nYou are so caring\nso caring, so caring.", + "v3" + ], + [ + "You are so mighty\nso mighty, so mighty.\nYou are so mighty\nso mighty, so mighty.", + "v4" + ] + ] +} diff --git a/tests/resources/powerpraisesongs/You are so faithful.ppl b/tests/resources/powerpraisesongs/You are so faithful.ppl new file mode 100644 index 000000000..420ae8fb6 --- /dev/null +++ b/tests/resources/powerpraisesongs/You are so faithful.ppl @@ -0,0 +1,2 @@ + +You are so faithfulLobpreisEnglischYou are so faithfulso faithful, so faithful.Du bist so treuso treu, so treu.You are so faithfulso faithful, so faithful.Du bist so treuso treu, so treu.That's why I praise youin the morningThat's why I praise youin the noontime.Deshalb preise ich Dicham MorgenDeshalb preise ich Dicham Mittag.That's why I praise youin the eveningThat's why I praise youall the time.Deshalb preise ich Dicham AbendDeshalb preise ich Dichallezeit.You are so lovingso loving, so loving.Du bist so liebevollso liebevoll, so liebevoll.You are so lovingso loving, so loving.Du bist so liebevollso liebevoll, so liebevoll.You are so caringso caring, so caring.Du sorgst so gutDu kümmerst dich um uns.You are so caringso caring, so caring.Du sorgst so gutDu kümmerst dich um uns.You are so mightyso mighty, so mighty.Du bist so mächtigso mächtig, so mächtig.You are so mightyso mighty, so mighty.Du bist so mächtigso mächtig, so mächtig.Strophe 1RefrainStrophe 2RefrainStrophe 3RefrainStrophe 4lastslideMusik & Copyright unbekanntfirstslideTahoma30truefalse167772153020Tahoma20falsefalse167772153020Tahoma14falsefalse167772153020Tahoma30falsefalse167772153020true0true0125Blumen\Blume 6.jpg
30
20
centercenterinline50406070302040
From 5d7b60f714974fb840a2290bc1767d224bf014d3 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sat, 5 Jul 2014 21:56:32 +0200 Subject: [PATCH 08/20] Changing Author type functional --- openlp/plugins/songs/forms/editsongform.py | 20 +++++++++++------ openlp/plugins/songs/lib/db.py | 25 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 9c7d4a61e..10a235c5c 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -356,12 +356,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): # Types self.author_types_combo_box.clear() - self.author_types_combo_box.addItem('') # Don't iterate over the dictionary to give them this specific order - self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Words], AuthorType.Words) - self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Music], AuthorType.Music) - self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.WordsAndMusic], AuthorType.WordsAndMusic) - self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Translation], AuthorType.Translation) + for author_type in AuthorType.SortedTypes: + self.author_types_combo_box.addItem(AuthorType.Types[author_type], author_type) def load_topics(self): """ @@ -612,8 +609,17 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.author_edit_button.setEnabled(False) item = self.authors_list_view.currentItem() author_id, author_type = item.data(QtCore.Qt.UserRole) - - #dialog = QtGui.QDialog(self) + choice, ok = QtGui.QInputDialog.getItem(self, translate('SongsPlugin.EditSongForm', 'Edit Author Type'), + translate('SongsPlugin.EditSongForm', 'Choose type for this author'), + AuthorType.TranslatedTypes, + current=AuthorType.SortedTypes.index(author_type), + editable=False) + if not ok: + return + author = self.manager.get_object(Author, author_id) + author_type = AuthorType.from_translated_text(choice) + item.setData(QtCore.Qt.UserRole, (author_id, author_type)) + item.setText(author.get_display_name(author_type)) def on_author_remove_button_clicked(self): """ diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index 16f7ea719..96a9e8c1f 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -69,17 +69,42 @@ class AuthorType(object): The 'words+music' type is not an official type, but is provided for convenience. """ + NoType = '' Words = 'words' Music = 'music' WordsAndMusic = 'words+music' Translation = 'translation' Types = { + NoType: '', Words: translate('SongsPlugin.AuthorType', 'Words', 'Author who wrote the lyrics of a song'), Music: translate('SongsPlugin.AuthorType', 'Music', 'Author who wrote the music of a song'), WordsAndMusic: translate('SongsPlugin.AuthorType', 'Words and Music', 'Author who wrote both lyrics and music of a song'), Translation: translate('SongsPlugin.AuthorType', 'Translation', 'Author who translated the song') } + SortedTypes = [ + NoType, + Words, + Music, + WordsAndMusic + ] + TranslatedTypes = [ + Types[NoType], + Types[Words], + Types[Music], + Types[WordsAndMusic] + ] + + @staticmethod + def from_translated_text(translated_type): + """ + Get the AuthorType from a translated string. + :param translated_type: Translated Author type. + """ + for key, value in AuthorType.Types.items(): + if value == translated_type: + return key + return None class Book(BaseModel): From 8e8288ddaacd440c3cce0b681f0ed2155ac4ead2 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sat, 5 Jul 2014 22:04:17 +0200 Subject: [PATCH 09/20] Add test --- openlp/plugins/songs/lib/db.py | 2 +- tests/functional/openlp_plugins/songs/test_db.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index 96a9e8c1f..a9206a397 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -104,7 +104,7 @@ class AuthorType(object): for key, value in AuthorType.Types.items(): if value == translated_type: return key - return None + return AuthorType.NoType class Book(BaseModel): diff --git a/tests/functional/openlp_plugins/songs/test_db.py b/tests/functional/openlp_plugins/songs/test_db.py index 3080db77e..53b98c045 100644 --- a/tests/functional/openlp_plugins/songs/test_db.py +++ b/tests/functional/openlp_plugins/songs/test_db.py @@ -112,3 +112,16 @@ class TestDB(TestCase): # THEN: It should have been removed and the other author should still be there self.assertEqual(1, len(song.authors_songs)) self.assertEqual(None, song.authors_songs[0].author_type) + + def test_get_author_type_from_translated_text(self): + """ + Test getting an author type from translated text + """ + # GIVEN: A string with an author type + author_type_name = AuthorType.Types[AuthorType.Words] + + # WHEN: We call the method + author_type = AuthorType.from_translated_text(author_type_name) + + # THEN: The type should be correct + self.assertEqual(author_type, AuthorType.Words) From b92ca3f2093b0e4232df51edae584ec95e9a4c90 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 7 Jul 2014 13:17:24 +0200 Subject: [PATCH 10/20] Fix service up movement Fixes: https://launchpad.net/bugs/1338316 --- openlp/core/ui/servicemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 52afb5edc..592b01524 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -1103,7 +1103,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage Moves the cursor selection up the window. Called by the up arrow. """ item = self.service_manager_list.currentItem() - item_before = self.service_manager_list.item_above(item) + item_before = self.service_manager_list.itemAbove(item) if item_before is None: return self.service_manager_list.setCurrentItem(item_before) From 201cfad9c48cdb66a63b45f27f73d4cf03f24aa0 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 7 Jul 2014 14:25:00 +0200 Subject: [PATCH 11/20] Remove unused imports --- openlp/plugins/songs/lib/powerpraiseimport.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openlp/plugins/songs/lib/powerpraiseimport.py b/openlp/plugins/songs/lib/powerpraiseimport.py index 6a67619b3..ff30f9763 100644 --- a/openlp/plugins/songs/lib/powerpraiseimport.py +++ b/openlp/plugins/songs/lib/powerpraiseimport.py @@ -32,11 +32,9 @@ Powerpraise song files into the current database. """ import os -import base64 from lxml import objectify from openlp.core.ui.wizard import WizardStrings -from openlp.plugins.songs.lib import strip_rtf from .songimport import SongImport From c1df88693dca0e7bc3fc8e0848b6fcf5a9e6cae1 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Wed, 9 Jul 2014 14:19:32 +0200 Subject: [PATCH 12/20] Revert changes --- openlp/plugins/songs/forms/editsongdialog.py | 8 ++++---- openlp/plugins/songs/forms/editsongform.py | 13 +++++++++++++ openlp/plugins/songs/lib/mediaitem.py | 3 +-- openlp/plugins/songs/lib/upgrade.py | 19 +++---------------- .../openlp_plugins/songs/test_mediaitem.py | 12 ++++++------ 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index 6abdf5f4f..a9ca71946 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -218,12 +218,12 @@ class Ui_EditSongDialog(object): self.rights_layout.setObjectName('rights_layout') self.copyright_layout = QtGui.QHBoxLayout() self.copyright_layout.setObjectName('copyright_layout') - self.copyright_label = QtGui.QLabel(self.rights_group_box) - self.copyright_label.setObjectName('copyright_label') - self.copyright_layout.addWidget(self.copyright_label) self.copyright_edit = QtGui.QLineEdit(self.rights_group_box) self.copyright_edit.setObjectName('copyright_edit') self.copyright_layout.addWidget(self.copyright_edit) + self.copyright_insert_button = QtGui.QToolButton(self.rights_group_box) + self.copyright_insert_button.setObjectName('copyright_insert_button') + self.copyright_layout.addWidget(self.copyright_insert_button) self.rights_layout.addLayout(self.copyright_layout) self.ccli_layout = QtGui.QHBoxLayout() self.ccli_layout.setObjectName('ccli_layout') @@ -318,7 +318,7 @@ class Ui_EditSongDialog(object): self.theme_group_box.setTitle(UiStrings().Theme) self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme')) self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information')) - self.copyright_label.setText(translate('SongsPlugin.EditSongForm', 'Copyright:')) + self.copyright_insert_button.setText(SongStrings.CopyrightSymbol) self.ccli_label.setText(UiStrings().CCLINumberLabel) self.comments_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Comments')) self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.theme_tab), diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 3af7522d2..2125922fe 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -75,6 +75,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.topic_add_button.clicked.connect(self.on_topic_add_button_clicked) self.topic_remove_button.clicked.connect(self.on_topic_remove_button_clicked) self.topics_list_view.itemClicked.connect(self.on_topic_list_view_clicked) + self.copyright_insert_button.clicked.connect(self.on_copyright_insert_button_triggered) self.verse_add_button.clicked.connect(self.on_verse_add_button_clicked) self.verse_list_widget.doubleClicked.connect(self.on_verse_edit_button_clicked) self.verse_edit_button.clicked.connect(self.on_verse_edit_button_clicked) @@ -795,6 +796,18 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): label_text = self.not_all_verses_used_warning self.warning_label.setText(label_text) + def on_copyright_insert_button_triggered(self): + """ + Copyright insert button pressed + """ + text = self.copyright_edit.text() + pos = self.copyright_edit.cursorPosition() + sign = SongStrings.CopyrightSymbol + text = text[:pos] + sign + text[pos:] + self.copyright_edit.setText(text) + self.copyright_edit.setFocus() + self.copyright_edit.setCursorPosition(pos + len(sign)) + def on_maintenance_button_clicked(self): """ Maintenance button pressed diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index c4eaa73e8..d57c8fbcc 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -506,8 +506,7 @@ class SongMediaItem(MediaManagerItem): if authors_translation: item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation], create_separated_list(authors_translation))) - if song.copyright: - item.raw_footer.append('%s %s' % (SongStrings.CopyrightSymbol, song.copyright)) + item.raw_footer.append(song.copyright) if self.display_songbook and song.book: item.raw_footer.append("%s #%s" % (song.book.name, song.song_number)) if Settings().value('core/ccli number'): diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index df841b39c..580ae767d 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -27,7 +27,8 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`upgrade` module provides a way for the database and schema that is the backend for the Songs plugin +The :mod:`upgrade` module provides a way for the database and schema that is the +backend for the Songs plugin """ import logging @@ -38,7 +39,7 @@ from sqlalchemy.sql.expression import func, false, null, text from openlp.core.lib.db import get_upgrade_op log = logging.getLogger(__name__) -__version__ = 5 +__version__ = 4 def upgrade_1(session, metadata): @@ -118,17 +119,3 @@ def upgrade_4(session, metadata): op.rename_table('authors_songs_tmp', 'authors_songs') except OperationalError: log.info('Upgrade 4 has already been run') - - -def upgrade_5(session, metadata): - """ - Version 5 upgrade - - This upgrade removes all hard-coded copyright symbols from the copyright field in the songs. - The copyright symbol is now being added directly in the footer. - """ - try: - op = get_upgrade_op(session) - op.execute('UPDATE songs SET copyright=TRIM(REPLACE(copyright, "©", ""))') - except OperationalError: - log.info('Upgrade 5 has already been run') diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index da6ae4b71..bc22a4577 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -58,7 +58,7 @@ class TestMediaItem(TestCase, TestMixin): author_list = self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', '© My copyright'], + self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', 'My copyright'], 'The array should be returned correctly with a song, one author and copyright') self.assertEqual(author_list, ['my author'], 'The author list should be returned correctly with one author') @@ -97,7 +97,7 @@ class TestMediaItem(TestCase, TestMixin): # THEN: I get the following Array returned self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author', - 'Translation: translator', '© My copyright'], + 'Translation: translator', 'My copyright'], 'The array should be returned correctly with a song, two authors and copyright') self.assertEqual(author_list, ['another author', 'my author', 'translator'], 'The author list should be returned correctly with two authors') @@ -117,7 +117,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 1234'], + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'], 'The array should be returned correctly with a song, an author, copyright and ccli') # WHEN: I amend the CCLI value @@ -125,7 +125,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I would get an amended footer string - self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 4321'], + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'], 'The array should be returned correctly with a song, an author, copyright and amended ccli') def build_song_footer_base_songbook_test(self): @@ -145,14 +145,14 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: The songbook should not be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright']) + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright']) # WHEN: I activate the "display songbook" option self.media_item.display_songbook = True self.media_item.generate_footer(service_item, mock_song) # THEN: The songbook should be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'My songbook #12']) + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12']) def authors_match_test(self): """ From 383f7ef6e2128f74fb3d9a060e80dada09ebeb45 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Wed, 9 Jul 2014 14:33:26 +0200 Subject: [PATCH 13/20] Option for displaying copyright symbol --- openlp/plugins/songs/lib/mediaitem.py | 11 +++++++++-- openlp/plugins/songs/lib/songstab.py | 14 ++++++++++++++ openlp/plugins/songs/songsplugin.py | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index fde103c5f..0d4fe5908 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -36,7 +36,7 @@ from PyQt4 import QtCore, QtGui from sqlalchemy.sql import or_ from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate -from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItem, ServiceItemContext, \ +from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \ check_item_selected, create_separated_list from openlp.core.lib.ui import create_widget_action from openlp.plugins.songs.forms.editsongform import EditSongForm @@ -126,6 +126,7 @@ class SongMediaItem(MediaManagerItem): self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit') self.add_song_from_service = Settings().value(self.settings_section + '/add song from service') self.display_songbook = Settings().value(self.settings_section + '/display songbook') + self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol') def retranslateUi(self): self.search_text_label.setText('%s:' % UiStrings().Search) @@ -506,7 +507,13 @@ class SongMediaItem(MediaManagerItem): if authors_translation: item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation], create_separated_list(authors_translation))) - item.raw_footer.append(song.copyright) + if song.copyright: + if self.display_copyright_symbol: + print("copyright") + item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright)) + else: + print("no copyright") + item.raw_footer.append(song.copyright) if self.display_songbook and song.book: item.raw_footer.append("%s #%s" % (song.book.name, song.song_number)) if Settings().value('core/ccli number'): diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index 1cf06d047..09d409c4a 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -31,6 +31,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.common import Settings, translate from openlp.core.lib import SettingsTab +from openlp.plugins.songs.lib.ui import SongStrings class SongsTab(SettingsTab): @@ -62,6 +63,9 @@ class SongsTab(SettingsTab): self.display_songbook_check_box = QtGui.QCheckBox(self.mode_group_box) self.display_songbook_check_box.setObjectName('songbook_check_box') self.mode_layout.addWidget(self.display_songbook_check_box) + self.display_copyright_check_box = QtGui.QCheckBox(self.mode_group_box) + self.display_copyright_check_box.setObjectName('copyright_check_box') + self.mode_layout.addWidget(self.display_copyright_check_box) self.left_layout.addWidget(self.mode_group_box) self.left_layout.addStretch() self.right_layout.addStretch() @@ -70,6 +74,7 @@ class SongsTab(SettingsTab): self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed) self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed) self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed) + self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed) def retranslateUi(self): self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode')) @@ -80,6 +85,9 @@ class SongsTab(SettingsTab): self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab', 'Import missing songs from service files')) self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer')) + self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab', + 'Display "%s" symbol before copyright info' % + SongStrings.CopyrightSymbol)) def on_search_as_type_check_box_changed(self, check_state): self.song_search = (check_state == QtCore.Qt.Checked) @@ -96,6 +104,9 @@ class SongsTab(SettingsTab): def on_songbook_check_box_changed(self, check_state): self.display_songbook = (check_state == QtCore.Qt.Checked) + def on_copyright_check_box_changed(self, check_state): + self.display_copyright_symbol = (check_state == QtCore.Qt.Checked) + def load(self): settings = Settings() settings.beginGroup(self.settings_section) @@ -104,11 +115,13 @@ class SongsTab(SettingsTab): self.update_edit = settings.value('update service on edit') self.update_load = settings.value('add song from service') self.display_songbook = settings.value('display songbook') + self.display_copyright_symbol = settings.value('display copyright symbol') self.search_as_type_check_box.setChecked(self.song_search) self.tool_bar_active_check_box.setChecked(self.tool_bar) self.update_on_edit_check_box.setChecked(self.update_edit) self.add_from_service_check_box.setChecked(self.update_load) self.display_songbook_check_box.setChecked(self.display_songbook) + self.display_copyright_check_box.setChecked(self.display_copyright_symbol) settings.endGroup() def save(self): @@ -119,6 +132,7 @@ class SongsTab(SettingsTab): settings.setValue('update service on edit', self.update_edit) settings.setValue('add song from service', self.update_load) settings.setValue('display songbook', self.display_songbook) + settings.setValue('display copyright symbol', self.display_copyright_symbol) settings.endGroup() if self.tab_visited: self.settings_form.register_post_process('songs_config_updated') diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 5cbedc994..56834a6eb 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -64,6 +64,7 @@ __default_settings__ = { 'songs/add song from service': True, 'songs/display songbar': True, 'songs/display songbook': False, + 'songs/display copyright symbol': False, 'songs/last directory import': '', 'songs/last directory export': '', 'songs/songselect username': '', From 79009a1a7fce07d8fef5600ac9374696fb1373c7 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Wed, 9 Jul 2014 14:41:55 +0200 Subject: [PATCH 14/20] Add test --- .../openlp_plugins/songs/test_mediaitem.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index bc22a4577..f3ae511ea 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -28,6 +28,7 @@ class TestMediaItem(TestCase, TestMixin): patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'): self.media_item = SongMediaItem(None, MagicMock()) self.media_item.display_songbook = False + self.media_item.display_copyright_symbol = False self.get_application() self.build_settings() QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) @@ -154,6 +155,23 @@ class TestMediaItem(TestCase, TestMixin): # THEN: The songbook should be in the footer self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12']) + def build_song_footer_copyright_test(self): + """ + Test building song footer with displaying the copyright symbol + """ + # GIVEN: A Song and a Service Item; displaying the copyright symbol is enabled + self.media_item.display_copyright_symbol = True + mock_song = MagicMock() + mock_song.title = 'My Song' + mock_song.copyright = 'My copyright' + service_item = ServiceItem(None) + + # WHEN: I generate the Footer with default settings + self.media_item.generate_footer(service_item, mock_song) + + # THEN: The copyright symbol should be in the footer + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright']) + def authors_match_test(self): """ Test the author matching when importing a song from a service From 6d5e6f0fafb0e290123ecf4429ff572422c60e15 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Wed, 9 Jul 2014 14:45:54 +0200 Subject: [PATCH 15/20] Remove debug output --- openlp/plugins/songs/lib/mediaitem.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 0d4fe5908..33c4f2e53 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -509,10 +509,8 @@ class SongMediaItem(MediaManagerItem): create_separated_list(authors_translation))) if song.copyright: if self.display_copyright_symbol: - print("copyright") item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright)) else: - print("no copyright") item.raw_footer.append(song.copyright) if self.display_songbook and song.book: item.raw_footer.append("%s #%s" % (song.book.name, song.song_number)) From 79c5d61d9483f7cf0b63be3aeadc3afca1a72862 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sat, 12 Jul 2014 22:00:58 +0200 Subject: [PATCH 16/20] Negative test --- .../openlp_plugins/songs/test_mediaitem.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index f3ae511ea..a473f8569 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -155,7 +155,7 @@ class TestMediaItem(TestCase, TestMixin): # THEN: The songbook should be in the footer self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12']) - def build_song_footer_copyright_test(self): + def build_song_footer_copyright_enabled_test(self): """ Test building song footer with displaying the copyright symbol """ @@ -172,6 +172,22 @@ class TestMediaItem(TestCase, TestMixin): # THEN: The copyright symbol should be in the footer self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright']) + def build_song_footer_copyright_disabled_test(self): + """ + Test building song footer without displaying the copyright symbol + """ + # GIVEN: A Song and a Service Item; displaying the copyright symbol should be disabled by default + mock_song = MagicMock() + mock_song.title = 'My Song' + mock_song.copyright = 'My copyright' + service_item = ServiceItem(None) + + # WHEN: I generate the Footer with default settings + self.media_item.generate_footer(service_item, mock_song) + + # THEN: The copyright symbol should not be in the footer + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright']) + def authors_match_test(self): """ Test the author matching when importing a song from a service From f2d3bbee475d43e4f8c72499b7c887743cdb52bf Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 13 Jul 2014 01:47:53 +0200 Subject: [PATCH 17/20] Added the name of the current item to the service controller, and added some more tests --- openlp/core/ui/slidecontroller.py | 24 +- .../openlp_core_ui/test_slidecontroller.py | 504 +++++++++++++++++- 2 files changed, 512 insertions(+), 16 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index bf92e9d76..44c28deb6 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -96,6 +96,8 @@ class DisplayController(QtGui.QWidget): """ This is the generic function to send signal for control widgets, created from within other plugins This function is needed to catch the current controller + + :param args: Arguments to send to the plugins """ sender = self.sender().objectName() if self.sender().objectName() else self.sender().text() controller = self @@ -143,11 +145,19 @@ class SlideController(DisplayController, RegistryProperties): self.panel_layout = QtGui.QVBoxLayout(self.panel) self.panel_layout.setSpacing(0) self.panel_layout.setMargin(0) - # Type label for the top of the slide controller + # Type label at the top of the slide controller self.type_label = QtGui.QLabel(self.panel) self.type_label.setStyleSheet('font-weight: bold; font-size: 12pt;') self.type_label.setAlignment(QtCore.Qt.AlignCenter) + if self.is_live: + self.type_label.setText(UiStrings().Live) + else: + self.type_label.setText(UiStrings().Preview) self.panel_layout.addWidget(self.type_label) + # Info label for the title of the current item, at the top of the slide controller + self.info_label = QtGui.QLabel(self.panel) + self.info_label.setAlignment(QtCore.Qt.AlignCenter) + self.panel_layout.addWidget(self.info_label) # Splitter self.splitter = QtGui.QSplitter(self.panel) self.splitter.setOrientation(QtCore.Qt.Vertical) @@ -402,12 +412,17 @@ class SlideController(DisplayController, RegistryProperties): """ try: from openlp.plugins.songs.lib import VerseType - SONGS_PLUGIN_AVAILABLE = True + is_songs_plugin_available = True except ImportError: - SONGS_PLUGIN_AVAILABLE = False + class VerseType(object): + """ + This empty class is mostly just to satisfy Python, PEP8 and PyCharm + """ + pass + is_songs_plugin_available = False sender_name = self.sender().objectName() verse_type = sender_name[15:] if sender_name[:15] == 'shortcutAction_' else '' - if SONGS_PLUGIN_AVAILABLE: + if is_songs_plugin_available: if verse_type == 'V': self.current_shortcut = VerseType.translated_tags[VerseType.Verse] elif verse_type == 'C': @@ -777,6 +792,7 @@ class SlideController(DisplayController, RegistryProperties): if service_item.is_command(): Registry().execute( '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no]) + self.info_label.setText(self.service_item.title) self.slide_list = {} if self.is_live: self.song_menu.menu().clear() diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index 104c83750..3a1e754f8 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -30,10 +30,13 @@ Package to test the openlp.core.ui.slidecontroller package. """ from unittest import TestCase +from openlp.core import Registry +from openlp.core.lib import ServiceItemAction from openlp.core.ui import SlideController +from openlp.core.ui.slidecontroller import WIDE_MENU, NON_TEXT_MENU -from tests.interfaces import MagicMock +from tests.interfaces import MagicMock, patch class TestSlideController(TestCase): @@ -42,37 +45,514 @@ class TestSlideController(TestCase): """ Test the initial slide controller state . """ - # GIVEN: A new slideController instance. + # GIVEN: A new SlideController instance. slide_controller = SlideController(None) + # WHEN: the default controller is built. # THEN: The controller should not be a live controller. self.assertEqual(slide_controller.is_live, False, 'The base slide controller should not be a live controller') - def toggle_blank_test(self): + def text_service_item_blank_test(self): """ - Test the setting of the display blank icons by display type. + Test that loading a text-based service item into the slide controller sets the correct blank menu """ - # GIVEN: A new slideController instance. + # GIVEN: A new SlideController instance. slide_controller = SlideController(None) service_item = MagicMock() toolbar = MagicMock() - toolbar.set_widget_visible = self.dummy_widget_visible + toolbar.set_widget_visible = MagicMock() slide_controller.toolbar = toolbar slide_controller.service_item = service_item - # WHEN a text based service item is used + # WHEN: a text based service item is used slide_controller.service_item.is_text = MagicMock(return_value=True) slide_controller.set_blank_menu() - # THEN: then call set up the toolbar to blank the display screen. - self.assertEqual(len(self.test_widget), 3, 'There should be three icons to display on the screen') + # THEN: the call to set the visible items on the toolbar should be correct + toolbar.set_widget_visible.assert_called_with(WIDE_MENU, True) + + def non_text_service_item_blank_test(self): + """ + Test that loading a non-text service item into the slide controller sets the correct blank menu + """ + # GIVEN: A new SlideController instance. + slide_controller = SlideController(None) + service_item = MagicMock() + toolbar = MagicMock() + toolbar.set_widget_visible = MagicMock() + slide_controller.toolbar = toolbar + slide_controller.service_item = service_item # WHEN a non text based service item is used slide_controller.service_item.is_text = MagicMock(return_value=False) slide_controller.set_blank_menu() # THEN: then call set up the toolbar to blank the display screen. - self.assertEqual(len(self.test_widget), 2, 'There should be only two icons to display on the screen') + toolbar.set_widget_visible.assert_called_with(NON_TEXT_MENU, True) - def dummy_widget_visible(self, widget, visible=True): - self.test_widget = widget + def receive_spin_delay_test(self): + """ + Test that the spin box is updated accordingly after a call to receive_spin_delay() + """ + with patch('openlp.core.ui.slidecontroller.Settings') as MockedSettings: + # GIVEN: A new SlideController instance. + mocked_value = MagicMock(return_value=1) + MockedSettings.return_value = MagicMock(value=mocked_value) + mocked_delay_spin_box = MagicMock() + slide_controller = SlideController(None) + slide_controller.delay_spin_box = mocked_delay_spin_box + + # WHEN: The receive_spin_delay() method is called + slide_controller.receive_spin_delay() + + # THEN: The Settings()value() and delay_spin_box.setValue() methods should have been called correctly + mocked_value.assert_called_with('core/loop delay') + mocked_delay_spin_box.setValue.assert_called_with(1) + + def toggle_display_blank_test(self): + """ + Check that the toggle_display('blank') method calls the on_blank_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "blank" + slide_controller.toggle_display('blank') + + # THEN: Only on_blank_display() should have been called with an argument of True + mocked_on_blank_display.assert_called_once_with(True) + self.assertEqual(0, mocked_on_theme_display.call_count, 'on_theme_display should not have been called') + self.assertEqual(0, mocked_on_hide_display.call_count, 'on_hide_display should not have been called') + + def toggle_display_hide_test(self): + """ + Check that the toggle_display('hide') method calls the on_blank_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "hide" + slide_controller.toggle_display('hide') + + # THEN: Only on_blank_display() should have been called with an argument of True + mocked_on_blank_display.assert_called_once_with(True) + self.assertEqual(0, mocked_on_theme_display.call_count, 'on_theme_display should not have been called') + self.assertEqual(0, mocked_on_hide_display.call_count, 'on_hide_display should not have been called') + + def toggle_display_theme_test(self): + """ + Check that the toggle_display('theme') method calls the on_theme_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "theme" + slide_controller.toggle_display('theme') + + # THEN: Only on_theme_display() should have been called with an argument of True + mocked_on_theme_display.assert_called_once_with(True) + self.assertEqual(0, mocked_on_blank_display.call_count, 'on_blank_display should not have been called') + self.assertEqual(0, mocked_on_hide_display.call_count, 'on_hide_display should not have been called') + + def toggle_display_desktop_test(self): + """ + Check that the toggle_display('desktop') method calls the on_hide_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "desktop" + slide_controller.toggle_display('desktop') + + # THEN: Only on_hide_display() should have been called with an argument of True + mocked_on_hide_display.assert_called_once_with(True) + self.assertEqual(0, mocked_on_blank_display.call_count, 'on_blank_display should not have been called') + self.assertEqual(0, mocked_on_theme_display.call_count, 'on_theme_display should not have been called') + + def toggle_display_show_test(self): + """ + Check that the toggle_display('show') method calls all the on_X_display() methods + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "show" + slide_controller.toggle_display('show') + + # THEN: All the on_X_display() methods should have been called with an argument of False + mocked_on_blank_display.assert_called_once_with(False) + mocked_on_theme_display.assert_called_once_with(False) + mocked_on_hide_display.assert_called_once_with(False) + + def live_escape_test(self): + """ + Test that when the live_escape() method is called, the display is set to invisible and any media is stopped + """ + # GIVEN: A new SlideController instance and mocked out display and media_controller + mocked_display = MagicMock() + mocked_media_controller = MagicMock() + Registry.create() + Registry().register('media_controller', mocked_media_controller) + slide_controller = SlideController(None) + slide_controller.display = mocked_display + + # WHEN: live_escape() is called + slide_controller.live_escape() + + # THEN: the display should be set to invisible and the media controller stopped + mocked_display.setVisible.assert_called_once_with(False) + mocked_media_controller.media_stop.assert_called_once_with(slide_controller) + + def service_previous_test(self): + """ + Check that calling the service_previous() method adds the previous key to the queue and processes the queue + """ + # GIVEN: A new SlideController instance. + mocked_keypress_queue = MagicMock() + mocked_process_queue = MagicMock() + slide_controller = SlideController(None) + slide_controller.keypress_queue = mocked_keypress_queue + slide_controller._process_queue = mocked_process_queue + + # WHEN: The service_previous() method is called + slide_controller.service_previous() + + # THEN: The keypress is added to the queue and the queue is processed + mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Previous) + mocked_process_queue.assert_called_once_with() + + def service_next_test(self): + """ + Check that calling the service_next() method adds the next key to the queue and processes the queue + """ + # GIVEN: A new SlideController instance and mocked out methods + mocked_keypress_queue = MagicMock() + mocked_process_queue = MagicMock() + slide_controller = SlideController(None) + slide_controller.keypress_queue = mocked_keypress_queue + slide_controller._process_queue = mocked_process_queue + + # WHEN: The service_next() method is called + slide_controller.service_next() + + # THEN: The keypress is added to the queue and the queue is processed + mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Next) + mocked_process_queue.assert_called_once_with() + + def update_slide_limits_test(self): + """ + Test that calling the update_slide_limits() method updates the slide limits + """ + # GIVEN: A mocked out Settings object, a new SlideController and a mocked out main_window + with patch('openlp.core.ui.slidecontroller.Settings') as MockedSettings: + mocked_value = MagicMock(return_value=10) + MockedSettings.return_value = MagicMock(value=mocked_value) + mocked_main_window = MagicMock(advanced_settings_section='advanced') + Registry.create() + Registry().register('main_window', mocked_main_window) + slide_controller = SlideController(None) + + # WHEN: update_slide_limits() is called + slide_controller.update_slide_limits() + + # THEN: The value of slide_limits should be 10 + mocked_value.assert_called_once_with('advanced/slide limits') + self.assertEqual(10, slide_controller.slide_limits, 'Slide limits should have been updated to 10') + + def enable_tool_bar_live_test(self): + """ + Check that when enable_tool_bar on a live slide controller is called, enable_live_tool_bar is called + """ + # GIVEN: Mocked out enable methods and a real slide controller which is set to live + mocked_enable_live_tool_bar = MagicMock() + mocked_enable_preview_tool_bar = MagicMock() + slide_controller = SlideController(None) + slide_controller.is_live = True + slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar + slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar + mocked_service_item = MagicMock() + + # WHEN: enable_tool_bar() is called + slide_controller.enable_tool_bar(mocked_service_item) + + # THEN: The enable_live_tool_bar() method is called, not enable_preview_tool_bar() + mocked_enable_live_tool_bar.assert_called_once_with(mocked_service_item) + self.assertEqual(0, mocked_enable_preview_tool_bar.call_count, 'The preview method should not have been called') + + def enable_tool_bar_preview_test(self): + """ + Check that when enable_tool_bar on a preview slide controller is called, enable_preview_tool_bar is called + """ + # GIVEN: Mocked out enable methods and a real slide controller which is set to live + mocked_enable_live_tool_bar = MagicMock() + mocked_enable_preview_tool_bar = MagicMock() + slide_controller = SlideController(None) + slide_controller.is_live = False + slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar + slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar + mocked_service_item = MagicMock() + + # WHEN: enable_tool_bar() is called + slide_controller.enable_tool_bar(mocked_service_item) + + # THEN: The enable_preview_tool_bar() method is called, not enable_live_tool_bar() + mocked_enable_preview_tool_bar.assert_called_once_with(mocked_service_item) + self.assertEqual(0, mocked_enable_live_tool_bar.call_count, 'The live method should not have been called') + + def refresh_service_item_text_test(self): + """ + Test that the refresh_service_item() method refreshes a text service item + """ + # GIVEN: A mock service item and a fresh slide controller + mocked_service_item = MagicMock() + mocked_service_item.is_text.return_value = True + mocked_service_item.is_image.return_value = False + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller._process_item = mocked_process_item + slide_controller.selected_row = 5 + + # WHEN: The refresh_service_item method() is called + slide_controller.refresh_service_item() + + # THEN: The item should be re-processed + mocked_service_item.is_text.assert_called_once_with() + self.assertEqual(0, mocked_service_item.is_image.call_count, 'is_image should not have been called') + mocked_service_item.render.assert_called_once_with() + mocked_process_item.assert_called_once_with(mocked_service_item, 5) + + def refresh_service_item_image_test(self): + """ + Test that the refresh_service_item() method refreshes a image service item + """ + # GIVEN: A mock service item and a fresh slide controller + mocked_service_item = MagicMock() + mocked_service_item.is_text.return_value = False + mocked_service_item.is_image.return_value = True + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller._process_item = mocked_process_item + slide_controller.selected_row = 5 + + # WHEN: The refresh_service_item method() is called + slide_controller.refresh_service_item() + + # THEN: The item should be re-processed + mocked_service_item.is_text.assert_called_once_with() + mocked_service_item.is_image.assert_called_once_with() + mocked_service_item.render.assert_called_once_with() + mocked_process_item.assert_called_once_with(mocked_service_item, 5) + + def refresh_service_item_not_image_or_text_test(self): + """ + Test that the refresh_service_item() method does not refresh a service item if it's neither text or an image + """ + # GIVEN: A mock service item and a fresh slide controller + mocked_service_item = MagicMock() + mocked_service_item.is_text.return_value = False + mocked_service_item.is_image.return_value = False + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller._process_item = mocked_process_item + slide_controller.selected_row = 5 + + # WHEN: The refresh_service_item method() is called + slide_controller.refresh_service_item() + + # THEN: The item should be re-processed + mocked_service_item.is_text.assert_called_once_with() + mocked_service_item.is_image.assert_called_once_with() + self.assertEqual(0, mocked_service_item.render.call_count, 'The render() method should not have been called') + self.assertEqual(0, mocked_process_item.call_count, + 'The mocked_process_item() method should not have been called') + + def add_service_item_with_song_edit_test(self): + """ + Test the add_service_item() method when song_edit is True + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_process_item =MagicMock() + slide_controller = SlideController(None) + slide_controller._process_item = mocked_process_item + slide_controller.song_edit = True + slide_controller.selected_row = 2 + + # WHEN: The item is added to the service + slide_controller.add_service_item(mocked_item) + + # THEN: The item is processed, the slide number is correct, and the song is not editable (or something) + mocked_item.render.assert_called_once_with() + self.assertFalse(slide_controller.song_edit, 'song_edit should be False') + mocked_process_item.assert_called_once_with(mocked_item, 2) + + def add_service_item_without_song_edit_test(self): + """ + Test the add_service_item() method when song_edit is False + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_process_item =MagicMock() + slide_controller = SlideController(None) + slide_controller._process_item = mocked_process_item + slide_controller.song_edit = False + slide_controller.selected_row = 2 + + # WHEN: The item is added to the service + slide_controller.add_service_item(mocked_item) + + # THEN: The item is processed, the slide number is correct, and the song is not editable (or something) + mocked_item.render.assert_called_once_with() + self.assertFalse(slide_controller.song_edit, 'song_edit should be False') + mocked_process_item.assert_called_once_with(mocked_item, 0) + + def replace_service_manager_item_different_items_test(self): + """ + Test that when the service items are not the same, nothing happens + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_preview_widget = MagicMock() + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.preview_widget = mocked_preview_widget + slide_controller._process_item = mocked_process_item + slide_controller.service_item = None + + # WHEN: The service item is replaced + slide_controller.replace_service_manager_item(mocked_item) + + # THEN: The service item should not be processed + self.assertEqual(0, mocked_process_item.call_count, 'The _process_item() method should not have been called') + self.assertEqual(0, mocked_preview_widget.current_slide_number.call_count, + 'The preview_widgetcurrent_slide_number.() method should not have been called') + + def replace_service_manager_item_same_item_test(self): + """ + Test that when the service item is the same, the service item is reprocessed + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_preview_widget = MagicMock() + mocked_preview_widget.current_slide_number.return_value = 7 + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.preview_widget = mocked_preview_widget + slide_controller._process_item = mocked_process_item + slide_controller.service_item = mocked_item + + # WHEN: The service item is replaced + slide_controller.replace_service_manager_item(mocked_item) + + # THEN: The service item should not be processed + mocked_preview_widget.current_slide_number.assert_called_with() + mocked_process_item.assert_called_once_with(mocked_item, 7) + + def on_slide_selected_index_no_service_item_test(self): + """ + Test that when there is no service item, the on_slide_selected_index() method returns immediately + """ + # GIVEN: A mocked service item and a slide controller without a service item + mocked_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = None + + # WHEN: The method is called + slide_controller.on_slide_selected_index([10]) + + # THEN: It should have exited early + self.assertEqual(0, mocked_item.is_command.call_count, 'The service item should have not been called') + + def on_slide_selected_index_service_item_command_test(self): + """ + Test that when there is a command service item, the command is executed + """ + # GIVEN: A mocked service item and a slide controller with a service item + mocked_item = MagicMock() + mocked_item.is_command.return_value = True + mocked_item.name = 'Mocked Item' + mocked_execute = MagicMock() + mocked_update_preview = MagicMock() + mocked_preview_widget = MagicMock() + mocked_slide_selected = MagicMock() + Registry.execute = mocked_execute + Registry.create() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_item + slide_controller.update_preview = mocked_update_preview + slide_controller.preview_widget = mocked_preview_widget + slide_controller.slide_selected = mocked_slide_selected + slide_controller.is_live = True + + # WHEN: The method is called + slide_controller.on_slide_selected_index([9]) + + # THEN: It should have sent a notification + mocked_item.is_command.assert_called_once_with() + mocked_execute.assert_called_once_with('mocked item_slide', [mocked_item, True, 9]) + mocked_update_preview.assert_called_once_with() + self.assertEqual(0, mocked_preview_widget.change_slide.call_count, 'Change slide should not have been called') + self.assertEqual(0, mocked_slide_selected.call_count, 'slide_selected should not have been called') + + def on_slide_selected_index_service_item_not_command_test(self): + """ + Test that when there is a service item but it's not a command, the preview widget is updated + """ + # GIVEN: A mocked service item and a slide controller with a service item + mocked_item = MagicMock() + mocked_item.is_command.return_value = False + mocked_item.name = 'Mocked Item' + mocked_execute = MagicMock() + mocked_update_preview = MagicMock() + mocked_preview_widget = MagicMock() + mocked_slide_selected = MagicMock() + Registry.execute = mocked_execute + Registry.create() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_item + slide_controller.update_preview = mocked_update_preview + slide_controller.preview_widget = mocked_preview_widget + slide_controller.slide_selected = mocked_slide_selected + + # WHEN: The method is called + slide_controller.on_slide_selected_index([7]) + + # THEN: It should have sent a notification + mocked_item.is_command.assert_called_once_with() + self.assertEqual(0, mocked_execute.call_count, 'Execute should not have been called') + self.assertEqual(0, mocked_update_preview.call_count, 'Update preview should not have been called') + mocked_preview_widget.change_slide.assert_called_once_with(7) + mocked_slide_selected.assert_called_once_with() From ba9ff89354eea616ea6e82c4744fc7ee8d25c9c0 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 13 Jul 2014 13:04:37 +0200 Subject: [PATCH 18/20] [fix] PEP8 error: no whitespace --- tests/functional/openlp_core_ui/test_slidecontroller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index 3a1e754f8..ed237d424 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -405,7 +405,7 @@ class TestSlideController(TestCase): """ # GIVEN: A slide controller and a new item to add mocked_item = MagicMock() - mocked_process_item =MagicMock() + mocked_process_item = MagicMock() slide_controller = SlideController(None) slide_controller._process_item = mocked_process_item slide_controller.song_edit = True @@ -425,7 +425,7 @@ class TestSlideController(TestCase): """ # GIVEN: A slide controller and a new item to add mocked_item = MagicMock() - mocked_process_item =MagicMock() + mocked_process_item = MagicMock() slide_controller = SlideController(None) slide_controller._process_item = mocked_process_item slide_controller.song_edit = False From 63350ab6eb9dc91743441b106fa58f2278d03e74 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 13 Jul 2014 15:10:58 +0200 Subject: [PATCH 19/20] Try to ignore the 2.0.x line of tags --- tests/utils/test_bzr_tags.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/utils/test_bzr_tags.py b/tests/utils/test_bzr_tags.py index acadbd8c4..bc5f4ade0 100644 --- a/tests/utils/test_bzr_tags.py +++ b/tests/utils/test_bzr_tags.py @@ -30,7 +30,7 @@ Package to test for proper bzr tags. """ import os - +import re from unittest import TestCase from subprocess import Popen, PIPE @@ -52,6 +52,7 @@ TAGS = [ ['2.0', '2118'], ['2.1.0', '2119'] ] +TAG_SEARCH = re.compile('2\.0\.\d') class TestBzrTags(TestCase): @@ -65,8 +66,9 @@ class TestBzrTags(TestCase): # WHEN getting the branches tags bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE) - stdout = bzr.communicate()[0] - tags = [line.decode('utf-8').split() for line in stdout.splitlines()] + std_out = bzr.communicate()[0] + tags = [line.decode('utf-8').split() for line in std_out.splitlines()] + tags = [t_r for t_r in tags if t_r[1] != '?' or not (t_r[1] == '?' and TAG_SEARCH.search(t_r[0]))] # THEN the tags should match the accepted tags self.assertEqual(TAGS, tags, 'List of tags should match') From 563fb794b5dab9907375cd450e68bb083fc83f33 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 13 Jul 2014 21:21:50 +0200 Subject: [PATCH 20/20] Added an explanatory comment --- tests/utils/test_bzr_tags.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/utils/test_bzr_tags.py b/tests/utils/test_bzr_tags.py index bc5f4ade0..14da67bc7 100644 --- a/tests/utils/test_bzr_tags.py +++ b/tests/utils/test_bzr_tags.py @@ -52,6 +52,9 @@ TAGS = [ ['2.0', '2118'], ['2.1.0', '2119'] ] +# Depending on the repository, we sometimes have the 2.0.x tags in the repo too. They come up with a revision number of +# "?", which I suspect is due to the fact that we're using shared repositories. This regular expression matches all +# 2.0.x tags. TAG_SEARCH = re.compile('2\.0\.\d')