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

Proposed by Samuel Mehrbrodt
Status: Superseded
Proposed branch: lp:~sam92/openlp/bug-1695620
Merge into: lp:openlp
Diff against target: 717 lines (+286/-144)
7 files modified
openlp/core/lib/serviceitem.py (+4/-7)
openlp/core/ui/maindisplay.py (+2/-2)
openlp/core/ui/printserviceform.py (+5/-5)
openlp/plugins/songs/lib/mediaitem.py (+43/-19)
openlp/plugins/songs/lib/songstab.py (+72/-37)
openlp/plugins/songs/songsplugin.py (+53/-3)
tests/functional/openlp_plugins/songs/test_mediaitem.py (+107/-71)
To merge this branch: bzr merge lp:~sam92/openlp/bug-1695620
Reviewer Review Type Date Requested Status
Tim Bentley Needs Fixing
Raoul Snyman Pending
Review via email: mp+325514@code.launchpad.net

This proposal supersedes a proposal from 2017-06-04.

This proposal has been superseded by a proposal from 2017-06-12.

Description of the change

Make use of Mako for footer generation being configurable in song settings

- removed now obsolete and via template better configurable options to display "songbook", "written by" and "copyright" information in footer
- added explanation box for so far used settings as Mako placeholders
- added songs configuration setting for template including reset button
- added default template replacing currently existing configuration as best as possible (should be backwards compatible or at least be adaptable to correspond to former settings)
- write and adapt tests for new and removed functionality
- Added some more available fields in the footer: Alternate Title, CCLI Number, Topics, Authors (all music, all words)

lp:~sam92/openlp/bug-1695620 (revision 2756)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2085/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1995/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1909/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1287/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1136/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/265/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/110/

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

You need to run CI on this to show the success of tests.
Not sure why but this request did a full download when I did a check out. Also cannot run due to chord issues.
Cannot run this but there seem to be lots of line breaks in the footer display. As the footer area is small this will not be displayed and lead to forum comments.
You need some validation do defend against too long footers!

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

We're moving away from Python generated HTML, and using a JS system based on Reveal.js. Come chat with tgc and myself in IRC.

review: Disapprove
Revision history for this message
Samuel Mehrbrodt (sam92) wrote :

> Cannot run this but there seem to be lots of line breaks in the footer display. As the footer area is small this will not be displayed and lead to forum comments.

The footer has the same height as before, there should be no difference by default.

So I replaced Pystache with Mako. Should be safe to merge now I think.
As the new renderer is in a very early state atm, I can't see how I can adopt this for the new renderer. Maybe we can merge this now and adopt when the new renderer is ready?

Revision history for this message
Tim Bentley (trb143) wrote :

Blank lines!
Will review tomorrow as I have bible study!

review: Needs Fixing
lp:~sam92/openlp/bug-1695620 updated
2757. By Samuel Mehrbrodt

Remove blank lines

2758. By Samuel Mehrbrodt

Adjust display of available placeholders to mako syntax

2759. By Samuel Mehrbrodt

Only save footer template if it has been changed

2760. By Samuel Mehrbrodt

Merge trunk

2761. By Samuel Mehrbrodt

Remove blank lines

2762. By Samuel Mehrbrodt

Merge trunk

2763. By Samuel Mehrbrodt

Merge trunk

2764. By Samuel Mehrbrodt

