diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index 965adb053..a1e37abcf 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -295,7 +295,7 @@ def set_case_insensitive_completer(cache, widget): Sets a case insensitive text completer for a widget. :param cache: The list of items to use as suggestions. - :param widget: A widget to set the completer (QComboBox or QTextEdit instance) + :param widget: A widget to set the completer (QComboBox or QLineEdit instance) """ completer = QtGui.QCompleter(cache) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index ae47a110f..2125922fe 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -42,7 +42,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStri from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box from openlp.plugins.songs.lib import VerseType, clean_song -from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorSong, AuthorType, Topic, MediaFile +from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.xml import SongXML from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog @@ -966,10 +966,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.song.authors_songs = [] for row in range(self.authors_list_view.count()): item = self.authors_list_view.item(row) - author_song = AuthorSong() - author_song.author_id = item.data(QtCore.Qt.UserRole)[0] - author_song.author_type = item.data(QtCore.Qt.UserRole)[1] - self.song.authors_songs.append(author_song) + self.song.add_author(self.manager.get_object(Author, item.data(QtCore.Qt.UserRole)[0]), + item.data(QtCore.Qt.UserRole)[1]) self.song.topics = [] for row in range(self.topics_list_view.count()): item = self.topics_list_view.item(row) diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 4e9bdad93..bdbae41fb 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -400,7 +400,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog, RegistryPrope """ Merges two authors into one author. - :param old_author: The object, which was edited, that will be deleted + :param old_author: The object, which was edited, that will be deleted """ # Find the duplicate. existing_author = self.manager.get_object_filtered( @@ -415,11 +415,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog, RegistryPrope # Find the songs, which have the old_author as author. songs = self.manager.get_all_objects(Song, Song.authors.contains(old_author)) for song in songs: - # We check if the song has already existing_author as author. If - # that is not the case we add it. - if existing_author not in song.authors: - song.authors.append(existing_author) - song.authors.remove(old_author) + for author_song in song.authors_songs: + song.add_author(existing_author, author_song.author_type) + song.remove_author(old_author, author_song.author_type) self.manager.save_object(song) self.manager.delete_object(Author, old_author.id) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index ec14175ac..7dcb98de5 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -390,12 +390,12 @@ def clean_song(manager, song): verses = SongXML().get_verses(song.lyrics) song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses]) # The song does not have any author, add one. - if not song.authors and not song.authors_songs: # Need to check both relations + if not song.authors_songs: name = SongStrings.AuthorUnknown author = manager.get_object_filtered(Author, Author.display_name == name) if author is None: author = Author.populate(display_name=name, last_name='', first_name='') - song.authors.append(author) + song.add_author(author) if song.copyright: song.copyright = CONTROL_CHARS.sub('', song.copyright).strip() diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index 91649c951..93c27d32e 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -114,6 +114,33 @@ class Song(BaseModel): """ self.sort_key = get_natural_key(self.title) + def add_author(self, author, author_type=None): + """ + Add an author to the song if it not yet exists + + :param author: Author object + :param author_type: AuthorType constant or None + """ + for author_song in self.authors_songs: + if author_song.author == author and author_song.author_type == author_type: + return + new_author_song = AuthorSong() + new_author_song.author = author + new_author_song.author_type = author_type + self.authors_songs.append(new_author_song) + + def remove_author(self, author, author_type=None): + """ + Remove an existing author from the song + + :param author: Author object + :param author_type: AuthorType constant or None + """ + for author_song in self.authors_songs: + if author_song.author == author and author_song.author_type == author_type: + self.authors_songs.remove(author_song) + return + class Topic(BaseModel): """ @@ -283,9 +310,10 @@ def init_schema(url): mapper(Book, song_books_table) mapper(MediaFile, media_files_table) mapper(Song, songs_table, properties={ - # Use the authors_songs relation when you need access to the 'author_type' attribute. + # Use the authors_songs relation when you need access to the 'author_type' attribute + # or when creating new relations 'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"), - 'authors': relation(Author, secondary=authors_songs_table), + 'authors': relation(Author, secondary=authors_songs_table, viewonly=True), 'book': relation(Book, backref='songs'), 'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight), 'topics': relation(Topic, backref='songs', secondary=songs_topics_table) diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index 6f8bd6978..2b31718c2 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -343,7 +343,7 @@ class FoilPresenter(object): author = Author.populate(display_name=display_name, last_name=display_name.split(' ')[-1], first_name=' '.join(display_name.split(' ')[:-1])) self.manager.save_object(author) - song.authors.append(author) + song.add_author(author) def _process_cclinumber(self, foilpresenterfolie, song): """ diff --git a/openlp/plugins/songs/lib/olpimport.py b/openlp/plugins/songs/lib/olpimport.py index 335ba606a..f4b066ef0 100644 --- a/openlp/plugins/songs/lib/olpimport.py +++ b/openlp/plugins/songs/lib/olpimport.py @@ -187,7 +187,7 @@ class OpenLPSongImport(SongImport): first_name=author.first_name, last_name=author.last_name, display_name=author.display_name) - new_song.authors.append(existing_author) + new_song.add_author(existing_author) if song.book: existing_song_book = self.manager.get_object_filtered(Book, Book.name == song.book.name) if existing_song_book is None: diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index a5fbb99e0..754288546 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -325,7 +325,7 @@ class SongImport(QtCore.QObject): author = Author.populate(display_name=author_text, last_name=author_text.split(' ')[-1], first_name=' '.join(author_text.split(' ')[:-1])) - song.authors.append(author) + song.add_author(author) if self.song_book_name: song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name) if song_book is None: diff --git a/openlp/plugins/songs/lib/songselect.py b/openlp/plugins/songs/lib/songselect.py index 232cdb9cf..6fd084a47 100644 --- a/openlp/plugins/songs/lib/songselect.py +++ b/openlp/plugins/songs/lib/songselect.py @@ -196,13 +196,13 @@ class SongSelectImport(object): db_song.lyrics = song_xml.extract_xml() clean_song(self.db_manager, db_song) self.db_manager.save_object(db_song) - db_song.authors = [] + db_song.authors_songs = [] for author_name in song['authors']: author = self.db_manager.get_object_filtered(Author, Author.display_name == author_name) if not author: author = Author.populate(first_name=author_name.rsplit(' ', 1)[0], last_name=author_name.rsplit(' ', 1)[1], display_name=author_name) - db_song.authors.append(author) + db_song.add_author(author) self.db_manager.save_object(db_song) return db_song diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 87e5da21e..5481ea1c4 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -71,7 +71,7 @@ from lxml import etree, objectify from openlp.core.common import translate from openlp.core.lib import FormattingTags from openlp.plugins.songs.lib import VerseType, clean_song -from openlp.plugins.songs.lib.db import Author, AuthorSong, AuthorType, Book, Song, Topic +from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song, Topic from openlp.core.utils import get_application_version log = logging.getLogger(__name__) @@ -519,10 +519,7 @@ class OpenLyrics(object): author = Author.populate(display_name=display_name, last_name=display_name.split(' ')[-1], first_name=' '.join(display_name.split(' ')[:-1])) - author_song = AuthorSong() - author_song.author = author - author_song.author_type = author_type - song.authors_songs.append(author_song) + song.add_author(author, author_type) def _process_cclinumber(self, properties, song): """ diff --git a/tests/functional/openlp_plugins/songs/test_db.py b/tests/functional/openlp_plugins/songs/test_db.py new file mode 100644 index 000000000..3080db77e --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_db.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 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 # +############################################################################### +""" +This module contains tests for the db submodule of the Songs plugin. +""" +from unittest import TestCase + +from openlp.plugins.songs.lib.db import Song, Author, AuthorType + + +class TestDB(TestCase): + """ + Test the functions in the :mod:`db` module. + """ + + def test_add_author(self): + """ + Test adding an author to a song + """ + # GIVEN: A song and an author + song = Song() + song.authors_songs = [] + author = Author() + author.first_name = "Max" + author.last_name = "Mustermann" + + # WHEN: We add an author to the song + song.add_author(author) + + # THEN: The author should have been added with author_type=None + self.assertEqual(1, len(song.authors_songs)) + self.assertEqual("Max", song.authors_songs[0].author.first_name) + self.assertEqual("Mustermann", song.authors_songs[0].author.last_name) + self.assertIsNone(song.authors_songs[0].author_type) + + def test_add_author_with_type(self): + """ + Test adding an author with a type specified to a song + """ + # GIVEN: A song and an author + song = Song() + song.authors_songs = [] + author = Author() + author.first_name = "Max" + author.last_name = "Mustermann" + + # WHEN: We add an author to the song + song.add_author(author, AuthorType.Words) + + # THEN: The author should have been added with author_type=None + self.assertEqual(1, len(song.authors_songs)) + self.assertEqual("Max", song.authors_songs[0].author.first_name) + self.assertEqual("Mustermann", song.authors_songs[0].author.last_name) + self.assertEqual(AuthorType.Words, song.authors_songs[0].author_type) + + def test_remove_author(self): + """ + Test removing an author from a song + """ + # GIVEN: A song with an author + song = Song() + song.authors_songs = [] + author = Author() + song.add_author(author) + + # WHEN: We remove the author + song.remove_author(author) + + # THEN: It should have been removed + self.assertEqual(0, len(song.authors_songs)) + + def test_remove_author_with_type(self): + """ + Test removing an author with a type specified from a song + """ + # GIVEN: A song with two authors + song = Song() + song.authors_songs = [] + author = Author() + song.add_author(author) + song.add_author(author, AuthorType.Translation) + + # WHEN: We remove the author with a certain type + song.remove_author(author, AuthorType.Translation) + + # 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) diff --git a/tests/functional/openlp_plugins/songs/test_songselect.py b/tests/functional/openlp_plugins/songs/test_songselect.py index 8d1237190..0f9001342 100644 --- a/tests/functional/openlp_plugins/songs/test_songselect.py +++ b/tests/functional/openlp_plugins/songs/test_songselect.py @@ -344,7 +344,7 @@ class TestSongSelect(TestCase): mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False) MockedAuthor.populate.assert_called_with(first_name='Public', last_name='Domain', display_name='Public Domain') - self.assertEqual(1, len(result.authors), 'There should only be one author') + self.assertEqual(1, len(result.authors_songs), 'There should only be one author') def save_song_existing_author_test(self): """ @@ -379,4 +379,4 @@ class TestSongSelect(TestCase): 'The save_object() method should have been called twice') mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False) self.assertEqual(0, MockedAuthor.populate.call_count, 'A new author should not have been instantiated') - self.assertEqual(1, len(result.authors), 'There should only be one author') + self.assertEqual(1, len(result.authors_songs), 'There should only be one author')