Merge lp:~sam92/openlp/bug-1552563 into lp:openlp

Proposed by Samuel Mehrbrodt
Status: Merged
Approved by: Tim Bentley
Approved revision: 2659
Merged at revision: 2655
Proposed branch: lp:~sam92/openlp/bug-1552563
Merge into: lp:openlp
Diff against target: 144 lines (+39/-37)
3 files modified
openlp/plugins/songs/lib/mediaitem.py (+10/-12)
tests/functional/openlp_plugins/songs/test_mediaitem.py (+29/-24)
tests/functional/openlp_plugins/songs/test_openlpimporter.py (+0/-1)
To merge this branch: bzr merge lp:~sam92/openlp/bug-1552563
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Raoul Snyman Approve
Review via email: mp+293427@code.launchpad.net

This proposal supersedes a proposal from 2016-04-29.

Description of the change

Fix performance regression with Songbook search

The problem was that in each iteration the database was accessed (the song object).
Fixed this by loading all neccessary information directly in the query.

To post a comment you must log in.
Revision history for this message
Samuel Mehrbrodt (sam92) wrote : Posted in a previous version of this proposal
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
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
=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py 2016-04-25 11:01:32 +0000
+++ openlp/plugins/songs/lib/mediaitem.py 2016-04-29 17:34:59 +0000
@@ -21,7 +21,6 @@
21###############################################################################21###############################################################################
2222
23import logging23import logging
24import re
25import os24import os
26import shutil25import shutil
2726
@@ -207,9 +206,11 @@
207 search_keywords = search_keywords.rpartition(' ')206 search_keywords = search_keywords.rpartition(' ')
208 search_book = search_keywords[0] + '%'207 search_book = search_keywords[0] + '%'
209 search_entry = search_keywords[2] + '%'208 search_entry = search_keywords[2] + '%'
210 search_results = (self.plugin.manager.session.query(SongBookEntry)209 search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id)
210 .join(Song)
211 .join(Book)211 .join(Book)
212 .filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry)).all())212 .filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry),
213 Song.temporary.is_(False)).all())
213 self.display_results_book(search_results)214 self.display_results_book(search_results)
214 elif search_type == SongSearch.Themes:215 elif search_type == SongSearch.Themes:
215 log.debug('Theme Search')216 log.debug('Theme Search')
@@ -313,23 +314,20 @@
313 """314 """
314 Display the song search results in the media manager list, grouped by book and entry315 Display the song search results in the media manager list, grouped by book and entry
315316
316 :param search_results: A list of db SongBookEntry objects317 :param search_results: A tuple containing (songbook entry, book name, song title, song id)
317 :return: None318 :return: None
318 """319 """
319 def get_songbook_key(songbook_entry):320 def get_songbook_key(result):
320 """Get the key to sort by"""321 """Get the key to sort by"""
321 return (get_natural_key(songbook_entry.songbook.name), get_natural_key(songbook_entry.entry))322 return (get_natural_key(result[1]), get_natural_key(result[0]), get_natural_key(result[2]))
322323
323 log.debug('display results Book')324 log.debug('display results Book')
324 self.list_view.clear()325 self.list_view.clear()
325 search_results.sort(key=get_songbook_key)326 search_results.sort(key=get_songbook_key)
326 for songbook_entry in search_results:327 for result in search_results:
327 # Do not display temporary songs328 song_detail = '%s #%s: %s' % (result[1], result[0], result[2])
328 if songbook_entry.song.temporary:
329 continue
330 song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
331 song_name = QtWidgets.QListWidgetItem(song_detail)329 song_name = QtWidgets.QListWidgetItem(song_detail)
332 song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)330 song_name.setData(QtCore.Qt.UserRole, result[3])
333 self.list_view.addItem(song_name)331 self.list_view.addItem(song_name)
334332
335 def display_results_topic(self, search_results):333 def display_results_topic(self, search_results):
336334
=== modified file 'tests/functional/openlp_plugins/songs/test_mediaitem.py'
--- tests/functional/openlp_plugins/songs/test_mediaitem.py 2016-04-17 21:22:30 +0000
+++ tests/functional/openlp_plugins/songs/test_mediaitem.py 2016-04-29 17:34:59 +0000
@@ -23,6 +23,7 @@
23This module contains tests for the lib submodule of the Songs plugin.23This module contains tests for the lib submodule of the Songs plugin.
24"""24"""
25from unittest import TestCase25from unittest import TestCase
26from unittest.mock import call
2627
27from PyQt5 import QtCore28from PyQt5 import QtCore
2829
@@ -151,29 +152,7 @@
151 # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem152 # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
152 with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \153 with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
153 patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:154 patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
154 mock_search_results = []155 mock_search_results = [('1', 'My Book', 'My Song', 1)]
155 mock_songbook_entry = MagicMock()
156 mock_songbook_entry_temp = MagicMock()
157 mock_songbook = MagicMock()
158 mock_song = MagicMock()
159 mock_song_temp = MagicMock()
160 mock_songbook_entry.entry = '1'
161 mock_songbook_entry_temp.entry = '2'
162 mock_songbook.name = 'My Book'
163 mock_song.id = 1
164 mock_song.title = 'My Song'
165 mock_song.sort_key = 'My Song'
166 mock_song.temporary = False
167 mock_song_temp.id = 2
168 mock_song_temp.title = 'My Temporary'
169 mock_song_temp.sort_key = 'My Temporary'
170 mock_song_temp.temporary = True
171 mock_songbook_entry.song = mock_song
172 mock_songbook_entry.songbook = mock_songbook
173 mock_songbook_entry_temp.song = mock_song_temp
174 mock_songbook_entry_temp.songbook = mock_songbook
175 mock_search_results.append(mock_songbook_entry)
176 mock_search_results.append(mock_songbook_entry_temp)
177 mock_qlist_widget = MagicMock()156 mock_qlist_widget = MagicMock()
178 MockedQListWidgetItem.return_value = mock_qlist_widget157 MockedQListWidgetItem.return_value = mock_qlist_widget
179158
@@ -183,9 +162,35 @@
183 # THEN: The current list view is cleared, the widget is created, and the relevant attributes set162 # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
184 self.media_item.list_view.clear.assert_called_with()163 self.media_item.list_view.clear.assert_called_with()
185 MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')164 MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
186 mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_songbook_entry.song.id)165 mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
187 self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)166 self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
188167
168 def songbook_natural_sorting_test(self):
169 """
170 Test that songbooks are sorted naturally
171 """
172 # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
173 with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem:
174 mock_search_results = [('2', 'Thy Book', 'Thy Song', 50),
175 ('2', 'My Book', 'Your Song', 7),
176 ('10', 'My Book', 'Our Song', 12),
177 ('1', 'My Book', 'My Song', 1),
178 ('2', 'Thy Book', 'A Song', 8)]
179 mock_qlist_widget = MagicMock()
180 MockedQListWidgetItem.return_value = mock_qlist_widget
181
182 # WHEN: I display song search results grouped by book
183 self.media_item.display_results_book(mock_search_results)
184
185 # THEN: The songbooks are inserted in the right (natural) order,
186 # grouped first by book, then by number, then by song title
187 calls = [call('My Book #1: My Song'), call().setData(QtCore.Qt.UserRole, 1),
188 call('My Book #2: Your Song'), call().setData(QtCore.Qt.UserRole, 7),
189 call('My Book #10: Our Song'), call().setData(QtCore.Qt.UserRole, 12),
190 call('Thy Book #2: A Song'), call().setData(QtCore.Qt.UserRole, 8),
191 call('Thy Book #2: Thy Song'), call().setData(QtCore.Qt.UserRole, 50)]
192 MockedQListWidgetItem.assert_has_calls(calls)
193
189 def display_results_topic_test(self):194 def display_results_topic_test(self):
190 """195 """
191 Test displaying song search results grouped by topic with basic song196 Test displaying song search results grouped by topic with basic song
192197
=== modified file 'tests/functional/openlp_plugins/songs/test_openlpimporter.py'
--- tests/functional/openlp_plugins/songs/test_openlpimporter.py 2016-04-27 21:23:16 +0000
+++ tests/functional/openlp_plugins/songs/test_openlpimporter.py 2016-04-29 17:34:59 +0000
@@ -73,4 +73,3 @@
73 self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')73 self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
74 self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,74 self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,
75 'setMaximum on import_wizard.progress_bar should not have been called')75 'setMaximum on import_wizard.progress_bar should not have been called')
76