Merge lp:~raoul-snyman/openlp/db-upgrades-2.4 into lp:openlp/2.4

Proposed by Raoul Snyman
Status: Merged
Merged at revision: 2676
Proposed branch: lp:~raoul-snyman/openlp/db-upgrades-2.4
Merge into: lp:openlp/2.4
Diff against target: 251 lines (+120/-20)
4 files modified
CHANGELOG.rst (+8/-0)
openlp/plugins/songs/lib/importers/openlp.py (+31/-2)
openlp/plugins/songs/lib/upgrade.py (+32/-16)
tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py (+49/-2)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/db-upgrades-2.4
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Review via email: mp+319385@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'CHANGELOG.rst'
--- CHANGELOG.rst 1970-01-01 00:00:00 +0000
+++ CHANGELOG.rst 2017-03-09 05:46:09 +0000
@@ -0,0 +1,8 @@
1OpenLP 2.4.6
2============
3
4* Fixed a bug where the author type upgrade was being ignore because it was looking at the wrong table
5* Fixed a bug where the songs_songbooks table was not being created because the if expression was the wrong way round
6* Changed the songs_songbooks migration SQL slightly to take into account a bug that has (hopefully) been fixed
7* Add another upgrade step to remove erroneous songs_songbooks entries due to the previous bug
8* Added importing of author types to the OpenLP 2 song importer
09
=== modified file 'openlp/plugins/songs/lib/importers/openlp.py'
--- openlp/plugins/songs/lib/importers/openlp.py 2017-03-01 18:24:27 +0000
+++ openlp/plugins/songs/lib/importers/openlp.py 2017-03-09 05:46:09 +0000
@@ -61,6 +61,12 @@
61 :param progress_dialog: The QProgressDialog used when importing songs from the FRW.61 :param progress_dialog: The QProgressDialog used when importing songs from the FRW.
62 """62 """
6363
64 class OldAuthorSong(BaseModel):
65 """
66 Maps to the authors table
67 """
68 pass
69
64 class OldAuthor(BaseModel):70 class OldAuthor(BaseModel):
65 """71 """
66 Maps to the authors table72 Maps to the authors table
@@ -117,6 +123,10 @@
117 has_songs_books = True123 has_songs_books = True
118 else:124 else:
119 has_songs_books = False125 has_songs_books = False
126 if 'authors_songs' in list(source_meta.tables.keys()):
127 has_authors_songs = True
128 else:
129 has_authors_songs = False
120 # Load up the tabls and map them out130 # Load up the tabls and map them out
121 source_authors_table = source_meta.tables['authors']131 source_authors_table = source_meta.tables['authors']
122 source_song_books_table = source_meta.tables['song_books']132 source_song_books_table = source_meta.tables['song_books']
@@ -139,6 +149,10 @@
139 class_mapper(OldSongBookEntry)149 class_mapper(OldSongBookEntry)
140 except UnmappedClassError:150 except UnmappedClassError:
141 mapper(OldSongBookEntry, source_songs_songbooks_table, properties={'songbook': relation(OldBook)})151 mapper(OldSongBookEntry, source_songs_songbooks_table, properties={'songbook': relation(OldBook)})
152 if has_authors_songs and 'author_type' in source_authors_songs_table.c.values():
153 has_author_type = True
154 else:
155 has_author_type = False
142 # Set up the songs relationships156 # Set up the songs relationships
143 song_props = {157 song_props = {
144 'authors': relation(OldAuthor, backref='songs', secondary=source_authors_songs_table),158 'authors': relation(OldAuthor, backref='songs', secondary=source_authors_songs_table),
@@ -157,6 +171,8 @@
157 song_props['songbook_entries'] = relation(OldSongBookEntry, backref='song', cascade='all, delete-orphan')171 song_props['songbook_entries'] = relation(OldSongBookEntry, backref='song', cascade='all, delete-orphan')
158 else:172 else:
159 song_props['book'] = relation(OldBook, backref='songs')173 song_props['book'] = relation(OldBook, backref='songs')
174 if has_authors_songs:
175 song_props['authors_songs'] = relation(OldAuthorSong)
160 # Map the rest of the tables176 # Map the rest of the tables
161 try:177 try:
162 class_mapper(OldAuthor)178 class_mapper(OldAuthor)
@@ -174,6 +190,11 @@
174 class_mapper(OldTopic)190 class_mapper(OldTopic)
175 except UnmappedClassError:191 except UnmappedClassError:
176 mapper(OldTopic, source_topics_table)192 mapper(OldTopic, source_topics_table)
193 if has_authors_songs:
194 try:
195 class_mapper(OldAuthorSong)
196 except UnmappedClassError:
197 mapper(OldAuthorSong, source_authors_songs_table)
177198
178 source_songs = self.source_session.query(OldSong).all()199 source_songs = self.source_session.query(OldSong).all()
179 if self.import_wizard:200 if self.import_wizard:
@@ -205,8 +226,16 @@
205 existing_author = Author.populate(226 existing_author = Author.populate(
206 first_name=author.first_name,227 first_name=author.first_name,
207 last_name=author.last_name,228 last_name=author.last_name,
208 display_name=author.display_name)229 display_name=author.display_name
209 new_song.add_author(existing_author)230 )
231 # if this is a new database, we need to import the author type too
232 author_type = None
233 if has_author_type:
234 for author_song in song.authors_songs:
235 if author_song.author_id == author.id:
236 author_type = author_song.author_type
237 break
238 new_song.add_author(existing_author, author_type)
210 # Find or create all the topics and add them to the new song object239 # Find or create all the topics and add them to the new song object
211 if song.topics:240 if song.topics:
212 for topic in song.topics:241 for topic in song.topics:
213242
=== modified file 'openlp/plugins/songs/lib/upgrade.py'
--- openlp/plugins/songs/lib/upgrade.py 2016-12-31 11:05:48 +0000
+++ openlp/plugins/songs/lib/upgrade.py 2017-03-09 05:46:09 +0000
@@ -26,13 +26,13 @@
26import logging26import logging
2727
28from sqlalchemy import Table, Column, ForeignKey, types28from sqlalchemy import Table, Column, ForeignKey, types
29from sqlalchemy.sql.expression import func, false, null, text29from sqlalchemy.sql.expression import func, false, null, text, and_, select
3030
31from openlp.core.lib.db import get_upgrade_op31from openlp.core.lib.db import get_upgrade_op
32from openlp.core.utils.db import drop_columns32from openlp.core.utils.db import drop_columns
3333
34log = logging.getLogger(__name__)34log = logging.getLogger(__name__)
35__version__ = 535__version__ = 6
3636
3737
38# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version38# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@@ -105,13 +105,15 @@
105 # Since SQLite doesn't support changing the primary key of a table, we need to recreate the table105 # Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
106 # and copy the old values106 # and copy the old values
107 op = get_upgrade_op(session)107 op = get_upgrade_op(session)
108 songs_table = Table('songs', metadata)108 authors_songs = Table('authors_songs', metadata, autoload=True)
109 if 'author_type' not in [col.name for col in songs_table.c.values()]:109 if 'author_type' not in [col.name for col in authors_songs.c.values()]:
110 op.create_table('authors_songs_tmp',110 authors_songs_tmp = op.create_table(
111 Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),111 'authors_songs_tmp',
112 Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),112 Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
113 Column('author_type', types.Unicode(255), primary_key=True,113 Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
114 nullable=False, server_default=text('""')))114 Column('author_type', types.Unicode(255), primary_key=True,
115 nullable=False, server_default=text('""'))
116 )
115 op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')117 op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
116 op.drop_table('authors_songs')118 op.drop_table('authors_songs')
117 op.rename_table('authors_songs_tmp', 'authors_songs')119 op.rename_table('authors_songs_tmp', 'authors_songs')
@@ -126,20 +128,22 @@
126 This upgrade adds support for multiple songbooks128 This upgrade adds support for multiple songbooks
127 """129 """
128 op = get_upgrade_op(session)130 op = get_upgrade_op(session)
129 songs_table = Table('songs', metadata)131 songs_table = Table('songs', metadata, autoload=True)
130 if 'song_book_id' in [col.name for col in songs_table.c.values()]:132 if 'song_book_id' not in [col.name for col in songs_table.c.values()]:
131 log.warning('Skipping upgrade_5 step of upgrading the song db')133 log.warning('Skipping upgrade_5 step of upgrading the song db')
132 return134 return
133135
134 # Create the mapping table (songs <-> songbooks)136 # Create the mapping table (songs <-> songbooks)
135 op.create_table('songs_songbooks',137 songs_songbooks_table = op.create_table(
136 Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),138 'songs_songbooks',
137 Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),139 Column('songbook_id', types.Integer(), ForeignKey('song_books.id'), primary_key=True),
138 Column('entry', types.Unicode(255), primary_key=True, nullable=False))140 Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
141 Column('entry', types.Unicode(255), primary_key=True, nullable=False)
142 )
139143
140 # Migrate old data144 # Migrate old data
141 op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\145 op.execute('INSERT INTO songs_songbooks SELECT song_book_id, id, song_number FROM songs\
142 WHERE song_book_id IS NOT NULL AND song_number IS NOT NULL')146 WHERE song_book_id IS NOT NULL AND song_book_id <> 0 AND song_number IS NOT NULL')
143147
144 # Drop old columns148 # Drop old columns
145 if metadata.bind.url.get_dialect().name == 'sqlite':149 if metadata.bind.url.get_dialect().name == 'sqlite':
@@ -148,3 +152,15 @@
148 op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')152 op.drop_constraint('songs_ibfk_1', 'songs', 'foreignkey')
149 op.drop_column('songs', 'song_book_id')153 op.drop_column('songs', 'song_book_id')
150 op.drop_column('songs', 'song_number')154 op.drop_column('songs', 'song_number')
155
156
157def upgrade_6(session, metadata):
158 """
159 Version 6 upgrade.
160
161 This is to fix an issue we had with songbooks with an id of "0" being imported in the previous upgrade.
162 """
163 op = get_upgrade_op(session)
164 songs_songbooks = Table('songs_songbooks', metadata, autoload=True)
165 del_query = songs_songbooks.delete().where(songs_songbooks.c.songbook_id == 0)
166 op.execute(del_query)
151167
=== modified file 'tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py'
--- tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py 2016-12-31 11:05:48 +0000
+++ tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py 2017-03-09 05:46:09 +0000
@@ -23,13 +23,13 @@
23Package to test the openlp.plugins.songs.forms.editsongform package.23Package to test the openlp.plugins.songs.forms.editsongform package.
24"""24"""
25from unittest import TestCase25from unittest import TestCase
26from unittest.mock import MagicMock, patch
2627
27from PyQt5 import QtWidgets28from PyQt5 import QtCore, QtWidgets
2829
29from openlp.core.common import Registry30from openlp.core.common import Registry
30from openlp.core.common.uistrings import UiStrings31from openlp.core.common.uistrings import UiStrings
31from openlp.plugins.songs.forms.editsongform import EditSongForm32from openlp.plugins.songs.forms.editsongform import EditSongForm
32from tests.interfaces import MagicMock
33from tests.helpers.testmixin import TestMixin33from tests.helpers.testmixin import TestMixin
3434
3535
@@ -157,3 +157,50 @@
157157
158 # THEN: The verse order should be converted to uppercase158 # THEN: The verse order should be converted to uppercase
159 self.assertEqual(form.verse_order_edit.text(), 'V1 V2 C1 V3 C1 V4 C1')159 self.assertEqual(form.verse_order_edit.text(), 'V1 V2 C1 V3 C1 V4 C1')
160
161 @patch('openlp.plugins.songs.forms.editsongform.QtWidgets.QListWidgetItem')
162 def test_add_author_to_list(self, MockedQListWidgetItem):
163 """
164 Test the _add_author_to_list() method
165 """
166 # GIVEN: A song edit form and some mocked stuff
167 mocked_author = MagicMock()
168 mocked_author.id = 1
169 mocked_author.get_display_name.return_value = 'John Newton'
170 mocked_author_type = 'words'
171 mocked_widget_item = MagicMock()
172 MockedQListWidgetItem.return_value = mocked_widget_item
173
174 # WHEN: _add_author_to_list() is called
175 with patch.object(self.form.authors_list_view, 'addItem') as mocked_add_item:
176 self.form._add_author_to_list(mocked_author, mocked_author_type)
177
178 # THEN: All the correct methods should have been called
179 mocked_author.get_display_name.assert_called_once_with('words')
180 MockedQListWidgetItem.assert_called_once_with('John Newton')
181 mocked_widget_item.setData.assert_called_once_with(QtCore.Qt.UserRole, (1, mocked_author_type))
182 mocked_add_item.assert_called_once_with(mocked_widget_item)
183
184 @patch('openlp.plugins.songs.forms.editsongform.SongBookEntry')
185 @patch('openlp.plugins.songs.forms.editsongform.QtWidgets.QListWidgetItem')
186 def test_add_songbook_entry_to_list(self, MockedQListWidgetItem, MockedSongbookEntry):
187 """
188 Test the add_songbook_entry_to_list() method
189 """
190 # GIVEN: A song edit form and some mocked stuff
191 songbook_id = 1
192 songbook_name = 'Hymnal'
193 entry = '546'
194 MockedSongbookEntry.get_display_name.return_value = 'Hymnal #546'
195 mocked_widget_item = MagicMock()
196 MockedQListWidgetItem.return_value = mocked_widget_item
197
198 # WHEN: _add_author_to_list() is called
199 with patch.object(self.form.songbooks_list_view, 'addItem') as mocked_add_item:
200 self.form.add_songbook_entry_to_list(songbook_id, songbook_name, entry)
201
202 # THEN: All the correct methods should have been called
203 MockedSongbookEntry.get_display_name.assert_called_once_with(songbook_name, entry)
204 MockedQListWidgetItem.assert_called_once_with('Hymnal #546')
205 mocked_widget_item.setData.assert_called_once_with(QtCore.Qt.UserRole, (songbook_id, entry))
206 mocked_add_item.assert_called_once_with(mocked_widget_item)

Subscribers

People subscribed via source and target branches

to all changes: