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
1=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
2--- openlp/plugins/songs/lib/mediaitem.py 2016-04-25 11:01:32 +0000
3+++ openlp/plugins/songs/lib/mediaitem.py 2016-04-29 17:34:59 +0000
4@@ -21,7 +21,6 @@
5 ###############################################################################
6
7 import logging
8-import re
9 import os
10 import shutil
11
12@@ -207,9 +206,11 @@
13 search_keywords = search_keywords.rpartition(' ')
14 search_book = search_keywords[0] + '%'
15 search_entry = search_keywords[2] + '%'
16- search_results = (self.plugin.manager.session.query(SongBookEntry)
17+ search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id)
18+ .join(Song)
19 .join(Book)
20- .filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry)).all())
21+ .filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry),
22+ Song.temporary.is_(False)).all())
23 self.display_results_book(search_results)
24 elif search_type == SongSearch.Themes:
25 log.debug('Theme Search')
26@@ -313,23 +314,20 @@
27 """
28 Display the song search results in the media manager list, grouped by book and entry
29
30- :param search_results: A list of db SongBookEntry objects
31+ :param search_results: A tuple containing (songbook entry, book name, song title, song id)
32 :return: None
33 """
34- def get_songbook_key(songbook_entry):
35+ def get_songbook_key(result):
36 """Get the key to sort by"""
37- return (get_natural_key(songbook_entry.songbook.name), get_natural_key(songbook_entry.entry))
38+ return (get_natural_key(result[1]), get_natural_key(result[0]), get_natural_key(result[2]))
39
40 log.debug('display results Book')
41 self.list_view.clear()
42 search_results.sort(key=get_songbook_key)
43- for songbook_entry in search_results:
44- # Do not display temporary songs
45- if songbook_entry.song.temporary:
46- continue
47- song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
48+ for result in search_results:
49+ song_detail = '%s #%s: %s' % (result[1], result[0], result[2])
50 song_name = QtWidgets.QListWidgetItem(song_detail)
51- song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
52+ song_name.setData(QtCore.Qt.UserRole, result[3])
53 self.list_view.addItem(song_name)
54
55 def display_results_topic(self, search_results):
56
57=== modified file 'tests/functional/openlp_plugins/songs/test_mediaitem.py'
58--- tests/functional/openlp_plugins/songs/test_mediaitem.py 2016-04-17 21:22:30 +0000
59+++ tests/functional/openlp_plugins/songs/test_mediaitem.py 2016-04-29 17:34:59 +0000
60@@ -23,6 +23,7 @@
61 This module contains tests for the lib submodule of the Songs plugin.
62 """
63 from unittest import TestCase
64+from unittest.mock import call
65
66 from PyQt5 import QtCore
67
68@@ -151,29 +152,7 @@
69 # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
70 with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
71 patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
72- mock_search_results = []
73- mock_songbook_entry = MagicMock()
74- mock_songbook_entry_temp = MagicMock()
75- mock_songbook = MagicMock()
76- mock_song = MagicMock()
77- mock_song_temp = MagicMock()
78- mock_songbook_entry.entry = '1'
79- mock_songbook_entry_temp.entry = '2'
80- mock_songbook.name = 'My Book'
81- mock_song.id = 1
82- mock_song.title = 'My Song'
83- mock_song.sort_key = 'My Song'
84- mock_song.temporary = False
85- mock_song_temp.id = 2
86- mock_song_temp.title = 'My Temporary'
87- mock_song_temp.sort_key = 'My Temporary'
88- mock_song_temp.temporary = True
89- mock_songbook_entry.song = mock_song
90- mock_songbook_entry.songbook = mock_songbook
91- mock_songbook_entry_temp.song = mock_song_temp
92- mock_songbook_entry_temp.songbook = mock_songbook
93- mock_search_results.append(mock_songbook_entry)
94- mock_search_results.append(mock_songbook_entry_temp)
95+ mock_search_results = [('1', 'My Book', 'My Song', 1)]
96 mock_qlist_widget = MagicMock()
97 MockedQListWidgetItem.return_value = mock_qlist_widget
98
99@@ -183,9 +162,35 @@
100 # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
101 self.media_item.list_view.clear.assert_called_with()
102 MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
103- mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_songbook_entry.song.id)
104+ mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
105 self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
106
107+ def songbook_natural_sorting_test(self):
108+ """
109+ Test that songbooks are sorted naturally
110+ """
111+ # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
112+ with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem:
113+ mock_search_results = [('2', 'Thy Book', 'Thy Song', 50),
114+ ('2', 'My Book', 'Your Song', 7),
115+ ('10', 'My Book', 'Our Song', 12),
116+ ('1', 'My Book', 'My Song', 1),
117+ ('2', 'Thy Book', 'A Song', 8)]
118+ mock_qlist_widget = MagicMock()
119+ MockedQListWidgetItem.return_value = mock_qlist_widget
120+
121+ # WHEN: I display song search results grouped by book
122+ self.media_item.display_results_book(mock_search_results)
123+
124+ # THEN: The songbooks are inserted in the right (natural) order,
125+ # grouped first by book, then by number, then by song title
126+ calls = [call('My Book #1: My Song'), call().setData(QtCore.Qt.UserRole, 1),
127+ call('My Book #2: Your Song'), call().setData(QtCore.Qt.UserRole, 7),
128+ call('My Book #10: Our Song'), call().setData(QtCore.Qt.UserRole, 12),
129+ call('Thy Book #2: A Song'), call().setData(QtCore.Qt.UserRole, 8),
130+ call('Thy Book #2: Thy Song'), call().setData(QtCore.Qt.UserRole, 50)]
131+ MockedQListWidgetItem.assert_has_calls(calls)
132+
133 def display_results_topic_test(self):
134 """
135 Test displaying song search results grouped by topic with basic song
136
137=== modified file 'tests/functional/openlp_plugins/songs/test_openlpimporter.py'
138--- tests/functional/openlp_plugins/songs/test_openlpimporter.py 2016-04-27 21:23:16 +0000
139+++ tests/functional/openlp_plugins/songs/test_openlpimporter.py 2016-04-29 17:34:59 +0000
140@@ -73,4 +73,3 @@
141 self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
142 self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,
143 'setMaximum on import_wizard.progress_bar should not have been called')
144-