forked from openlp/openlp
Add this to your merge proposal:
-------------------------------- lp:~raoul-snyman/openlp/db-upgrades-2.4 (revision 2681) [SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1929/ [SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1840/ [SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1781/ [SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1511/ [SUCCESS] https://ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/1101/ [SUCCESS] https://ci.openlp.io/... bzr-revno: 2676
This commit is contained in:
commit
c4162fdf1e
8
CHANGELOG.rst
Normal file
8
CHANGELOG.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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
|
||||||
|
* Add another upgrade step to remove erroneous songs_songbooks entries due to the previous bug
|
||||||
|
* Added importing of author types to the OpenLP 2 song importer
|
@ -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 table
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
class OldAuthor(BaseModel):
|
class OldAuthor(BaseModel):
|
||||||
"""
|
"""
|
||||||
Maps to the authors table
|
Maps to the authors table
|
||||||
@ -117,6 +123,10 @@ class OpenLPSongImport(SongImport):
|
|||||||
has_songs_books = True
|
has_songs_books = True
|
||||||
else:
|
else:
|
||||||
has_songs_books = False
|
has_songs_books = False
|
||||||
|
if 'authors_songs' in list(source_meta.tables.keys()):
|
||||||
|
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 +149,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 +171,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 +190,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(OldAuthorSong)
|
||||||
|
except UnmappedClassError:
|
||||||
|
mapper(OldAuthorSong, source_authors_songs_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 +226,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:
|
||||||
|
@ -26,13 +26,13 @@ backend for the Songs plugin
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, ForeignKey, types
|
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, and_, select
|
||||||
|
|
||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
from openlp.core.utils.db import drop_columns
|
from openlp.core.utils.db import drop_columns
|
||||||
|
|
||||||
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
|
||||||
@ -105,13 +105,15 @@ def upgrade_4(session, metadata):
|
|||||||
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
||||||
# and copy the old values
|
# and copy the old values
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
songs_table = Table('songs', metadata)
|
authors_songs = Table('authors_songs', metadata, autoload=True)
|
||||||
if 'author_type' not in [col.name for col in songs_table.c.values()]:
|
if 'author_type' not in [col.name for col in authors_songs.c.values()]:
|
||||||
op.create_table('authors_songs_tmp',
|
authors_songs_tmp = op.create_table(
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
'authors_songs_tmp',
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
Column('author_type', types.Unicode(255), primary_key=True,
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
nullable=False, server_default=text('""')))
|
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.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
||||||
op.drop_table('authors_songs')
|
op.drop_table('authors_songs')
|
||||||
op.rename_table('authors_songs_tmp', 'authors_songs')
|
op.rename_table('authors_songs_tmp', 'authors_songs')
|
||||||
@ -126,20 +128,22 @@ def upgrade_5(session, metadata):
|
|||||||
This upgrade adds support for multiple songbooks
|
This upgrade adds support for multiple songbooks
|
||||||
"""
|
"""
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
songs_table = Table('songs', metadata)
|
songs_table = Table('songs', metadata, autoload=True)
|
||||||
if 'song_book_id' in [col.name for col in songs_table.c.values()]:
|
if 'song_book_id' not in [col.name for col in songs_table.c.values()]:
|
||||||
log.warning('Skipping upgrade_5 step of upgrading the song db')
|
log.warning('Skipping upgrade_5 step of upgrading the song db')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create the mapping table (songs <-> songbooks)
|
# Create the mapping table (songs <-> songbooks)
|
||||||
op.create_table('songs_songbooks',
|
songs_songbooks_table = op.create_table(
|
||||||
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
|
'songs_songbooks',
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
|
||||||
Column('entry', types.Unicode(255), primary_key=True, nullable=False))
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
|
Column('entry', types.Unicode(255), primary_key=True, nullable=False)
|
||||||
|
)
|
||||||
|
|
||||||
# Migrate old data
|
# Migrate old data
|
||||||
op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\
|
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')
|
WHERE song_book_id IS NOT NULL AND song_book_id <> 0 AND song_number IS NOT NULL')
|
||||||
|
|
||||||
# Drop old columns
|
# Drop old columns
|
||||||
if metadata.bind.url.get_dialect().name == 'sqlite':
|
if metadata.bind.url.get_dialect().name == 'sqlite':
|
||||||
@ -148,3 +152,15 @@ def upgrade_5(session, metadata):
|
|||||||
op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
|
op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
|
||||||
op.drop_column('songs', 'song_book_id')
|
op.drop_column('songs', 'song_book_id')
|
||||||
op.drop_column('songs', 'song_number')
|
op.drop_column('songs', 'song_number')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_6(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 6 upgrade.
|
||||||
|
|
||||||
|
This is to fix an issue we had with songbooks with an id of "0" being imported in the previous upgrade.
|
||||||
|
"""
|
||||||
|
op = get_upgrade_op(session)
|
||||||
|
songs_songbooks = Table('songs_songbooks', metadata, autoload=True)
|
||||||
|
del_query = songs_songbooks.delete().where(songs_songbooks.c.songbook_id == 0)
|
||||||
|
op.execute(del_query)
|
||||||
|
@ -23,13 +23,13 @@
|
|||||||
Package to test the openlp.plugins.songs.forms.editsongform package.
|
Package to test the openlp.plugins.songs.forms.editsongform package.
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
from openlp.core.common.uistrings import UiStrings
|
from openlp.core.common.uistrings import UiStrings
|
||||||
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
||||||
from tests.interfaces import MagicMock
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
|
||||||
@ -157,3 +157,50 @@ class TestEditSongForm(TestCase, TestMixin):
|
|||||||
|
|
||||||
# THEN: The verse order should be converted to uppercase
|
# THEN: The verse order should be converted to uppercase
|
||||||
self.assertEqual(form.verse_order_edit.text(), 'V1 V2 C1 V3 C1 V4 C1')
|
self.assertEqual(form.verse_order_edit.text(), 'V1 V2 C1 V3 C1 V4 C1')
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.editsongform.QtWidgets.QListWidgetItem')
|
||||||
|
def test_add_author_to_list(self, MockedQListWidgetItem):
|
||||||
|
"""
|
||||||
|
Test the _add_author_to_list() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A song edit form and some mocked stuff
|
||||||
|
mocked_author = MagicMock()
|
||||||
|
mocked_author.id = 1
|
||||||
|
mocked_author.get_display_name.return_value = 'John Newton'
|
||||||
|
mocked_author_type = 'words'
|
||||||
|
mocked_widget_item = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mocked_widget_item
|
||||||
|
|
||||||
|
# WHEN: _add_author_to_list() is called
|
||||||
|
with patch.object(self.form.authors_list_view, 'addItem') as mocked_add_item:
|
||||||
|
self.form._add_author_to_list(mocked_author, mocked_author_type)
|
||||||
|
|
||||||
|
# THEN: All the correct methods should have been called
|
||||||
|
mocked_author.get_display_name.assert_called_once_with('words')
|
||||||
|
MockedQListWidgetItem.assert_called_once_with('John Newton')
|
||||||
|
mocked_widget_item.setData.assert_called_once_with(QtCore.Qt.UserRole, (1, mocked_author_type))
|
||||||
|
mocked_add_item.assert_called_once_with(mocked_widget_item)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.forms.editsongform.SongBookEntry')
|
||||||
|
@patch('openlp.plugins.songs.forms.editsongform.QtWidgets.QListWidgetItem')
|
||||||
|
def test_add_songbook_entry_to_list(self, MockedQListWidgetItem, MockedSongbookEntry):
|
||||||
|
"""
|
||||||
|
Test the add_songbook_entry_to_list() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A song edit form and some mocked stuff
|
||||||
|
songbook_id = 1
|
||||||
|
songbook_name = 'Hymnal'
|
||||||
|
entry = '546'
|
||||||
|
MockedSongbookEntry.get_display_name.return_value = 'Hymnal #546'
|
||||||
|
mocked_widget_item = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mocked_widget_item
|
||||||
|
|
||||||
|
# WHEN: _add_author_to_list() is called
|
||||||
|
with patch.object(self.form.songbooks_list_view, 'addItem') as mocked_add_item:
|
||||||
|
self.form.add_songbook_entry_to_list(songbook_id, songbook_name, entry)
|
||||||
|
|
||||||
|
# THEN: All the correct methods should have been called
|
||||||
|
MockedSongbookEntry.get_display_name.assert_called_once_with(songbook_name, entry)
|
||||||
|
MockedQListWidgetItem.assert_called_once_with('Hymnal #546')
|
||||||
|
mocked_widget_item.setData.assert_called_once_with(QtCore.Qt.UserRole, (songbook_id, entry))
|
||||||
|
mocked_add_item.assert_called_once_with(mocked_widget_item)
|
||||||
|
Loading…
Reference in New Issue
Block a user