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