This commit is contained in:
Tim Bentley 2016-01-09 20:07:40 +00:00
commit 51be6b1848
12 changed files with 436 additions and 131 deletions

71
openlp/core/utils/db.py Normal file
View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`db` module provides helper functions for database related methods.
"""
import sqlalchemy
import logging
from copy import deepcopy
log = logging.getLogger(__name__)
def drop_column(op, tablename, columnname):
drop_columns(op, tablename, [columnname])
def drop_columns(op, tablename, columns):
"""
Column dropping functionality for SQLite, as there is no DROP COLUMN support in SQLite
From https://github.com/klugjohannes/alembic-sqlite
"""
# get the db engine and reflect database tables
engine = op.get_bind()
meta = sqlalchemy.MetaData(bind=engine)
meta.reflect()
# create a select statement from the old table
old_table = meta.tables[tablename]
select = sqlalchemy.sql.select([c for c in old_table.c if c.name not in columns])
# get remaining columns without table attribute attached
remaining_columns = [deepcopy(c) for c in old_table.columns if c.name not in columns]
for column in remaining_columns:
column.table = None
# create a temporary new table
new_tablename = '{0}_new'.format(tablename)
op.create_table(new_tablename, *remaining_columns)
meta.reflect()
new_table = meta.tables[new_tablename]
# copy data from old table
insert = sqlalchemy.sql.insert(new_table).from_select([c.name for c in remaining_columns], select)
engine.execute(insert)
# drop the old table and rename the new table to take the old tables
# position
op.drop_table(tablename)
op.rename_table(new_tablename, tablename)

View File

@ -37,7 +37,7 @@ class Ui_EditSongDialog(object):
def setupUi(self, edit_song_dialog): def setupUi(self, edit_song_dialog):
edit_song_dialog.setObjectName('edit_song_dialog') edit_song_dialog.setObjectName('edit_song_dialog')
edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
edit_song_dialog.resize(650, 400) edit_song_dialog.resize(900, 600)
edit_song_dialog.setModal(True) edit_song_dialog.setModal(True)
self.dialog_layout = QtWidgets.QVBoxLayout(edit_song_dialog) self.dialog_layout = QtWidgets.QVBoxLayout(edit_song_dialog)
self.dialog_layout.setSpacing(8) self.dialog_layout.setSpacing(8)
@ -173,22 +173,33 @@ class Ui_EditSongDialog(object):
self.topic_remove_layout.addWidget(self.topic_remove_button) self.topic_remove_layout.addWidget(self.topic_remove_button)
self.topics_layout.addLayout(self.topic_remove_layout) self.topics_layout.addLayout(self.topic_remove_layout)
self.authors_right_layout.addWidget(self.topics_group_box) self.authors_right_layout.addWidget(self.topics_group_box)
self.song_book_group_box = QtWidgets.QGroupBox(self.authors_tab) self.songbook_group_box = QtWidgets.QGroupBox(self.authors_tab)
self.song_book_group_box.setObjectName('song_book_group_box') self.songbook_group_box.setObjectName('songbook_group_box')
self.song_book_layout = QtWidgets.QFormLayout(self.song_book_group_box) self.songbooks_layout = QtWidgets.QVBoxLayout(self.songbook_group_box)
self.song_book_layout.setObjectName('song_book_layout') self.songbooks_layout.setObjectName('songbooks_layout')
self.song_book_name_label = QtWidgets.QLabel(self.song_book_group_box) self.songbook_add_layout = QtWidgets.QHBoxLayout()
self.song_book_name_label.setObjectName('song_book_name_label') self.songbook_add_layout.setObjectName('songbook_add_layout')
self.song_book_combo_box = create_combo_box(self.song_book_group_box, 'song_book_combo_box') self.songbooks_combo_box = create_combo_box(self.songbook_group_box, 'songbooks_combo_box')
self.song_book_name_label.setBuddy(self.song_book_combo_box) self.songbook_add_layout.addWidget(self.songbooks_combo_box)
self.song_book_layout.addRow(self.song_book_name_label, self.song_book_combo_box) self.songbook_entry_edit = QtWidgets.QLineEdit(self.songbook_group_box)
self.song_book_number_label = QtWidgets.QLabel(self.song_book_group_box) self.songbook_entry_edit.setMaximumWidth(100)
self.song_book_number_label.setObjectName('song_book_number_label') self.songbook_add_layout.addWidget(self.songbook_entry_edit)
self.song_book_number_edit = QtWidgets.QLineEdit(self.song_book_group_box) self.songbook_add_button = QtWidgets.QPushButton(self.songbook_group_box)
self.song_book_number_edit.setObjectName('song_book_number_edit') self.songbook_add_button.setObjectName('songbook_add_button')
self.song_book_number_label.setBuddy(self.song_book_number_edit) self.songbook_add_layout.addWidget(self.songbook_add_button)
self.song_book_layout.addRow(self.song_book_number_label, self.song_book_number_edit) self.songbooks_layout.addLayout(self.songbook_add_layout)
self.authors_right_layout.addWidget(self.song_book_group_box) self.songbooks_list_view = QtWidgets.QListWidget(self.songbook_group_box)
self.songbooks_list_view.setAlternatingRowColors(True)
self.songbooks_list_view.setObjectName('songbooks_list_view')
self.songbooks_layout.addWidget(self.songbooks_list_view)
self.songbook_remove_layout = QtWidgets.QHBoxLayout()
self.songbook_remove_layout.setObjectName('songbook_remove_layout')
self.songbook_remove_layout.addStretch()
self.songbook_remove_button = QtWidgets.QPushButton(self.songbook_group_box)
self.songbook_remove_button.setObjectName('songbook_remove_button')
self.songbook_remove_layout.addWidget(self.songbook_remove_button)
self.songbooks_layout.addLayout(self.songbook_remove_layout)
self.authors_right_layout.addWidget(self.songbook_group_box)
self.authors_tab_layout.addLayout(self.authors_right_layout) self.authors_tab_layout.addLayout(self.authors_right_layout)
self.song_tab_widget.addTab(self.authors_tab, '') self.song_tab_widget.addTab(self.authors_tab, '')
# theme tab # theme tab
@ -303,15 +314,15 @@ class Ui_EditSongDialog(object):
self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song')) self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song'))
self.author_edit_button.setText(translate('SongsPlugin.EditSongForm', '&Edit Author Type')) self.author_edit_button.setText(translate('SongsPlugin.EditSongForm', '&Edit Author Type'))
self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))
self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Song Books')) self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Songbooks'))
self.topics_group_box.setTitle(SongStrings.Topic) self.topics_group_box.setTitle(SongStrings.Topics)
self.topic_add_button.setText(translate('SongsPlugin.EditSongForm', 'A&dd to Song')) self.topic_add_button.setText(translate('SongsPlugin.EditSongForm', 'A&dd to Song'))
self.topic_remove_button.setText(translate('SongsPlugin.EditSongForm', 'R&emove')) self.topic_remove_button.setText(translate('SongsPlugin.EditSongForm', 'R&emove'))
self.song_book_group_box.setTitle(SongStrings.SongBook) self.songbook_group_box.setTitle(SongStrings.SongBooks)
self.song_book_name_label.setText(translate('SongsPlugin.EditSongForm', 'Book:')) self.songbook_add_button.setText(translate('SongsPlugin.EditSongForm', 'Add &to Song'))
self.song_book_number_label.setText(translate('SongsPlugin.EditSongForm', 'Number:')) self.songbook_remove_button.setText(translate('SongsPlugin.EditSongForm', 'Re&move'))
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.authors_tab), self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.authors_tab),
translate('SongsPlugin.EditSongForm', 'Authors, Topics && Song Book')) translate('SongsPlugin.EditSongForm', 'Authors, Topics && Songbooks'))
self.theme_group_box.setTitle(UiStrings().Theme) self.theme_group_box.setTitle(UiStrings().Theme)
self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme')) self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme'))
self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information')) self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information'))

View File

@ -35,7 +35,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 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.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 import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import SongXML from openlp.plugins.songs.lib.openlyricsxml import SongXML
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
@ -70,6 +70,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.topic_add_button.clicked.connect(self.on_topic_add_button_clicked) 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.topic_remove_button.clicked.connect(self.on_topic_remove_button_clicked)
self.topics_list_view.itemClicked.connect(self.on_topic_list_view_clicked) self.topics_list_view.itemClicked.connect(self.on_topic_list_view_clicked)
self.songbook_add_button.clicked.connect(self.on_songbook_add_button_clicked)
self.songbook_remove_button.clicked.connect(self.on_songbook_remove_button_clicked)
self.songbooks_list_view.itemClicked.connect(self.on_songbook_list_view_clicked)
self.copyright_insert_button.clicked.connect(self.on_copyright_insert_button_triggered) 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_add_button.clicked.connect(self.on_verse_add_button_clicked)
self.verse_list_widget.doubleClicked.connect(self.on_verse_edit_button_clicked) self.verse_list_widget.doubleClicked.connect(self.on_verse_edit_button_clicked)
@ -126,6 +129,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
author_item.setData(QtCore.Qt.UserRole, (author.id, author_type)) author_item.setData(QtCore.Qt.UserRole, (author.id, author_type))
self.authors_list_view.addItem(author_item) self.authors_list_view.addItem(author_item)
def add_songbook_entry_to_list(self, songbook_id, songbook_name, entry):
songbook_entry_item = QtWidgets.QListWidgetItem(SongBookEntry.get_display_name(songbook_name, entry))
songbook_entry_item.setData(QtCore.Qt.UserRole, (songbook_id, entry))
self.songbooks_list_view.addItem(songbook_entry_item)
def _extract_verse_order(self, verse_order): def _extract_verse_order(self, verse_order):
""" """
Split out the verse order Split out the verse order
@ -220,17 +228,6 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
result = self._validate_verse_list(self.verse_order_edit.text(), self.verse_list_widget.rowCount()) result = self._validate_verse_list(self.verse_order_edit.text(), self.verse_list_widget.rowCount())
if not result: if not result:
return False return False
text = self.song_book_combo_box.currentText()
if self.song_book_combo_box.findText(text, QtCore.Qt.MatchExactly) < 0:
if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Book'),
translate('SongsPlugin.EditSongForm', 'This song book does not exist, do you want to add it?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
book = Book.populate(name=text, publisher='')
self.manager.save_object(book)
else:
return False
# Validate tags (lp#1199639) # Validate tags (lp#1199639)
misplaced_tags = [] misplaced_tags = []
verse_tags = [] verse_tags = []
@ -328,6 +325,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
if self.topics_combo_box.hasFocus() and self.topics_combo_box.currentText(): if self.topics_combo_box.hasFocus() and self.topics_combo_box.currentText():
self.on_topic_add_button_clicked() self.on_topic_add_button_clicked()
return return
if self.songbooks_combo_box.hasFocus() and self.songbooks_combo_box.currentText():
self.on_songbook_add_button_clicked()
return
QtWidgets.QDialog.keyPressEvent(self, event) QtWidgets.QDialog.keyPressEvent(self, event)
def initialise(self): def initialise(self):
@ -368,12 +368,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.topics = [] self.topics = []
self._load_objects(Topic, self.topics_combo_box, self.topics) self._load_objects(Topic, self.topics_combo_box, self.topics)
def load_books(self): def load_songbooks(self):
""" """
Load the song books into the combobox Load the Songbooks into the combobox
""" """
self.books = [] self.songbooks = []
self._load_objects(Book, self.song_book_combo_box, self.books) self._load_objects(Book, self.songbooks_combo_box, self.songbooks)
def load_themes(self, theme_list): def load_themes(self, theme_list):
""" """
@ -414,12 +414,13 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.verse_list_widget.setRowCount(0) self.verse_list_widget.setRowCount(0)
self.authors_list_view.clear() self.authors_list_view.clear()
self.topics_list_view.clear() self.topics_list_view.clear()
self.songbooks_list_view.clear()
self.songbook_entry_edit.clear()
self.audio_list_widget.clear() self.audio_list_widget.clear()
self.title_edit.setFocus() self.title_edit.setFocus()
self.song_book_number_edit.clear()
self.load_authors() self.load_authors()
self.load_topics() self.load_topics()
self.load_books() self.load_songbooks()
self.load_media_files() self.load_media_files()
self.theme_combo_box.setEditText('') self.theme_combo_box.setEditText('')
self.theme_combo_box.setCurrentIndex(0) self.theme_combo_box.setCurrentIndex(0)
@ -438,18 +439,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.song_tab_widget.setCurrentIndex(0) self.song_tab_widget.setCurrentIndex(0)
self.load_authors() self.load_authors()
self.load_topics() self.load_topics()
self.load_books() self.load_songbooks()
self.load_media_files() self.load_media_files()
self.song = self.manager.get_object(Song, song_id) self.song = self.manager.get_object(Song, song_id)
self.title_edit.setText(self.song.title) self.title_edit.setText(self.song.title)
self.alternative_edit.setText( self.alternative_edit.setText(self.song.alternate_title if self.song.alternate_title else '')
self.song.alternate_title if self.song.alternate_title else '')
if self.song.song_book_id != 0:
book_name = self.manager.get_object(Book, self.song.song_book_id)
find_and_set_in_combo_box(self.song_book_combo_box, str(book_name.name))
else:
self.song_book_combo_box.setEditText('')
self.song_book_combo_box.setCurrentIndex(0)
if self.song.theme_name: if self.song.theme_name:
find_and_set_in_combo_box(self.theme_combo_box, str(self.song.theme_name)) find_and_set_in_combo_box(self.theme_combo_box, str(self.song.theme_name))
else: else:
@ -459,7 +453,6 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.copyright_edit.setText(self.song.copyright if self.song.copyright else '') self.copyright_edit.setText(self.song.copyright if self.song.copyright else '')
self.comments_edit.setPlainText(self.song.comments if self.song.comments else '') self.comments_edit.setPlainText(self.song.comments if self.song.comments else '')
self.ccli_number_edit.setText(self.song.ccli_number if self.song.ccli_number else '') self.ccli_number_edit.setText(self.song.ccli_number if self.song.ccli_number else '')
self.song_book_number_edit.setText(self.song.song_number if self.song.song_number else '')
# lazy xml migration for now # lazy xml migration for now
self.verse_list_widget.clear() self.verse_list_widget.clear()
self.verse_list_widget.setRowCount(0) self.verse_list_widget.setRowCount(0)
@ -521,6 +514,10 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
topic_name = QtWidgets.QListWidgetItem(str(topic.name)) topic_name = QtWidgets.QListWidgetItem(str(topic.name))
topic_name.setData(QtCore.Qt.UserRole, topic.id) topic_name.setData(QtCore.Qt.UserRole, topic.id)
self.topics_list_view.addItem(topic_name) self.topics_list_view.addItem(topic_name)
self.songbooks_list_view.clear()
for songbook_entry in self.song.songbook_entries:
self.add_songbook_entry_to_list(songbook_entry.songbook.id, songbook_entry.songbook.name,
songbook_entry.entry)
self.audio_list_widget.clear() self.audio_list_widget.clear()
for media in self.song.media_files: for media in self.song.media_files:
media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1]) media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1])
@ -679,6 +676,48 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
row = self.topics_list_view.row(item) row = self.topics_list_view.row(item)
self.topics_list_view.takeItem(row) self.topics_list_view.takeItem(row)
def on_songbook_add_button_clicked(self):
item = int(self.songbooks_combo_box.currentIndex())
text = self.songbooks_combo_box.currentText()
if item == 0 and text:
if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Songbook'),
translate('SongsPlugin.EditSongForm', 'This Songbook does not exist, do you want to add it?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
songbook = Book.populate(name=text)
self.manager.save_object(songbook)
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text())
self.load_songbooks()
self.songbooks_combo_box.setCurrentIndex(0)
self.songbook_entry_edit.clear()
else:
return
elif item > 0:
item_id = (self.songbooks_combo_box.itemData(item))
songbook = self.manager.get_object(Book, item_id)
if self.songbooks_list_view.findItems(str(songbook.name), QtCore.Qt.MatchExactly):
critical_error_message_box(
message=translate('SongsPlugin.EditSongForm', 'This Songbook is already in the list.'))
else:
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text())
self.songbooks_combo_box.setCurrentIndex(0)
self.songbook_entry_edit.clear()
else:
QtWidgets.QMessageBox.warning(
self, UiStrings().NISs,
translate('SongsPlugin.EditSongForm', 'You have not selected a valid Songbook. Either select a '
'Songbook from the list, or type in a new Songbook and click the "Add to Song" '
'button to add the new Songbook.'))
def on_songbook_list_view_clicked(self):
self.songbook_remove_button.setEnabled(True)
def on_songbook_remove_button_clicked(self):
self.songbook_remove_button.setEnabled(False)
row = self.songbooks_list_view.row(self.songbooks_list_view.currentItem())
self.songbooks_list_view.takeItem(row)
def on_verse_list_view_clicked(self): def on_verse_list_view_clicked(self):
self.verse_edit_button.setEnabled(True) self.verse_edit_button.setEnabled(True)
self.verse_delete_button.setEnabled(True) self.verse_delete_button.setEnabled(True)
@ -841,17 +880,10 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
""" """
Maintenance button pressed Maintenance button pressed
""" """
temp_song_book = None
item = int(self.song_book_combo_box.currentIndex())
text = self.song_book_combo_box.currentText()
if item == 0 and text:
temp_song_book = text
self.media_item.song_maintenance_form.exec(True) self.media_item.song_maintenance_form.exec(True)
self.load_authors() self.load_authors()
self.load_books() self.load_songbooks()
self.load_topics() self.load_topics()
if temp_song_book:
self.song_book_combo_box.setEditText(temp_song_book)
def on_preview(self, button): def on_preview(self, button):
""" """
@ -931,7 +963,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
log.debug('SongEditForm.clearCaches') log.debug('SongEditForm.clearCaches')
self.authors = [] self.authors = []
self.themes = [] self.themes = []
self.books = [] self.songbooks = []
self.topics = [] self.topics = []
def reject(self): def reject(self):
@ -980,12 +1012,6 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
order.append('%s%s' % (verse_tag, verse_num)) order.append('%s%s' % (verse_tag, verse_num))
self.song.verse_order = ' '.join(order) self.song.verse_order = ' '.join(order)
self.song.ccli_number = self.ccli_number_edit.text() self.song.ccli_number = self.ccli_number_edit.text()
self.song.song_number = self.song_book_number_edit.text()
book_name = self.song_book_combo_box.currentText()
if book_name:
self.song.book = self.manager.get_object_filtered(Book, Book.name == book_name)
else:
self.song.book = None
theme_name = self.theme_combo_box.currentText() theme_name = self.theme_combo_box.currentText()
if theme_name: if theme_name:
self.song.theme_name = theme_name self.song.theme_name = theme_name
@ -1004,6 +1030,13 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
topic = self.manager.get_object(Topic, topic_id) topic = self.manager.get_object(Topic, topic_id)
if topic is not None: if topic is not None:
self.song.topics.append(topic) self.song.topics.append(topic)
self.song.songbook_entries = []
for row in range(self.songbooks_list_view.count()):
item = self.songbooks_list_view.item(row)
songbook_id = item.data(QtCore.Qt.UserRole)[0]
songbook = self.manager.get_object(Book, songbook_id)
entry = item.data(QtCore.Qt.UserRole)[1]
self.song.add_songbook_entry(songbook, entry)
# Save the song here because we need a valid id for the audio files. # Save the song here because we need a valid id for the audio files.
clean_song(self.manager, self.song) clean_song(self.manager, self.song)
self.manager.save_object(self.song) self.manager.save_object(self.song)

View File

@ -28,7 +28,7 @@ from openlp.core.lib.ui import create_button_box
class Ui_SongBookDialog(object): class Ui_SongBookDialog(object):
""" """
The user interface for the song book dialog. The user interface for the Songbook dialog.
""" """
def setupUi(self, song_book_dialog): def setupUi(self, song_book_dialog):
""" """
@ -63,6 +63,6 @@ class Ui_SongBookDialog(object):
""" """
Translate the UI on the fly. Translate the UI on the fly.
""" """
song_book_dialog.setWindowTitle(translate('SongsPlugin.SongBookForm', 'Song Book Maintenance')) song_book_dialog.setWindowTitle(translate('SongsPlugin.SongBookForm', 'Songbook Maintenance'))
self.name_label.setText(translate('SongsPlugin.SongBookForm', '&Name:')) self.name_label.setText(translate('SongsPlugin.SongBookForm', '&Name:'))
self.publisher_label.setText(translate('SongsPlugin.SongBookForm', '&Publisher:')) self.publisher_label.setText(translate('SongsPlugin.SongBookForm', '&Publisher:'))

View File

@ -160,6 +160,36 @@ class Song(BaseModel):
self.authors_songs.remove(author_song) self.authors_songs.remove(author_song)
return return
def add_songbook_entry(self, songbook, entry):
"""
Add a Songbook Entry to the song if it not yet exists
:param songbook_name: Name of the Songbook.
:param entry: Entry in the Songbook (usually a number)
"""
for songbook_entry in self.songbook_entries:
if songbook_entry.songbook.name == songbook.name and songbook_entry.entry == entry:
return
new_songbook_entry = SongBookEntry()
new_songbook_entry.songbook = songbook
new_songbook_entry.entry = entry
self.songbook_entries.append(new_songbook_entry)
class SongBookEntry(BaseModel):
"""
SongBookEntry model
"""
def __repr__(self):
return SongBookEntry.get_display_name(self.songbook.name, self.entry)
@staticmethod
def get_display_name(songbook_name, entry):
if entry:
return "%s #%s" % (songbook_name, entry)
return songbook_name
class Topic(BaseModel): class Topic(BaseModel):
""" """
@ -182,6 +212,7 @@ def init_schema(url):
* media_files_songs * media_files_songs
* song_books * song_books
* songs * songs
* songs_songbooks
* songs_topics * songs_topics
* topics * topics
@ -222,7 +253,6 @@ def init_schema(url):
The *songs* table has the following columns: The *songs* table has the following columns:
* id * id
* song_book_id
* title * title
* alternate_title * alternate_title
* lyrics * lyrics
@ -230,11 +260,17 @@ def init_schema(url):
* copyright * copyright
* comments * comments
* ccli_number * ccli_number
* song_number
* theme_name * theme_name
* search_title * search_title
* search_lyrics * search_lyrics
**songs_songsbooks Table**
This is a mapping table between the *songs* and the *song_books* tables. It has the following columns:
* songbook_id
* song_id
* entry # The song number, like 120 or 550A
**songs_topics Table** **songs_topics Table**
This is a bridging table between the *songs* and *topics* tables, which This is a bridging table between the *songs* and *topics* tables, which
serves to create a many-to-many relationship between the two tables. It serves to create a many-to-many relationship between the two tables. It
@ -284,7 +320,6 @@ def init_schema(url):
songs_table = Table( songs_table = Table(
'songs', metadata, 'songs', metadata,
Column('id', types.Integer(), primary_key=True), Column('id', types.Integer(), primary_key=True),
Column('song_book_id', types.Integer(), ForeignKey('song_books.id'), default=None),
Column('title', types.Unicode(255), nullable=False), Column('title', types.Unicode(255), nullable=False),
Column('alternate_title', types.Unicode(255)), Column('alternate_title', types.Unicode(255)),
Column('lyrics', types.UnicodeText, nullable=False), Column('lyrics', types.UnicodeText, nullable=False),
@ -292,7 +327,6 @@ def init_schema(url):
Column('copyright', types.Unicode(255)), Column('copyright', types.Unicode(255)),
Column('comments', types.UnicodeText), Column('comments', types.UnicodeText),
Column('ccli_number', types.Unicode(64)), Column('ccli_number', types.Unicode(64)),
Column('song_number', types.Unicode(64)),
Column('theme_name', types.Unicode(128)), Column('theme_name', types.Unicode(128)),
Column('search_title', types.Unicode(255), index=True, nullable=False), Column('search_title', types.Unicode(255), index=True, nullable=False),
Column('search_lyrics', types.UnicodeText, nullable=False), Column('search_lyrics', types.UnicodeText, nullable=False),
@ -316,6 +350,14 @@ def init_schema(url):
Column('author_type', types.Unicode(255), primary_key=True, nullable=False, server_default=text('""')) Column('author_type', types.Unicode(255), primary_key=True, nullable=False, server_default=text('""'))
) )
# Definition of the "songs_songbooks" table
songs_songbooks_table = Table(
'songs_songbooks', metadata,
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
Column('entry', types.Unicode(255), primary_key=True, nullable=False)
)
# Definition of the "songs_topics" table # Definition of the "songs_topics" table
songs_topics_table = Table( songs_topics_table = Table(
'songs_topics', metadata, 'songs_topics', metadata,
@ -329,6 +371,9 @@ def init_schema(url):
mapper(AuthorSong, authors_songs_table, properties={ mapper(AuthorSong, authors_songs_table, properties={
'author': relation(Author) 'author': relation(Author)
}) })
mapper(SongBookEntry, songs_songbooks_table, properties={
'songbook': relation(Book)
})
mapper(Book, song_books_table) mapper(Book, song_books_table)
mapper(MediaFile, media_files_table) mapper(MediaFile, media_files_table)
mapper(Song, songs_table, properties={ mapper(Song, songs_table, properties={
@ -337,8 +382,8 @@ def init_schema(url):
'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"), 'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"),
# Use lazy='joined' to always load authors when the song is fetched from the database (bug 1366198) # Use lazy='joined' to always load authors when the song is fetched from the database (bug 1366198)
'authors': relation(Author, secondary=authors_songs_table, viewonly=True, lazy='joined'), 'authors': relation(Author, secondary=authors_songs_table, viewonly=True, lazy='joined'),
'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight), 'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
'songbook_entries': relation(SongBookEntry, backref='song', cascade="all, delete-orphan"),
'topics': relation(Topic, backref='songs', secondary=songs_topics_table) 'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
}) })
mapper(Topic, topics_table) mapper(Topic, topics_table)

View File

@ -37,7 +37,7 @@ from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songexportform import SongExportForm from openlp.plugins.songs.forms.songexportform import SongExportForm
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
@ -152,7 +152,7 @@ class SongMediaItem(MediaManagerItem):
(SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors, (SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors,
translate('SongsPlugin.MediaItem', 'Search Authors...')), translate('SongsPlugin.MediaItem', 'Search Authors...')),
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks, (SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
translate('SongsPlugin.MediaItem', 'Search Song Books...')), translate('SongsPlugin.MediaItem', 'Search Songbooks...')),
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes) (SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
]) ])
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section)) self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
@ -185,17 +185,8 @@ class SongMediaItem(MediaManagerItem):
Author, Author.display_name.like(search_string), Author.display_name.asc()) Author, Author.display_name.like(search_string), Author.display_name.asc())
self.display_results_author(search_results) self.display_results_author(search_results)
elif search_type == SongSearch.Books: elif search_type == SongSearch.Books:
log.debug('Books Search') log.debug('Songbook Search')
search_string = '%' + search_keywords + '%' self.display_results_book(search_keywords)
search_results = self.plugin.manager.get_all_objects(Book, Book.name.like(search_string), Book.name.asc())
song_number = False
if not search_results:
search_keywords = search_keywords.rpartition(' ')
search_string = '%' + search_keywords[0] + '%'
search_results = self.plugin.manager.get_all_objects(Book,
Book.name.like(search_string), Book.name.asc())
song_number = re.sub(r'[^0-9]', '', search_keywords[2])
self.display_results_book(search_results, song_number)
elif search_type == SongSearch.Themes: elif search_type == SongSearch.Themes:
log.debug('Theme Search') log.debug('Theme Search')
search_string = '%' + search_keywords + '%' search_string = '%' + search_keywords + '%'
@ -255,21 +246,29 @@ class SongMediaItem(MediaManagerItem):
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
def display_results_book(self, search_results, song_number=False): def display_results_book(self, search_keywords):
log.debug('display results Book') log.debug('display results Book')
self.list_view.clear() self.list_view.clear()
for book in search_results:
songs = sorted(book.songs, key=lambda song: int(re.match(r'[0-9]+', '0' + song.song_number).group())) search_keywords = search_keywords.rpartition(' ')
for song in songs: search_book = search_keywords[0]
# Do not display temporary songs search_entry = re.sub(r'[^0-9]', '', search_keywords[2])
if song.temporary:
continue songbook_entries = (self.plugin.manager.session.query(SongBookEntry)
if song_number and song_number not in song.song_number: .join(Book)
continue .order_by(Book.name)
song_detail = '%s - %s (%s)' % (book.name, song.song_number, song.title) .order_by(SongBookEntry.entry))
song_name = QtWidgets.QListWidgetItem(song_detail) for songbook_entry in songbook_entries:
song_name.setData(QtCore.Qt.UserRole, song.id) if songbook_entry.song.temporary:
self.list_view.addItem(song_name) continue
if search_book.lower() not in songbook_entry.songbook.name.lower():
continue
if search_entry not in songbook_entry.entry:
continue
song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
self.list_view.addItem(song_name)
def on_clear_text_button_click(self): def on_clear_text_button_click(self):
""" """
@ -524,8 +523,9 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright)) item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright))
else: else:
item.raw_footer.append(song.copyright) item.raw_footer.append(song.copyright)
if self.display_songbook and song.book: if self.display_songbook and song.songbook_entries:
item.raw_footer.append("%s #%s" % (song.book.name, song.song_number)) songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
item.raw_footer.append(", ".join(songbooks))
if Settings().value('core/ccli number'): if Settings().value('core/ccli number'):
item.raw_footer.append(translate('SongsPlugin.MediaItem', item.raw_footer.append(translate('SongsPlugin.MediaItem',
'CCLI License: ') + Settings().value('core/ccli number')) 'CCLI License: ') + Settings().value('core/ccli number'))

View File

@ -266,13 +266,12 @@ class OpenLyrics(object):
element.set('type', AuthorType.Music) element.set('type', AuthorType.Music)
else: else:
element.set('type', author_song.author_type) element.set('type', author_song.author_type)
book = self.manager.get_object_filtered(Book, Book.id == song.song_book_id) if song.songbook_entries:
if book is not None:
book = book.name
songbooks = etree.SubElement(properties, 'songbooks') songbooks = etree.SubElement(properties, 'songbooks')
element = self._add_text_to_element('songbook', songbooks, None, book) for songbook_entry in song.songbook_entries:
if song.song_number: element = self._add_text_to_element('songbook', songbooks, None, songbook_entry.songbook.name)
element.set('entry', song.song_number) if songbook_entry.entry:
element.set('entry', songbook_entry.entry)
if song.topics: if song.topics:
themes = etree.SubElement(properties, 'themes') themes = etree.SubElement(properties, 'themes')
for topic in song.topics: for topic in song.topics:
@ -744,8 +743,6 @@ class OpenLyrics(object):
:param properties: The property object (lxml.objectify.ObjectifiedElement). :param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object. :param song: The song object.
""" """
song.song_book_id = None
song.song_number = ''
if hasattr(properties, 'songbooks'): if hasattr(properties, 'songbooks'):
for songbook in properties.songbooks.songbook: for songbook in properties.songbooks.songbook:
book_name = songbook.get('name', '') book_name = songbook.get('name', '')
@ -755,10 +752,7 @@ class OpenLyrics(object):
# We need to create a book, because it does not exist. # We need to create a book, because it does not exist.
book = Book.populate(name=book_name, publisher='') book = Book.populate(name=book_name, publisher='')
self.manager.save_object(book) self.manager.save_object(book)
song.song_book_id = book.id song.add_songbook_entry(book, songbook.get('entry', ''))
song.song_number = songbook.get('entry', '')
# We only support one song book, so take the first one.
break
def _process_titles(self, properties, song): def _process_titles(self, properties, song):
""" """

View File

@ -35,8 +35,8 @@ class SongStrings(object):
Authors = translate('OpenLP.Ui', 'Authors', 'Plural') Authors = translate('OpenLP.Ui', 'Authors', 'Plural')
AuthorUnknown = translate('OpenLP.Ui', 'Author Unknown') # Used to populate the database. AuthorUnknown = translate('OpenLP.Ui', 'Author Unknown') # Used to populate the database.
CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.') CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.')
SongBook = translate('OpenLP.Ui', 'Song Book', 'Singular') SongBook = translate('OpenLP.Ui', 'Songbook', 'Singular')
SongBooks = translate('OpenLP.Ui', 'Song Books', 'Plural') SongBooks = translate('OpenLP.Ui', 'Songbooks', 'Plural')
SongIncomplete = translate('OpenLP.Ui', 'Title and/or verses not found') SongIncomplete = translate('OpenLP.Ui', 'Title and/or verses not found')
SongMaintenance = translate('OpenLP.Ui', 'Song Maintenance') SongMaintenance = translate('OpenLP.Ui', 'Song Maintenance')
Topic = translate('OpenLP.Ui', 'Topic', 'Singular') Topic = translate('OpenLP.Ui', 'Topic', 'Singular')

View File

@ -29,10 +29,10 @@ from sqlalchemy import Table, Column, ForeignKey, types
from sqlalchemy.sql.expression import func, false, null, text from sqlalchemy.sql.expression import func, false, null, text
from openlp.core.lib.db import get_upgrade_op from openlp.core.lib.db import get_upgrade_op
from openlp.core.common import trace_error_handler from openlp.core.utils.db import drop_columns
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__version__ = 4 __version__ = 5
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version # TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@ -117,3 +117,34 @@ def upgrade_4(session, metadata):
op.rename_table('authors_songs_tmp', 'authors_songs') op.rename_table('authors_songs_tmp', 'authors_songs')
else: else:
log.warning('Skipping upgrade_4 step of upgrading the song db') log.warning('Skipping upgrade_4 step of upgrading the song db')
def upgrade_5(session, metadata):
"""
Version 5 upgrade.
This upgrade adds support for multiple songbooks
"""
op = get_upgrade_op(session)
songs_table = Table('songs', metadata)
if 'song_book_id' in [col.name for col in songs_table.c.values()]:
log.warning('Skipping upgrade_5 step of upgrading the song db')
return
# Create the mapping table (songs <-> songbooks)
op.create_table('songs_songbooks',
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
Column('entry', types.Unicode(255), primary_key=True, nullable=False))
# Migrate old data
op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\
WHERE song_book_id IS NOT NULL AND song_number IS NOT NULL')
# Drop old columns
if metadata.bind.url.get_dialect().name == 'sqlite':
drop_columns(op, 'songs', ['song_book_id', 'song_number'])
else:
op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
op.drop_column('songs', 'song_book_id')
op.drop_column('songs', 'song_number')

View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.utils.db package.
"""
import os
import shutil
import sqlalchemy
from unittest import TestCase
from tempfile import mkdtemp
from openlp.core.utils.db import drop_column, drop_columns
from openlp.core.lib.db import init_db, get_upgrade_op
from tests.utils.constants import TEST_RESOURCES_PATH
class TestUtilsDBFunctions(TestCase):
def setUp(self):
"""
Create temp folder for keeping db file
"""
self.tmp_folder = mkdtemp()
def tearDown(self):
"""
Clean up
"""
shutil.rmtree(self.tmp_folder)
def delete_column_test(self):
"""
Test deleting a single column in a table
"""
# GIVEN: A temporary song db
db_path = os.path.join(TEST_RESOURCES_PATH, 'songs', 'songs-1.9.7.sqlite')
db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite')
shutil.copyfile(db_path, db_tmp_path)
db_url = 'sqlite:///' + db_tmp_path
session, metadata = init_db(db_url)
op = get_upgrade_op(session)
# WHEN: Deleting a columns in a table
drop_column(op, 'songs', 'song_book_id')
# THEN: The column should have been deleted
meta = sqlalchemy.MetaData(bind=op.get_bind())
meta.reflect()
columns = meta.tables['songs'].columns
for column in columns:
if column.name == 'song_book_id':
self.fail("The column 'song_book_id' should have been deleted.")
def delete_columns_test(self):
"""
Test deleting multiple columns in a table
"""
# GIVEN: A temporary song db
db_path = os.path.join(TEST_RESOURCES_PATH, 'songs', 'songs-1.9.7.sqlite')
db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite')
shutil.copyfile(db_path, db_tmp_path)
db_url = 'sqlite:///' + db_tmp_path
session, metadata = init_db(db_url)
op = get_upgrade_op(session)
# WHEN: Deleting a columns in a table
drop_columns(op, 'songs', ['song_book_id', 'song_number'])
# THEN: The columns should have been deleted
meta = sqlalchemy.MetaData(bind=op.get_bind())
meta.reflect()
columns = meta.tables['songs'].columns
for column in columns:
if column.name == 'song_book_id' or column.name == 'song_number':
self.fail("The column '%s' should have been deleted." % column.name)

