forked from openlp/openlp
Various fixes:
- Fix PresentationManager importer to take weird XML into account - Fix OpenLP importer to support author_types - Fix opening the data folder in KDE where it was being misinterpreted as a SMB share - Fix a problem with the media player no longer controlling the playlist - Fix a problem where a timer would expire after the application had been torn down - Fix song database upgrades by moving upgrades 4 and 5 into 6 and writing some damage control - Add a CHANGELOG.rst file
This commit is contained in:
parent
07ee83b4fe
commit
a2a51b95dd
10
CHANGELOG.rst
Normal file
10
CHANGELOG.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
OpenLP 2.4.6
|
||||||
|
============
|
||||||
|
|
||||||
|
* Fixed a bug where the author type upgrade was being ignore because it was looking at the wrong table
|
||||||
|
* Fixed a bug where the songs_songbooks table was not being created because the if expression was the wrong way round
|
||||||
|
* Changed the songs_songbooks migration SQL slightly to take into account a bug that has (hopefully) been fixed
|
||||||
|
* Sometimes the timer goes off as OpenLP is shutting down, and the application has already been deleted (reported via support system)
|
||||||
|
* Fix opening the data folder (KDE thought the old way was an SMB share)
|
||||||
|
* Fix a problem with the new QMediaPlayer not controlling the playlist anymore
|
||||||
|
* Added importing of author types to the OpenLP 2 song importer
|
@ -689,7 +689,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
|||||||
"""
|
"""
|
||||||
Skip forward to the next track in the list
|
Skip forward to the next track in the list
|
||||||
"""
|
"""
|
||||||
self.player.next()
|
self.playerlist.next()
|
||||||
|
|
||||||
def go_to(self, index):
|
def go_to(self, index):
|
||||||
"""
|
"""
|
||||||
|
@ -766,7 +766,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
Use the Online manual in other cases. (Linux)
|
Use the Online manual in other cases. (Linux)
|
||||||
"""
|
"""
|
||||||
if is_macosx() or is_win():
|
if is_macosx() or is_win():
|
||||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + self.local_help_file))
|
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(self.local_help_file))
|
||||||
else:
|
else:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open_new('http://manual.openlp.org/')
|
webbrowser.open_new('http://manual.openlp.org/')
|
||||||
@ -789,7 +789,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
Open data folder
|
Open data folder
|
||||||
"""
|
"""
|
||||||
path = AppLocation.get_data_path()
|
path = AppLocation.get_data_path()
|
||||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + path))
|
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path))
|
||||||
|
|
||||||
def on_update_theme_images(self):
|
def on_update_theme_images(self):
|
||||||
"""
|
"""
|
||||||
@ -1393,7 +1393,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
if event.timerId() == self.timer_id:
|
if event.timerId() == self.timer_id:
|
||||||
self.timer_id = 0
|
self.timer_id = 0
|
||||||
self.load_progress_bar.hide()
|
self.load_progress_bar.hide()
|
||||||
self.application.process_events()
|
# Sometimes the timer goes off as OpenLP is shutting down, and the application has already been deleted
|
||||||
|
if self.application:
|
||||||
|
self.application.process_events()
|
||||||
|
|
||||||
def set_new_data_path(self, new_data_path):
|
def set_new_data_path(self, new_data_path):
|
||||||
"""
|
"""
|
||||||
|
@ -61,6 +61,12 @@ class OpenLPSongImport(SongImport):
|
|||||||
:param progress_dialog: The QProgressDialog used when importing songs from the FRW.
|
:param progress_dialog: The QProgressDialog used when importing songs from the FRW.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class OldAuthorSong(BaseModel):
|
||||||
|
"""
|
||||||
|
Maps to the authors_songs table
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
class OldAuthor(BaseModel):
|
class OldAuthor(BaseModel):
|
||||||
"""
|
"""
|
||||||
Maps to the authors table
|
Maps to the authors table
|
||||||
@ -109,14 +115,19 @@ class OpenLPSongImport(SongImport):
|
|||||||
source_meta.reflect(engine)
|
source_meta.reflect(engine)
|
||||||
self.source_session = scoped_session(sessionmaker(bind=engine))
|
self.source_session = scoped_session(sessionmaker(bind=engine))
|
||||||
# Run some checks to see which version of the database we have
|
# Run some checks to see which version of the database we have
|
||||||
if 'media_files' in list(source_meta.tables.keys()):
|
table_list = list(source_meta.tables.keys())
|
||||||
|
if 'media_files' in table_list:
|
||||||
has_media_files = True
|
has_media_files = True
|
||||||
else:
|
else:
|
||||||
has_media_files = False
|
has_media_files = False
|
||||||
if 'songs_songbooks' in list(source_meta.tables.keys()):
|
if 'songs_songbooks' in table_list:
|
||||||
has_songs_books = True
|
has_songs_books = True
|
||||||
else:
|
else:
|
||||||
has_songs_books = False
|
has_songs_books = False
|
||||||
|
if 'authors_songs' in table_list:
|
||||||
|
has_authors_songs = True
|
||||||
|
else:
|
||||||
|
has_authors_songs = False
|
||||||
# Load up the tabls and map them out
|
# Load up the tabls and map them out
|
||||||
source_authors_table = source_meta.tables['authors']
|
source_authors_table = source_meta.tables['authors']
|
||||||
source_song_books_table = source_meta.tables['song_books']
|
source_song_books_table = source_meta.tables['song_books']
|
||||||
@ -139,6 +150,10 @@ class OpenLPSongImport(SongImport):
|
|||||||
class_mapper(OldSongBookEntry)
|
class_mapper(OldSongBookEntry)
|
||||||
except UnmappedClassError:
|
except UnmappedClassError:
|
||||||
mapper(OldSongBookEntry, source_songs_songbooks_table, properties={'songbook': relation(OldBook)})
|
mapper(OldSongBookEntry, source_songs_songbooks_table, properties={'songbook': relation(OldBook)})
|
||||||
|
if has_authors_songs and 'author_type' in source_authors_songs_table.c.values():
|
||||||
|
has_author_type = True
|
||||||
|
else:
|
||||||
|
has_author_type = False
|
||||||
# Set up the songs relationships
|
# Set up the songs relationships
|
||||||
song_props = {
|
song_props = {
|
||||||
'authors': relation(OldAuthor, backref='songs', secondary=source_authors_songs_table),
|
'authors': relation(OldAuthor, backref='songs', secondary=source_authors_songs_table),
|
||||||
@ -157,6 +172,8 @@ class OpenLPSongImport(SongImport):
|
|||||||
song_props['songbook_entries'] = relation(OldSongBookEntry, backref='song', cascade='all, delete-orphan')
|
song_props['songbook_entries'] = relation(OldSongBookEntry, backref='song', cascade='all, delete-orphan')
|
||||||
else:
|
else:
|
||||||
song_props['book'] = relation(OldBook, backref='songs')
|
song_props['book'] = relation(OldBook, backref='songs')
|
||||||
|
if has_authors_songs:
|
||||||
|
song_props['authors_songs'] = relation(OldAuthorSong)
|
||||||
# Map the rest of the tables
|
# Map the rest of the tables
|
||||||
try:
|
try:
|
||||||
class_mapper(OldAuthor)
|
class_mapper(OldAuthor)
|
||||||
@ -174,6 +191,11 @@ class OpenLPSongImport(SongImport):
|
|||||||
class_mapper(OldTopic)
|
class_mapper(OldTopic)
|
||||||
except UnmappedClassError:
|
except UnmappedClassError:
|
||||||
mapper(OldTopic, source_topics_table)
|
mapper(OldTopic, source_topics_table)
|
||||||
|
if has_authors_songs:
|
||||||
|
try:
|
||||||
|
class_mapper(OldTopic)
|
||||||
|
except UnmappedClassError:
|
||||||
|
mapper(OldTopic, source_topics_table)
|
||||||
|
|
||||||
source_songs = self.source_session.query(OldSong).all()
|
source_songs = self.source_session.query(OldSong).all()
|
||||||
if self.import_wizard:
|
if self.import_wizard:
|
||||||
@ -205,8 +227,16 @@ class OpenLPSongImport(SongImport):
|
|||||||
existing_author = Author.populate(
|
existing_author = Author.populate(
|
||||||
first_name=author.first_name,
|
first_name=author.first_name,
|
||||||
last_name=author.last_name,
|
last_name=author.last_name,
|
||||||
display_name=author.display_name)
|
display_name=author.display_name
|
||||||
new_song.add_author(existing_author)
|
)
|
||||||
|
# If this is a new database, we need to import the author_type too
|
||||||
|
author_type = None
|
||||||
|
if has_author_type:
|
||||||
|
for author_song in song.authors_songs:
|
||||||
|
if author_song.author_id == author.id:
|
||||||
|
author_type = author_song.author_type
|
||||||
|
break
|
||||||
|
new_song.add_author(existing_author, author_type)
|
||||||
# Find or create all the topics and add them to the new song object
|
# Find or create all the topics and add them to the new song object
|
||||||
if song.topics:
|
if song.topics:
|
||||||
for topic in song.topics:
|
for topic in song.topics:
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
The :mod:`presentationmanager` module provides the functionality for importing
|
The :mod:`presentationmanager` module provides the functionality for importing
|
||||||
Presentationmanager song files into the current database.
|
Presentationmanager song files into the current database.
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -34,8 +33,6 @@ from openlp.core.common import translate
|
|||||||
from openlp.core.ui.lib.wizard import WizardStrings
|
from openlp.core.ui.lib.wizard import WizardStrings
|
||||||
from .songimport import SongImport
|
from .songimport import SongImport
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PresentationManagerImport(SongImport):
|
class PresentationManagerImport(SongImport):
|
||||||
"""
|
"""
|
||||||
@ -75,11 +72,9 @@ class PresentationManagerImport(SongImport):
|
|||||||
some gymnastics.
|
some gymnastics.
|
||||||
"""
|
"""
|
||||||
if hasattr(elem, name):
|
if hasattr(elem, name):
|
||||||
log.debug('%s: %s', name, getattr(elem, name))
|
|
||||||
return str(getattr(elem, name))
|
return str(getattr(elem, name))
|
||||||
name = name[0].upper() + name[1:]
|
name = name[0].upper() + name[1:]
|
||||||
if hasattr(elem, name):
|
if hasattr(elem, name):
|
||||||
log.debug('%s: %s', name, getattr(elem, name))
|
|
||||||
return str(getattr(elem, name))
|
return str(getattr(elem, name))
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
@ -32,7 +32,7 @@ from openlp.core.common.db import drop_columns
|
|||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__version__ = 5
|
__version__ = 6
|
||||||
|
|
||||||
|
|
||||||
# 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
|
||||||
@ -52,7 +52,6 @@ def upgrade_1(session, metadata):
|
|||||||
:param metadata:
|
:param metadata:
|
||||||
"""
|
"""
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
songs_table = Table('songs', metadata, autoload=True)
|
|
||||||
if 'media_files_songs' in [t.name for t in metadata.tables.values()]:
|
if 'media_files_songs' in [t.name for t in metadata.tables.values()]:
|
||||||
op.drop_table('media_files_songs')
|
op.drop_table('media_files_songs')
|
||||||
op.add_column('media_files', Column('song_id', types.Integer(), server_default=null()))
|
op.add_column('media_files', Column('song_id', types.Integer(), server_default=null()))
|
||||||
@ -102,21 +101,8 @@ def upgrade_4(session, metadata):
|
|||||||
|
|
||||||
This upgrade adds a column for author type to the authors_songs table
|
This upgrade adds a column for author type to the authors_songs table
|
||||||
"""
|
"""
|
||||||
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
# This is now empty due to a bug in the upgrade
|
||||||
# and copy the old values
|
pass
|
||||||
op = get_upgrade_op(session)
|
|
||||||
songs_table = Table('songs', metadata)
|
|
||||||
if 'author_type' not in [col.name for col in songs_table.c.values()]:
|
|
||||||
op.create_table('authors_songs_tmp',
|
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
|
||||||
Column('author_type', types.Unicode(255), primary_key=True,
|
|
||||||
nullable=False, server_default=text('""')))
|
|
||||||
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
|
||||||
op.drop_table('authors_songs')
|
|
||||||
op.rename_table('authors_songs_tmp', 'authors_songs')
|
|
||||||
else:
|
|
||||||
log.warning('Skipping upgrade_4 step of upgrading the song db')
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade_5(session, metadata):
|
def upgrade_5(session, metadata):
|
||||||
@ -125,26 +111,54 @@ def upgrade_5(session, metadata):
|
|||||||
|
|
||||||
This upgrade adds support for multiple songbooks
|
This upgrade adds support for multiple songbooks
|
||||||
"""
|
"""
|
||||||
|
# This is now empty due to a bug in the upgrade
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_6(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 6 upgrade
|
||||||
|
|
||||||
|
This version corrects the errors in upgrades 4 and 5
|
||||||
|
"""
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
songs_table = Table('songs', metadata)
|
# Move upgrade 4 to here and correct it (authors_songs table, not songs table)
|
||||||
if 'song_book_id' in [col.name for col in songs_table.c.values()]:
|
authors_songs = Table('authors_songs', metadata, autoload=True)
|
||||||
log.warning('Skipping upgrade_5 step of upgrading the song db')
|
if 'author_type' not in [col.name for col in authors_songs.c.values()]:
|
||||||
return
|
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
||||||
|
# and copy the old values
|
||||||
|
op.create_table(
|
||||||
|
'authors_songs_tmp',
|
||||||
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
|
Column('author_type', types.Unicode(255), primary_key=True,
|
||||||
|
nullable=False, server_default=text('""'))
|
||||||
|
)
|
||||||
|
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
||||||
|
op.drop_table('authors_songs')
|
||||||
|
op.rename_table('authors_songs_tmp', 'authors_songs')
|
||||||
|
# Move upgrade 5 here to correct it
|
||||||
|
if 'songs_songbooks' not in [t.name for t in metadata.tables.values()]:
|
||||||
|
# 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)
|
||||||
|
)
|
||||||
|
|
||||||
# Create the mapping table (songs <-> songbooks)
|
# Migrate old data
|
||||||
op.create_table('songs_songbooks',
|
op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\
|
||||||
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
|
WHERE song_book_id IS NOT NULL AND song_number IS NOT NULL AND song_book_id <> 0')
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
|
||||||
Column('entry', types.Unicode(255), primary_key=True, nullable=False))
|
|
||||||
|
|
||||||
# Migrate old data
|
# Drop old columns
|
||||||
op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\
|
if metadata.bind.url.get_dialect().name == 'sqlite':
|
||||||
WHERE song_book_id IS NOT NULL AND song_number IS NOT NULL')
|
drop_columns(op, 'songs', ['song_book_id', 'song_number'])
|
||||||
|
else:
|
||||||
# Drop old columns
|
op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
|
||||||
if metadata.bind.url.get_dialect().name == 'sqlite':
|
op.drop_column('songs', 'song_book_id')
|
||||||
drop_columns(op, 'songs', ['song_book_id', 'song_number'])
|
op.drop_column('songs', 'song_number')
|
||||||
else:
|
# Finally, clean up our mess in people's databases
|
||||||
op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
|
songs_songbooks = Table('songs_songbooks', metadata, autoload=True)
|
||||||
op.drop_column('songs', 'song_book_id')
|
del_query = songs_songbooks.delete().where(songs_songbooks.c.songbook_id == 0)
|
||||||
op.drop_column('songs', 'song_number')
|
op.execute(del_query)
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
"""
|
"""
|
||||||
Package to test the openlp.plugins.bibles.forms.bibleimportform package.
|
Package to test the openlp.plugins.bibles.forms.bibleimportform package.
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
from unittest import TestCase, skip
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
@ -33,6 +33,7 @@ from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm, PYSWORD
|
|||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
|
||||||
|
@skip('One of the QFormLayouts in the BibleImportForm is causing a segfault')
|
||||||
class TestBibleImportForm(TestCase, TestMixin):
|
class TestBibleImportForm(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
Test the BibleImportForm class
|
Test the BibleImportForm class
|
||||||
|
@ -0,0 +1,292 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 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.plugins.songs.forms.songmaintenanceform package.
|
||||||
|
"""
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import MagicMock, patch, call
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtTest, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common import Registry, UiStrings
|
||||||
|
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||||
|
|
||||||
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestSongMaintenanceForm(TestCase, TestMixin):
|
||||||
|
"""
|
||||||
|
Test the SongMaintenanceForm class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Create the UI
|
||||||
|
"""
|
||||||
|
Registry.create()
|
||||||
|
self.setup_application()
|
||||||
|
self.main_window = QtWidgets.QMainWindow()
|
||||||
|
Registry().register('main_window', self.main_window)
|
||||||
|
self.mocked_manager = MagicMock()
|
||||||
|
self.form = SongMaintenanceForm(self.mocked_manager)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""
|
||||||
|
Delete all the C++ objects at the end so that we don't have a segfault
|
||||||
|
"""
|
||||||
|
del self.form
|
||||||
|
del self.main_window
|
||||||
|
|
||||||
|
def test_constructor(self):
|
||||||
|
"""
|
||||||
|
Test that a SongMaintenanceForm is created successfully
|
||||||
|
"""
|
||||||
|
# GIVEN: A SongMaintenanceForm
|
||||||
|
# WHEN: The form is created
|
||||||
|
# THEN: It should have some of the right components
|
||||||
|
assert self.form is not None
|
||||||
|
assert self.form.manager is self.mocked_manager
|
||||||
|
|
||||||
|
@patch.object(QtWidgets.QDialog, 'exec')
|
||||||
|
def test_exect(self, mocked_exec):
|
||||||
|
"""
|
||||||
|
Test the song maintenance form being executed
|
||||||
|
"""
|
||||||
|
# GIVEN: A song maintenance form
|
||||||
|
mocked_exec.return_value = True
|
||||||
|
|
||||||
|
# WHEN: The song mainetnance form is executed
|
||||||
|
with patch.object(self.form, 'type_list_widget') as mocked_type_list_widget, \
|
||||||
|
patch.object(self.form, 'reset_authors') as mocked_reset_authors, \
|
||||||
|
patch.object(self.form, 'reset_topics') as mocked_reset_topics, \
|
||||||
|
patch.object(self.form, 'reset_song_books') as mocked_reset_song_books:
|
||||||
|
result = self.form.exec(from_song_edit=True)
|
||||||
|
|
||||||
|
# THEN: The correct methods should have been called
|
||||||
|
assert self.form.from_song_edit is True
|
||||||
|
mocked_type_list_widget.setCurrentRow.assert_called_once_with(0)
|
||||||
|
mocked_reset_authors.assert_called_once_with()
|
||||||
|
mocked_reset_topics.assert_called_once_with()
|
||||||
|
mocked_reset_song_books.assert_called_once_with()
|
||||||
|
mocked_type_list_widget.setFocus.assert_called_once_with()
|
||||||
|
mocked_exec.assert_called_once_with(self.form)
|
||||||
|
|
||||||
|
def test_get_current_item_id_no_item(self):
|
||||||
|
"""
|
||||||
|
Test _get_current_item_id() when there's no item
|
||||||
|
"""
|
||||||
|
# GIVEN: A song maintenance form without a selected item
|
||||||
|
mocked_list_widget = MagicMock()
|
||||||
|
mocked_list_widget.currentItem.return_value = None
|
||||||
|
|
||||||
|
# WHEN: _get_current_item_id() is called
|
||||||
|
result = self.form._get_current_item_id(mocked_list_widget)
|
||||||
|
|
||||||
|
# THEN: The result should be -1
|
||||||
|
mocked_list_widget.currentItem.assert_called_once_with()
|
||||||
|
assert result == -1
|
||||||
|
|
||||||
|
def test_get_current_item_id(self):
|
||||||
|
"""
|
||||||
|
Test _get_current_item_id() when there's a valid item
|
||||||
|
"""
|
||||||
|
# GIVEN: A song maintenance form with a selected item
|
||||||
|
mocked_item = MagicMock()
|
||||||
|
mocked_item.data.return_value = 7
|
||||||
|
mocked_list_widget = MagicMock()
|
||||||
|
mocked_list_widget.currentItem.return_value = mocked_item
|
||||||
|
|
||||||
|
# WHEN: _get_current_item_id() is called
|
||||||
|
result = self.form._get_current_item_id(mocked_list_widget)
|
||||||
|
|
||||||
|
# THEN: The result should be -1
|
||||||
|
mocked_list_widget.currentItem.assert_called_once_with()
|
||||||
|
mocked_item.data.assert_called_once_with(QtCore.Qt.UserRole)
|
||||||
|
assert result == 7
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.critical_error_message_box')
|
||||||
|
def test_delete_item_no_item_id(self, mocked_critical_error_message_box):
|
||||||
|
"""
|
||||||
|
Test the _delete_item() method when there is no item selected
|
||||||
|
"""
|
||||||
|
# GIVEN: Some mocked items
|
||||||
|
mocked_item_class = MagicMock()
|
||||||
|
mocked_list_widget = MagicMock()
|
||||||
|
mocked_reset_func = MagicMock()
|
||||||
|
dialog_title = 'Delete Item'
|
||||||
|
delete_text = 'Are you sure you want to delete this item?'
|
||||||
|
error_text = 'There was a problem deleting this item'
|
||||||
|
|
||||||
|
# WHEN: _delete_item() is called
|
||||||
|
with patch.object(self.form, '_get_current_item_id') as mocked_get_current_item_id:
|
||||||
|
mocked_get_current_item_id.return_value = -1
|
||||||
|
self.form._delete_item(mocked_item_class, mocked_list_widget, mocked_reset_func, dialog_title, delete_text,
|
||||||
|
error_text)
|
||||||
|
|
||||||
|
# THEN: The right things should have been called
|
||||||
|
mocked_get_current_item_id.assert_called_once_with(mocked_list_widget)
|
||||||
|
mocked_critical_error_message_box.assert_called_once_with(dialog_title, UiStrings().NISs)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.critical_error_message_box')
|
||||||
|
def test_delete_item_invalid_item(self, mocked_critical_error_message_box):
|
||||||
|
"""
|
||||||
|
Test the _delete_item() method when the item doesn't exist in the database
|
||||||
|
"""
|
||||||
|
# GIVEN: Some mocked items
|
||||||
|
self.mocked_manager.get_object.return_value = None
|
||||||
|
mocked_item_class = MagicMock()
|
||||||
|
mocked_list_widget = MagicMock()
|
||||||
|
mocked_reset_func = MagicMock()
|
||||||
|
dialog_title = 'Delete Item'
|
||||||
|
delete_text = 'Are you sure you want to delete this item?'
|
||||||
|
error_text = 'There was a problem deleting this item'
|
||||||
|
|
||||||
|
# WHEN: _delete_item() is called
|
||||||
|
with patch.object(self.form, '_get_current_item_id') as mocked_get_current_item_id:
|
||||||
|
mocked_get_current_item_id.return_value = 1
|
||||||
|
self.form._delete_item(mocked_item_class, mocked_list_widget, mocked_reset_func, dialog_title, delete_text,
|
||||||
|
error_text)
|
||||||
|
|
||||||
|
# THEN: The right things should have been called
|
||||||
|
mocked_get_current_item_id.assert_called_once_with(mocked_list_widget)
|
||||||
|
self.mocked_manager.get_object.assert_called_once_with(mocked_item_class, 1)
|
||||||
|
mocked_critical_error_message_box.assert_called_once_with(dialog_title, error_text)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.critical_error_message_box')
|
||||||
|
def test_delete_item(self, mocked_critical_error_message_box):
|
||||||
|
"""
|
||||||
|
Test the _delete_item() method
|
||||||
|
"""
|
||||||
|
# GIVEN: Some mocked items
|
||||||
|
mocked_item = MagicMock()
|
||||||
|
mocked_item.songs = []
|
||||||
|
mocked_item.id = 1
|
||||||
|
self.mocked_manager.get_object.return_value = mocked_item
|
||||||
|
mocked_critical_error_message_box.return_value = QtWidgets.QMessageBox.Yes
|
||||||
|
mocked_item_class = MagicMock()
|
||||||
|
mocked_list_widget = MagicMock()
|
||||||
|
mocked_reset_func = MagicMock()
|
||||||
|
dialog_title = 'Delete Item'
|
||||||
|
delete_text = 'Are you sure you want to delete this item?'
|
||||||
|
error_text = 'There was a problem deleting this item'
|
||||||
|
|
||||||
|
# WHEN: _delete_item() is called
|
||||||
|
with patch.object(self.form, '_get_current_item_id') as mocked_get_current_item_id:
|
||||||
|
mocked_get_current_item_id.return_value = 1
|
||||||
|
self.form._delete_item(mocked_item_class, mocked_list_widget, mocked_reset_func, dialog_title, delete_text,
|
||||||
|
error_text)
|
||||||
|
|
||||||
|
# THEN: The right things should have been called
|
||||||
|
mocked_get_current_item_id.assert_called_once_with(mocked_list_widget)
|
||||||
|
self.mocked_manager.get_object.assert_called_once_with(mocked_item_class, 1)
|
||||||
|
mocked_critical_error_message_box.assert_called_once_with(dialog_title, delete_text, self.form, True)
|
||||||
|
self.mocked_manager.delete_object(mocked_item_class, 1)
|
||||||
|
mocked_reset_func.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem')
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.Author')
|
||||||
|
def test_reset_authors(self, MockedAuthor, MockedQListWidgetItem):
|
||||||
|
"""
|
||||||
|
Test the reset_authors() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked authors_list_widget and a few other mocks
|
||||||
|
mocked_author1 = MagicMock()
|
||||||
|
mocked_author1.display_name = 'John Newton'
|
||||||
|
mocked_author1.id = 1
|
||||||
|
mocked_author2 = MagicMock()
|
||||||
|
mocked_author2.display_name = ''
|
||||||
|
mocked_author2.first_name = 'John'
|
||||||
|
mocked_author2.last_name = 'Wesley'
|
||||||
|
mocked_author2.id = 2
|
||||||
|
mocked_authors = [mocked_author1, mocked_author2]
|
||||||
|
mocked_author_item1 = MagicMock()
|
||||||
|
mocked_author_item2 = MagicMock()
|
||||||
|
MockedQListWidgetItem.side_effect = [mocked_author_item1, mocked_author_item2]
|
||||||
|
MockedAuthor.display_name = None
|
||||||
|
self.mocked_manager.get_all_objects.return_value = mocked_authors
|
||||||
|
|
||||||
|
# WHEN: reset_authors() is called
|
||||||
|
with patch.object(self.form, 'authors_list_widget') as mocked_authors_list_widget:
|
||||||
|
self.form.reset_authors()
|
||||||
|
|
||||||
|
# THEN: The authors list should be reset
|
||||||
|
expected_widget_item_calls = [call('John Wesley'), call('John Newton')]
|
||||||
|
mocked_authors_list_widget.clear.assert_called_once_with()
|
||||||
|
self.mocked_manager.get_all_objects.assert_called_once_with(MockedAuthor)
|
||||||
|
assert MockedQListWidgetItem.call_args_list == expected_widget_item_calls, MockedQListWidgetItem.call_args_list
|
||||||
|
mocked_author_item1.setData.assert_called_once_with(QtCore.Qt.UserRole, 2)
|
||||||
|
mocked_author_item2.setData.assert_called_once_with(QtCore.Qt.UserRole, 1)
|
||||||
|
mocked_authors_list_widget.addItem.call_args_list == [
|
||||||
|
call(mocked_author_item1), call(mocked_author_item2)]
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem')
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.Topic')
|
||||||
|
def test_reset_topics(self, MockedTopic, MockedQListWidgetItem):
|
||||||
|
"""
|
||||||
|
Test the reset_topics() method
|
||||||
|
"""
|
||||||
|
# GIVEN: Some mocked out objects and methods
|
||||||
|
MockedTopic.name = 'Grace'
|
||||||
|
mocked_topic = MagicMock()
|
||||||
|
mocked_topic.id = 1
|
||||||
|
mocked_topic.name = 'Grace'
|
||||||
|
self.mocked_manager.get_all_objects.return_value = [mocked_topic]
|
||||||
|
mocked_topic_item = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mocked_topic_item
|
||||||
|
|
||||||
|
# WHEN: reset_topics() is called
|
||||||
|
with patch.object(self.form, 'topics_list_widget') as mocked_topic_list_widget:
|
||||||
|
self.form.reset_topics()
|
||||||
|
|
||||||
|
# THEN: The topics list should be reset correctly
|
||||||
|
mocked_topic_list_widget.clear.assert_called_once_with()
|
||||||
|
self.mocked_manager.get_all_objects.assert_called_once_with(MockedTopic)
|
||||||
|
MockedQListWidgetItem.assert_called_once_with('Grace')
|
||||||
|
mocked_topic_item.setData.assert_called_once_with(QtCore.Qt.UserRole, 1)
|
||||||
|
mocked_topic_list_widget.addItem.assert_called_once_with(mocked_topic_item)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem')
|
||||||
|
@patch('openlp.plugins.songs.forms.songmaintenanceform.Book')
|
||||||
|
def test_reset_song_books(self, MockedBook, MockedQListWidgetItem):
|
||||||
|
"""
|
||||||
|
Test the reset_song_books() method
|
||||||
|
"""
|
||||||
|
# GIVEN: Some mocked out objects and methods
|
||||||
|
MockedBook.name = 'Hymnal'
|
||||||
|
mocked_song_book = MagicMock()
|
||||||
|
mocked_song_book.id = 1
|
||||||
|
mocked_song_book.name = 'Hymnal'
|
||||||
|
mocked_song_book.publisher = 'Hymns and Psalms, Inc.'
|
||||||
|
self.mocked_manager.get_all_objects.return_value = [mocked_song_book]
|
||||||
|
mocked_song_book_item = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mocked_song_book_item
|
||||||
|
|
||||||
|
# WHEN: reset_song_books() is called
|
||||||
|
with patch.object(self.form, 'song_books_list_widget') as mocked_song_book_list_widget:
|
||||||
|
self.form.reset_song_books()
|
||||||
|
|
||||||
|
# THEN: The song_books list should be reset correctly
|
||||||
|
mocked_song_book_list_widget.clear.assert_called_once_with()
|
||||||
|
self.mocked_manager.get_all_objects.assert_called_once_with(MockedBook)
|
||||||
|
MockedQListWidgetItem.assert_called_once_with('Hymnal (Hymns and Psalms, Inc.)')
|
||||||
|
mocked_song_book_item.setData.assert_called_once_with(QtCore.Qt.UserRole, 1)
|
||||||
|
mocked_song_book_list_widget.addItem.assert_called_once_with(mocked_song_book_item)
|
Loading…
Reference in New Issue
Block a user