Fix songstab

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/lib/serviceitem.py'
2--- openlp/core/lib/serviceitem.py 2017-05-17 20:06:45 +0000
3+++ openlp/core/lib/serviceitem.py 2017-06-12 20:53:24 +0000
4@@ -163,7 +163,8 @@
5 self.items = []
6 self.iconic_representation = None
7 self.raw_footer = []
8- self.foot_text = ''
9+ # Plugins can set footer_html themselves. If they don't, it will be generated from raw_footer.
10+ self.footer_html = ''
11 self.theme = None
12 self.service_item_type = None
13 self._raw_frames = []
14@@ -276,12 +277,8 @@
15 else:
16 log.error('Invalid value renderer: {item}'.format(item=self.service_item_type))
17 self.title = clean_tags(self.title)
18- # The footer should never be None, but to be compatible with a few
19- # nightly builds between 1.9.4 and 1.9.5, we have to correct this to
20- # avoid tracebacks.
21- if self.raw_footer is None:
22- self.raw_footer = []
23- self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
24+ if not self.footer_html:
25+ self.footer_html = '<br>'.join([_f for _f in self.raw_footer if _f])
26
27 def add_from_image(self, path, title, background=None, thumbnail=None):
28 """
29
30=== modified file 'openlp/core/ui/maindisplay.py'
31--- openlp/core/ui/maindisplay.py 2017-06-04 12:26:50 +0000
32+++ openlp/core/ui/maindisplay.py 2017-06-12 20:53:24 +0000
33@@ -471,8 +471,8 @@
34 created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes,
35 plugins=self.plugin_manager.plugins)
36 self.web_view.setHtml(created_html)
37- if service_item.foot_text:
38- self.footer(service_item.foot_text)
39+ if service_item.footer_html:
40+ self.footer(service_item.footer_html)
41 # if was hidden keep it hidden
42 if self.hide_mode and self.is_live and not service_item.is_media():
43 if Settings().value('core/auto unblank'):
44
45=== modified file 'openlp/core/ui/printserviceform.py'
46--- openlp/core/ui/printserviceform.py 2017-06-04 12:14:23 +0000
47+++ openlp/core/ui/printserviceform.py 2017-06-12 20:53:24 +0000
48@@ -231,11 +231,11 @@
49 for slide in range(len(item.get_frames())):
50 self._add_element('li', item.get_frame_title(slide), ol)
51 # add footer
52- foot_text = item.foot_text
53- foot_text = foot_text.partition('<br>')[2]
54- if foot_text:
55- foot_text = html.escape(foot_text.replace('<br>', '\n'))
56- self._add_element('div', foot_text.replace('\n', '<br>'), parent=div, classId='itemFooter')
57+ footer_html = item.footer_html
58+ footer_html = footer_html.partition('<br>')[2]
59+ if footer_html:
60+ footer_html = html.escape(footer_html.replace('<br>', '\n'))
61+ self._add_element('div', footer_html.replace('\n', '<br>'), parent=div, classId='itemFooter')
62 # Add service items' notes.
63 if self.notes_check_box.isChecked():
64 if item.notes:
65
66=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
67--- openlp/plugins/songs/lib/mediaitem.py 2017-06-09 06:06:49 +0000
68+++ openlp/plugins/songs/lib/mediaitem.py 2017-06-12 20:53:24 +0000
69@@ -23,6 +23,7 @@
70 import logging
71 import os
72 import shutil
73+import mako
74
75 from PyQt5 import QtCore, QtWidgets
76 from sqlalchemy.sql import and_, or_
77@@ -30,7 +31,7 @@
78 from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
79 from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
80 check_item_selected, create_separated_list
81-from openlp.core.lib.ui import create_widget_action
82+from openlp.core.lib.ui import create_widget_action, critical_error_message_box
83 from openlp.core.common.languagemanager import get_natural_key
84 from openlp.plugins.songs.forms.editsongform import EditSongForm
85 from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
86@@ -125,9 +126,6 @@
87 self.is_search_as_you_type_enabled = Settings().value('advanced/search as type')
88 self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
89 self.add_song_from_service = Settings().value(self.settings_section + '/add song from service')
90- self.display_songbook = Settings().value(self.settings_section + '/display songbook')
91- self.display_written_by_text = Settings().value(self.settings_section + '/display written by')
92- self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol')
93
94 def retranslateUi(self):
95 self.search_text_label.setText('{text}:'.format(text=UiStrings().Search))
96@@ -645,12 +643,8 @@
97 item.raw_footer = []
98 item.raw_footer.append(song.title)
99 if authors_none:
100- # If the setting for showing "Written by:" is enabled, show it before unspecified authors.
101- if Settings().value('songs/display written by'):
102- item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
103- authors=create_separated_list(authors_none)))
104- else:
105- item.raw_footer.append("{authors}".format(authors=create_separated_list(authors_none)))
106+ item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
107+ authors=create_separated_list(authors_none)))
108 if authors_words_music:
109 item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic],
110 authors=create_separated_list(authors_words_music)))
111@@ -664,17 +658,47 @@
112 item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation],
113 authors=create_separated_list(authors_translation)))
114 if song.copyright:
115- if self.display_copyright_symbol:
116- item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol,
117- song=song.copyright))
118- else:
119- item.raw_footer.append(song.copyright)
120- if self.display_songbook and song.songbook_entries:
121- songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
122+ item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol,
123+ song=song.copyright))
124+ songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
125+ if song.songbook_entries:
126 item.raw_footer.append(", ".join(songbooks))
127 if Settings().value('core/ccli number'):
128- item.raw_footer.append(translate('SongsPlugin.MediaItem',
129- 'CCLI License: ') + Settings().value('core/ccli number'))
130+ item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') +
131+ Settings().value('core/ccli number'))
132+
133+ footer_template = Settings().value('songs/footer template')
134+ # Keep this in sync with the list in songstab.py
135+ vars = {
136+ 'title': song.title,
137+ 'alternate_title': song.alternate_title,
138+ 'authors_none_label': translate('OpenLP.Ui', 'Written by'),
139+ 'authors_none': authors_none,
140+ 'authors_words_label': AuthorType.Types[AuthorType.Words],
141+ 'authors_words': authors_words,
142+ 'authors_music_label': AuthorType.Types[AuthorType.Music],
143+ 'authors_music': authors_music,
144+ 'authors_words_music_label': AuthorType.Types[AuthorType.WordsAndMusic],
145+ 'authors_words_music': authors_words_music,
146+ 'authors_translation_label': AuthorType.Types[AuthorType.Translation],
147+ 'authors_translation': authors_translation,
148+ 'authors_words_all': authors_words + authors_words_music,
149+ 'authors_music_all': authors_music + authors_words_music,
150+ 'copyright': song.copyright,
151+ 'songbook_entries': songbooks,
152+ 'ccli_license': Settings().value('core/ccli number'),
153+ 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License'),
154+ 'ccli_number': song.ccli_number,
155+ 'topics': [topic.name for topic in song.topics]
156+ }
157+
158+ try:
159+ item.footer_html = mako.template.Template(footer_template).render_unicode(**vars).replace('\n', '')
160+ except mako.exceptions.SyntaxException:
161+ log.error('Failed to render Song footer html:\n' + mako.exceptions.text_error_template().render())
162+ critical_error_message_box(message=translate('SongsPlugin.MediaItem',
163+ 'Failed to render Song footer html.\nSee log for details'))
164+
165 return authors_all
166
167 def service_load(self, item):
168
169=== modified file 'openlp/plugins/songs/lib/songstab.py'
170--- openlp/plugins/songs/lib/songstab.py 2017-02-26 21:14:49 +0000
171+++ openlp/plugins/songs/lib/songstab.py 2017-06-12 20:53:24 +0000
172@@ -24,7 +24,7 @@
173
174 from openlp.core.common import Settings, translate
175 from openlp.core.lib import SettingsTab
176-from openlp.plugins.songs.lib.ui import SongStrings
177+from openlp.plugins.songs.lib.db import AuthorType
178
179
180 class SongsTab(SettingsTab):
181@@ -50,15 +50,6 @@
182 self.add_from_service_check_box = QtWidgets.QCheckBox(self.mode_group_box)
183 self.add_from_service_check_box.setObjectName('add_from_service_check_box')
184 self.mode_layout.addWidget(self.add_from_service_check_box)
185- self.display_songbook_check_box = QtWidgets.QCheckBox(self.mode_group_box)
186- self.display_songbook_check_box.setObjectName('songbook_check_box')
187- self.mode_layout.addWidget(self.display_songbook_check_box)
188- self.display_written_by_check_box = QtWidgets.QCheckBox(self.mode_group_box)
189- self.display_written_by_check_box.setObjectName('written_by_check_box')
190- self.mode_layout.addWidget(self.display_written_by_check_box)
191- self.display_copyright_check_box = QtWidgets.QCheckBox(self.mode_group_box)
192- self.display_copyright_check_box.setObjectName('copyright_check_box')
193- self.mode_layout.addWidget(self.display_copyright_check_box)
194 self.left_layout.addWidget(self.mode_group_box)
195 # Chords group box
196 self.chords_group_box = QtWidgets.QGroupBox(self.left_column)
197@@ -89,19 +80,35 @@
198 self.neolatin_notation_radio_button.setObjectName('neolatin_notation_radio_button')
199 self.chords_layout.addWidget(self.neolatin_notation_radio_button)
200 self.left_layout.addWidget(self.chords_group_box)
201+
202+ # Footer group box
203+ self.footer_group_box = QtWidgets.QGroupBox(self.left_column)
204+ self.footer_group_box.setObjectName('footer_group_box')
205+ self.footer_layout = QtWidgets.QVBoxLayout(self.footer_group_box)
206+ self.footer_layout.setObjectName('chords_layout')
207+ self.footer_info_label = QtWidgets.QLabel(self.footer_group_box)
208+ self.footer_layout.addWidget(self.footer_info_label)
209+ self.footer_placeholder_info = QtWidgets.QTextEdit(self.footer_group_box)
210+ self.footer_layout.addWidget(self.footer_placeholder_info)
211+ self.footer_desc_label = QtWidgets.QLabel(self.footer_group_box)
212+ self.footer_layout.addWidget(self.footer_desc_label)
213+ self.footer_edit_box = QtWidgets.QTextEdit(self.footer_group_box)
214+ self.footer_layout.addWidget(self.footer_edit_box)
215+ self.footer_reset_button = QtWidgets.QPushButton(self.footer_group_box)
216+ self.footer_layout.addWidget(self.footer_reset_button, alignment=QtCore.Qt.AlignRight)
217+ self.right_layout.addWidget(self.footer_group_box)
218+
219 self.left_layout.addStretch()
220 self.right_layout.addStretch()
221 self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
222 self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
223 self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
224- self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed)
225- self.display_written_by_check_box.stateChanged.connect(self.on_written_by_check_box_changed)
226- self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed)
227 self.mainview_chords_check_box.stateChanged.connect(self.on_mainview_chords_check_box_changed)
228 self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed)
229 self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked)
230 self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked)
231 self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked)
232+ self.footer_reset_button.clicked.connect(self.on_footer_reset_button_clicked)
233
234 def retranslateUi(self):
235 self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings'))
236@@ -110,12 +117,6 @@
237 self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
238 self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
239 'Import missing songs from Service files'))
240- self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
241- self.display_written_by_check_box.setText(translate(
242- 'SongsPlugin.SongsTab', 'Show "Written by:" in footer for unspecified authors'))
243- self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab',
244- 'Display "{symbol}" symbol before copyright '
245- 'info').format(symbol=SongStrings.CopyrightSymbol))
246 self.chords_info_label.setText(translate('SongsPlugin.SongsTab', 'If enabled all text between "[" and "]" will '
247 'be regarded as chords.'))
248 self.chords_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Chords'))
249@@ -127,6 +128,53 @@
250 self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)')
251 self.neolatin_notation_radio_button.setText(
252 translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)')
253+ self.footer_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Footer'))
254+ # Keep this in sync with the list in mediaitem.py
255+ const = '<code>"{}"</code>'
256+ placeholders = [
257+ # placeholder, description, can be empty, is a list
258+ ['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False],
259+ ['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate Title'), True, False],
260+ ['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False],
261+ ['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), False, True],
262+ ['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False],
263+ ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type "Words")'), False, True],
264+ ['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False],
265+ ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Music")'), False, True],
266+ ['authors_words_music_label', const.format(AuthorType.Types[AuthorType.WordsAndMusic]), False, False],
267+ ['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Words and Music")'), False, True],
268+ ['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False],
269+ ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type "Translation")'), False, True],
270+ ['authors_words_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Words" & "Words and Music")'),
271+ False, True],
272+ ['authors_music_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Music" & "Words and Music")'),
273+ False, True],
274+ ['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False],
275+ ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), False, True],
276+ ['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False],
277+ ['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False],
278+ ['ccli_number', translate('SongsPlugin.SongsTab', 'Song CCLI Number'), True, False],
279+ ['topics', translate('SongsPlugin.SongsTab', 'Topics'), False, True],
280+ ]
281+ placeholder_info = '<table style="background: #eee">\n<tr><th><b>{ph}</b></th><th><b>{desc}</b></th></tr>\n'\
282+ .format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'),
283+ desc=translate('SongsPlugin.SongsTab', 'Description'))
284+ for placeholder in placeholders:
285+ placeholder_info += '<tr><td>{{{{{pl}}}}}</td><td>{des}{opt}</td></tr>\n'\
286+ .format(pl=placeholder[0], des=placeholder[1],
287+ opt=('&nbsp;¹' if placeholder[2] else '') +
288+ ('&nbsp;²' if placeholder[3] else ''))
289+ placeholder_info += '</table>'
290+ placeholder_info += '\n<br/>¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty'))
291+ placeholder_info += '\n<br/>² {}'.format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty'))
292+ self.footer_placeholder_info.setHtml(placeholder_info)
293+ self.footer_placeholder_info.setReadOnly(True)
294+
295+ self.footer_info_label.setText(translate('SongsPlugin.SongsTab', 'How to use Footers:'))
296+ self.footer_desc_label.setText('{} (<a href="http://docs.makotemplates.org">{}</a>):'
297+ .format(translate('SongsPlugin.SongsTab', 'Footer Template'),
298+ translate('SongsPlugin.SongsTab', 'Mako Syntax')))
299+ self.footer_reset_button.setText(translate('SongsPlugin.SongsTab', 'Reset Template'))
300
301 def on_search_as_type_check_box_changed(self, check_state):
302 self.song_search = (check_state == QtCore.Qt.Checked)
303@@ -140,15 +188,6 @@
304 def on_add_from_service_check_box_changed(self, check_state):
305 self.update_load = (check_state == QtCore.Qt.Checked)
306
307- def on_songbook_check_box_changed(self, check_state):
308- self.display_songbook = (check_state == QtCore.Qt.Checked)
309-
310- def on_written_by_check_box_changed(self, check_state):
311- self.display_written_by = (check_state == QtCore.Qt.Checked)
312-
313- def on_copyright_check_box_changed(self, check_state):
314- self.display_copyright_symbol = (check_state == QtCore.Qt.Checked)
315-
316 def on_mainview_chords_check_box_changed(self, check_state):
317 self.mainview_chords = (check_state == QtCore.Qt.Checked)
318
319@@ -164,15 +203,15 @@
320 def on_neolatin_notation_button_clicked(self):
321 self.chord_notation = 'neo-latin'
322
323+ def on_footer_reset_button_clicked(self):
324+ self.footer_edit_box.setPlainText(Settings().get_default_value('songs/footer template'))
325+
326 def load(self):
327 settings = Settings()
328 settings.beginGroup(self.settings_section)
329 self.tool_bar = settings.value('display songbar')
330 self.update_edit = settings.value('update service on edit')
331 self.update_load = settings.value('add song from service')
332- self.display_songbook = settings.value('display songbook')
333- self.display_written_by = settings.value('display written by')
334- self.display_copyright_symbol = settings.value('display copyright symbol')
335 self.enable_chords = settings.value('enable chords')
336 self.chord_notation = settings.value('chord notation')
337 self.mainview_chords = settings.value('mainview chords')
338@@ -180,9 +219,6 @@
339 self.tool_bar_active_check_box.setChecked(self.tool_bar)
340 self.update_on_edit_check_box.setChecked(self.update_edit)
341 self.add_from_service_check_box.setChecked(self.update_load)
342- self.display_songbook_check_box.setChecked(self.display_songbook)
343- self.display_written_by_check_box.setChecked(self.display_written_by)
344- self.display_copyright_check_box.setChecked(self.display_copyright_symbol)
345 self.chords_group_box.setChecked(self.enable_chords)
346 self.mainview_chords_check_box.setChecked(self.mainview_chords)
347 self.disable_chords_import_check_box.setChecked(self.disable_chords_import)
348@@ -192,6 +228,7 @@
349 self.neolatin_notation_radio_button.setChecked(True)
350 else:
351 self.english_notation_radio_button.setChecked(True)
352+ self.footer_edit_box.setPlainText(settings.value('footer template'))
353 settings.endGroup()
354
355 def save(self):
356@@ -200,13 +237,11 @@
357 settings.setValue('display songbar', self.tool_bar)
358 settings.setValue('update service on edit', self.update_edit)
359 settings.setValue('add song from service', self.update_load)
360- settings.setValue('display songbook', self.display_songbook)
361- settings.setValue('display written by', self.display_written_by)
362- settings.setValue('display copyright symbol', self.display_copyright_symbol)
363 settings.setValue('enable chords', self.chords_group_box.isChecked())
364 settings.setValue('mainview chords', self.mainview_chords)
365 settings.setValue('disable chords import', self.disable_chords_import)
366 settings.setValue('chord notation', self.chord_notation)
367+ settings.setValue('footer template', self.footer_edit_box.toPlainText())
368 settings.endGroup()
369 if self.tab_visited:
370 self.settings_form.register_post_process('songs_config_updated')
371
372=== modified file 'openlp/plugins/songs/songsplugin.py'
373--- openlp/plugins/songs/songsplugin.py 2017-06-04 09:52:15 +0000
374+++ openlp/plugins/songs/songsplugin.py 2017-06-12 20:53:24 +0000
375@@ -59,9 +59,6 @@
376 'songs/update service on edit': False,
377 'songs/add song from service': True,
378 'songs/display songbar': True,
379- 'songs/display songbook': False,
380- 'songs/display written by': True,
381- 'songs/display copyright symbol': False,
382 'songs/last directory import': '',
383 'songs/last directory export': '',
384 'songs/songselect username': '',
385@@ -71,6 +68,59 @@
386 'songs/chord notation': 'english', # Can be english, german or neo-latin
387 'songs/mainview chords': False,
388 'songs/disable chords import': False,
389+ 'songs/footer template': """\
390+${title}<br/>
391+
392+%if authors_none:
393+ <%
394+ authors = ", ".join(authors_none)
395+ %>
396+ ${authors_none_label}:&nbsp;${authors}<br/>
397+%endif
398+
399+%if authors_words_music:
400+ <%
401+ authors = ", ".join(authors_words_music)
402+ %>
403+ ${authors_words_music_label}:&nbsp;${authors}<br/>
404+%endif
405+
406+%if authors_words:
407+ <%
408+ authors = ", ".join(authors_words)
409+ %>
410+ ${authors_words_label}:&nbsp;${authors}<br/>
411+%endif
412+
413+%if authors_music:
414+ <%
415+ authors = ", ".join(authors_music)
416+ %>
417+ ${authors_music_label}:&nbsp;${authors}<br/>
418+%endif
419+
420+%if authors_translation:
421+ <%
422+ authors = ", ".join(authors_translation)
423+ %>
424+ ${authors_translation_label}:&nbsp;${authors}<br/>
425+%endif
426+
427+%if copyright:
428+ &copy;&nbsp;${copyright}<br/>
429+%endif
430+
431+%if songbook_entries:
432+ <%
433+ entries = ", ".join(songbook_entries)
434+ %>
435+ ${entries}<br/>
436+%endif
437+
438+%if ccli_license:
439+ ${ccli_license_label}&nbsp;${ccli_license}<br/>
440+%endif
441+""",
442 }
443
444
445
446=== modified file 'tests/functional/openlp_plugins/songs/test_mediaitem.py'
447--- tests/functional/openlp_plugins/songs/test_mediaitem.py 2017-06-05 21:41:29 +0000
448+++ tests/functional/openlp_plugins/songs/test_mediaitem.py 2017-06-12 20:53:24 +0000
449@@ -34,6 +34,62 @@
450
451 from tests.helpers.testmixin import TestMixin
452
453+__default_settings__ = {
454+ 'songs/footer template': """
455+${title}<br/>
456+
457+%if authors_none:
458+ <%
459+ authors = ", ".join(authors_none)
460+ %>
461+ ${authors_none_label}:&nbsp;${authors}<br/>
462+%endif
463+
464+%if authors_words_music:
465+ <%
466+ authors = ", ".join(authors_words_music)
467+ %>
468+ ${authors_words_music_label}:&nbsp;${authors}<br/>
469+%endif
470+
471+%if authors_words:
472+ <%
473+ authors = ", ".join(authors_words)
474+ %>
475+ ${authors_words_label}:&nbsp;${authors}<br/>
476+%endif
477+
478+%if authors_music:
479+ <%
480+ authors = ", ".join(authors_music)
481+ %>
482+ ${authors_music_label}:&nbsp;${authors}<br/>
483+%endif
484+
485+%if authors_translation:
486+ <%
487+ authors = ", ".join(authors_translation)
488+ %>
489+ ${authors_translation_label}:&nbsp;${authors}<br/>
490+%endif
491+
492+%if copyright:
493+ &copy;&nbsp;${copyright}<br/>
494+%endif
495+
496+%if songbook_entries:
497+ <%
498+ entries = ", ".join(songbook_entries)
499+ %>
500+ ${entries}<br/>
501+%endif
502+
503+%if ccli_license:
504+ ${ccli_license_label}&nbsp;${ccli_license}<br/>
505+%endif
506+"""
507+}
508+
509
510 class TestMediaItem(TestCase, TestMixin):
511 """
512@@ -61,6 +117,7 @@
513 self.media_item.display_copyright_symbol = False
514 self.setup_application()
515 self.build_settings()
516+ Settings().extend_default_settings(__default_settings__)
517 QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
518
519 def tearDown(self):
520@@ -301,65 +358,45 @@
521 """
522 Test build songs footer with basic song and one author
523 """
524- # GIVEN: A Song and a Service Item, mocked settings: True for 'songs/display written by'
525- # and False for 'core/ccli number' (ccli will cause traceback if true)
526-
527- mocked_settings = MagicMock()
528- mocked_settings.value.side_effect = [True, False]
529- MockedSettings.return_value = mocked_settings
530-
531- mock_song = MagicMock()
532- mock_song.title = 'My Song'
533- mock_song.authors_songs = []
534- mock_author = MagicMock()
535- mock_author.display_name = 'my author'
536- mock_author_song = MagicMock()
537- mock_author_song.author = mock_author
538- mock_song.authors_songs.append(mock_author_song)
539- mock_song.copyright = 'My copyright'
540- service_item = ServiceItem(None)
541-
542- # WHEN: I generate the Footer with default settings
543- author_list = self.media_item.generate_footer(service_item, mock_song)
544-
545- # THEN: I get the following Array returned
546- self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', 'My copyright'],
547- 'The array should be returned correctly with a song, one author and copyright')
548- self.assertEqual(author_list, ['my author'],
549- 'The author list should be returned correctly with one author')
550-
551- @patch(u'openlp.plugins.songs.lib.mediaitem.Settings')
552- def test_build_song_footer_one_author_hide_written_by(self, MockedSettings):
553- """
554- Test build songs footer with basic song and one author
555- """
556- # GIVEN: A Song and a Service Item, mocked settings: False for 'songs/display written by'
557- # and False for 'core/ccli number' (ccli will cause traceback if true)
558-
559- mocked_settings = MagicMock()
560- mocked_settings.value.side_effect = [False, False]
561- MockedSettings.return_value = mocked_settings
562-
563- mock_song = MagicMock()
564- mock_song.title = 'My Song'
565- mock_song.authors_songs = []
566- mock_author = MagicMock()
567- mock_author.display_name = 'my author'
568- mock_author_song = MagicMock()
569- mock_author_song.author = mock_author
570- mock_song.authors_songs.append(mock_author_song)
571- mock_song.copyright = 'My copyright'
572- service_item = ServiceItem(None)
573-
574- # WHEN: I generate the Footer with default settings
575- author_list = self.media_item.generate_footer(service_item, mock_song)
576-
577- # THEN: I get the following Array returned
578- self.assertEqual(service_item.raw_footer, ['My Song', 'my author', 'My copyright'],
579- 'The array should be returned correctly with a song, one author and copyright,'
580- 'text Written by should not be part of the text.')
581- self.assertEqual(author_list, ['my author'],
582- 'The author list should be returned correctly with one author')
583+ # GIVEN: A Song and a Service Item, mocked settings
584+
585+ mocked_settings = MagicMock()
586+ mocked_settings.value.side_effect = [False, "", "0"]
587+ MockedSettings.return_value = mocked_settings
588+
589+ with patch('mako.template.Template.render_unicode') as MockedRenderer:
590+ mock_song = MagicMock()
591+ mock_song.title = 'My Song'
592+ mock_song.alternate_title = ''
593+ mock_song.ccli_number = ''
594+ mock_song.authors_songs = []
595+ mock_author = MagicMock()
596+ mock_author.display_name = 'my author'
597+ mock_author_song = MagicMock()
598+ mock_author_song.author = mock_author
599+ mock_song.authors_songs.append(mock_author_song)
600+ mock_song.copyright = 'My copyright'
601+ mock_song.songbook_entries = []
602+ service_item = ServiceItem(None)
603+
604+ # WHEN: I generate the Footer with default settings
605+ author_list = self.media_item.generate_footer(service_item, mock_song)
606+
607+ # THEN: The mako function was called with the following arguments
608+ args = {'authors_translation': [], 'authors_music_label': 'Music',
609+ 'copyright': 'My copyright', 'songbook_entries': [],
610+ 'alternate_title': '', 'topics': [], 'authors_music_all': [],
611+ 'authors_words_label': 'Words', 'authors_music': [],
612+ 'authors_words_music': [], 'ccli_number': '',
613+ 'authors_none_label': 'Written by', 'title': 'My Song',
614+ 'authors_words_music_label': 'Words and Music',
615+ 'authors_none': ['my author'],
616+ 'ccli_license_label': 'CCLI License', 'authors_words': [],
617+ 'ccli_license': '0', 'authors_translation_label': 'Translation',
618+ 'authors_words_all': []}
619+ MockedRenderer.assert_called_once_with(**args)
620+ self.assertEqual(author_list, ['my author'],
621+ 'The author list should be returned correctly with one author')
622
623 def test_build_song_footer_two_authors(self):
624 """
625@@ -388,6 +425,7 @@
626 mock_author_song.author_type = AuthorType.Translation
627 mock_song.authors_songs.append(mock_author_song)
628 mock_song.copyright = 'My copyright'
629+ mock_song.songbook_entries = []
630 service_item = ServiceItem(None)
631
632 # WHEN: I generate the Footer with default settings
633@@ -395,7 +433,7 @@
634
635 # THEN: I get the following Array returned
636 self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author',
637- 'Translation: translator', 'My copyright'],
638+ 'Translation: translator', '© My copyright'],
639 'The array should be returned correctly with a song, two authors and copyright')
640 self.assertEqual(author_list, ['another author', 'my author', 'translator'],
641 'The author list should be returned correctly with two authors')
642@@ -408,6 +446,7 @@
643 mock_song = MagicMock()
644 mock_song.title = 'My Song'
645 mock_song.copyright = 'My copyright'
646+ mock_song.songbook_entries = []
647 service_item = ServiceItem(None)
648 Settings().setValue('core/ccli number', '1234')
649
650@@ -415,7 +454,7 @@
651 self.media_item.generate_footer(service_item, mock_song)
652
653 # THEN: I get the following Array returned
654- self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'],
655+ self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 1234'],
656 'The array should be returned correctly with a song, an author, copyright and ccli')
657
658 # WHEN: I amend the CCLI value
659@@ -423,7 +462,7 @@
660 self.media_item.generate_footer(service_item, mock_song)
661
662 # THEN: I would get an amended footer string
663- self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'],
664+ self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 4321'],
665 'The array should be returned correctly with a song, an author, copyright and amended ccli')
666
667 def test_build_song_footer_base_songbook(self):
668@@ -436,6 +475,8 @@
669 song.copyright = 'My copyright'
670 song.authors_songs = []
671 song.songbook_entries = []
672+ song.alternate_title = ''
673+ song.topics = []
674 song.ccli_number = ''
675 book1 = MagicMock()
676 book1.name = "My songbook"
677@@ -449,15 +490,8 @@
678 # WHEN: I generate the Footer with default settings
679 self.media_item.generate_footer(service_item, song)
680
681- # THEN: The songbook should not be in the footer
682- self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright'])
683-
684- # WHEN: I activate the "display songbook" option
685- self.media_item.display_songbook = True
686- self.media_item.generate_footer(service_item, song)
687-
688 # THEN: The songbook should be in the footer
689- self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12, Thy songbook #502A'])
690+ self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A'])
691
692 def test_build_song_footer_copyright_enabled(self):
693 """
694@@ -468,6 +502,7 @@
695 mock_song = MagicMock()
696 mock_song.title = 'My Song'
697 mock_song.copyright = 'My copyright'
698+ mock_song.songbook_entries = []
699 service_item = ServiceItem(None)
700
701 # WHEN: I generate the Footer with default settings
702@@ -484,13 +519,14 @@
703 mock_song = MagicMock()
704 mock_song.title = 'My Song'
705 mock_song.copyright = 'My copyright'
706+ mock_song.songbook_entries = []
707 service_item = ServiceItem(None)
708
709 # WHEN: I generate the Footer with default settings
710 self.media_item.generate_footer(service_item, mock_song)
711
712 # THEN: The copyright symbol should not be in the footer
713- self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright'])
714+ self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright'])
715
716 def test_authors_match(self):
717 """