View File

@ -27,7 +27,7 @@ import shutil
from unittest import TestCase from unittest import TestCase
from tempfile import mkdtemp from tempfile import mkdtemp
from openlp.plugins.songs.lib.db import Song, Author, AuthorType from openlp.plugins.songs.lib.db import Song, Author, AuthorType, Book
from openlp.plugins.songs.lib import upgrade from openlp.plugins.songs.lib import upgrade
from openlp.core.lib.db import upgrade_db from openlp.core.lib.db import upgrade_db
from tests.utils.constants import TEST_RESOURCES_PATH from tests.utils.constants import TEST_RESOURCES_PATH
@ -179,6 +179,23 @@ class TestDB(TestCase):
# THEN: It should return the name with the type in brackets # THEN: It should return the name with the type in brackets
self.assertEqual("John Doe (Translation)", display_name) self.assertEqual("John Doe (Translation)", display_name)
def test_add_songbooks(self):
"""
Test that adding songbooks to a song works correctly
"""
# GIVEN: A mocked song and songbook
song = Song()
song.songbook_entries = []
songbook = Book()
songbook.name = "Thy Word"
# WHEN: We add two songbooks to a Song
song.add_songbook_entry(songbook, "120")
song.add_songbook_entry(songbook, "550A")
# THEN: The song should have two songbook entries
self.assertEqual(len(song.songbook_entries), 2, 'There should be two Songbook entries.')
def test_upgrade_old_song_db(self): def test_upgrade_old_song_db(self):
""" """
Test that we can upgrade an old song db to the current schema Test that we can upgrade an old song db to the current schema
@ -192,7 +209,7 @@ class TestDB(TestCase):
# WHEN: upgrading the db # WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade) updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# Then the song db should have been upgraded to the latest version # THEN: the song db should have been upgraded to the latest version
self.assertEqual(updated_to_version, latest_version, self.assertEqual(updated_to_version, latest_version,
'The song DB should have been upgrade to the latest version') 'The song DB should have been upgrade to the latest version')
@ -209,6 +226,6 @@ class TestDB(TestCase):
# WHEN: upgrading the db # WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade) updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# Then the song db should have been upgraded to the latest version without errors # THEN: the song db should have been upgraded to the latest version without errors
self.assertEqual(updated_to_version, latest_version, self.assertEqual(updated_to_version, latest_version,
'The song DB should have been upgrade to the latest version') 'The song DB should have been upgrade to the latest version')

View File

@ -29,7 +29,7 @@ from PyQt5 import QtCore
from openlp.core.common import Registry, Settings from openlp.core.common import Registry, Settings
from openlp.core.lib import ServiceItem from openlp.core.lib import ServiceItem
from openlp.plugins.songs.lib.mediaitem import SongMediaItem from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from openlp.plugins.songs.lib.db import AuthorType from openlp.plugins.songs.lib.db import AuthorType, Song
from tests.functional import patch, MagicMock from tests.functional import patch, MagicMock
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -152,29 +152,36 @@ class TestMediaItem(TestCase, TestMixin):
def build_song_footer_base_songbook_test(self): def build_song_footer_base_songbook_test(self):
""" """
Test build songs footer with basic song and a songbook Test build songs footer with basic song and multiple songbooks
""" """
# GIVEN: A Song and a Service Item # GIVEN: A Song and a Service Item
mock_song = MagicMock() song = Song()
mock_song.title = 'My Song' song.title = 'My Song'
mock_song.copyright = 'My copyright' song.copyright = 'My copyright'
mock_song.book = MagicMock() song.authors_songs = []
mock_song.book.name = "My songbook" song.songbook_entries = []
mock_song.song_number = 12 song.ccli_number = ''
book1 = MagicMock()
book1.name = "My songbook"
book2 = MagicMock()
book2.name = "Thy songbook"
song.songbookentries = []
song.add_songbook_entry(book1, '12')
song.add_songbook_entry(book2, '502A')
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song) self.media_item.generate_footer(service_item, song)
# THEN: The songbook should not be in the footer # 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 # WHEN: I activate the "display songbook" option
self.media_item.display_songbook = True self.media_item.display_songbook = True
self.media_item.generate_footer(service_item, mock_song) self.media_item.generate_footer(service_item, song)
# THEN: The songbook should be in the footer # 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, Thy songbook #502A'])
def build_song_footer_copyright_enabled_test(self): def build_song_footer_copyright_enabled_test(self):
""" """