From 66c9f8eb82ad160edb688f01be95a1f0c6e2bd54 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sun, 4 Jun 2017 01:31:47 +0200 Subject: [PATCH] made 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) - adjusted tests to new and removed functionality Fixes: https://launchpad.net/bugs/1695620 --- openlp/core/lib/serviceitem.py | 12 +- openlp/core/ui/maindisplay.py | 4 +- openlp/core/ui/printserviceform.py | 10 +- openlp/plugins/songs/lib/__init__.py | 14 ++ openlp/plugins/songs/lib/mediaitem.py | 50 +++--- openlp/plugins/songs/lib/songstab.py | 106 ++++++++----- openlp/plugins/songs/songsplugin.py | 43 ++++- .../openlp_plugins/songs/test_mediaitem.py | 147 ++++++++++-------- 8 files changed, 251 insertions(+), 135 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index c8040aa58..d107c47e6 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -163,7 +163,8 @@ class ServiceItem(RegistryProperties): self.items = [] self.iconic_representation = None self.raw_footer = [] - self.foot_text = '' + # Plugins can set footer_html themselves. If they don't, it will be generated from raw_footer. + self.footer_html = '' self.theme = None self.service_item_type = None self._raw_frames = [] @@ -276,12 +277,9 @@ class ServiceItem(RegistryProperties): else: log.error('Invalid value renderer: {item}'.format(item=self.service_item_type)) self.title = clean_tags(self.title) - # The footer should never be None, but to be compatible with a few - # nightly builds between 1.9.4 and 1.9.5, we have to correct this to - # avoid tracebacks. - if self.raw_footer is None: - self.raw_footer = [] - self.foot_text = '
'.join([_f for _f in self.raw_footer if _f]) + + if not self.footer_html: + self.footer_html = '
'.join([_f for _f in self.raw_footer if _f]) def add_from_image(self, path, title, background=None, thumbnail=None): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index a40ade826..c2b6b2ffb 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -471,8 +471,8 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes, plugins=self.plugin_manager.plugins) self.web_view.setHtml(created_html) - if service_item.foot_text: - self.footer(service_item.foot_text) + if service_item.footer_html: + self.footer(service_item.footer_html) # if was hidden keep it hidden if self.hide_mode and self.is_live and not service_item.is_media(): if Settings().value('core/auto unblank'): diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 7b3d80c8b..b78ddff3c 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -231,11 +231,11 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert for slide in range(len(item.get_frames())): self._add_element('li', item.get_frame_title(slide), ol) # add footer - foot_text = item.foot_text - foot_text = foot_text.partition('
')[2] - if foot_text: - foot_text = html.escape(foot_text.replace('
', '\n')) - self._add_element('div', foot_text.replace('\n', '
'), parent=div, classId='itemFooter') + footer_html = item.footer_html + footer_html = footer_html.partition('
')[2] + if footer_html: + footer_html = html.escape(footer_html.replace('
', '\n')) + self._add_element('div', footer_html.replace('\n', '
'), parent=div, classId='itemFooter') # Add service items' notes. if self.notes_check_box.isChecked(): if item.notes: diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 6798a39c0..97a1a7a12 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -664,3 +664,17 @@ def transpose_chord(chord, transpose_value, notation): else: transposed_chord += note + rest return transposed_chord + + +def make_list(array): + if len(array) > 0: + result = [] + for i in range(len(array)): + result.append({'entry': array[i]}) + if i == 0: + result[i]['first'] = True + if i == len(array) - 1: + result[i]['last'] = True + return result + else: + return False diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 7c4d128d2..1d944d9c7 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -23,6 +23,7 @@ import logging import os import shutil +import pystache from PyQt5 import QtCore, QtWidgets from sqlalchemy.sql import and_, or_ @@ -36,7 +37,7 @@ from openlp.plugins.songs.forms.editsongform import EditSongForm from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm from openlp.plugins.songs.forms.songimportform import SongImportForm from openlp.plugins.songs.forms.songexportform import SongExportForm -from openlp.plugins.songs.lib import VerseType, clean_string, delete_song +from openlp.plugins.songs.lib import VerseType, clean_string, delete_song, make_list from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML @@ -125,9 +126,6 @@ class SongMediaItem(MediaManagerItem): self.is_search_as_you_type_enabled = Settings().value('advanced/search as type') self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit') self.add_song_from_service = Settings().value(self.settings_section + '/add song from service') - self.display_songbook = Settings().value(self.settings_section + '/display songbook') - self.display_written_by_text = Settings().value(self.settings_section + '/display written by') - self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol') def retranslateUi(self): self.search_text_label.setText('{text}:'.format(text=UiStrings().Search)) @@ -641,12 +639,8 @@ class SongMediaItem(MediaManagerItem): item.raw_footer = [] item.raw_footer.append(song.title) if authors_none: - # If the setting for showing "Written by:" is enabled, show it before unspecified authors. - if Settings().value('songs/display written by'): - item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'), - authors=create_separated_list(authors_none))) - else: - item.raw_footer.append("{authors}".format(authors=create_separated_list(authors_none))) + item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'), + authors=create_separated_list(authors_none))) if authors_words_music: item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic], authors=create_separated_list(authors_words_music))) @@ -660,17 +654,35 @@ class SongMediaItem(MediaManagerItem): item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation], authors=create_separated_list(authors_translation))) if song.copyright: - if self.display_copyright_symbol: - item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol, - song=song.copyright)) - else: - item.raw_footer.append(song.copyright) - if self.display_songbook and song.songbook_entries: - songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries] + item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol, + song=song.copyright)) + songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries] + if song.songbook_entries: item.raw_footer.append(", ".join(songbooks)) if Settings().value('core/ccli number'): - item.raw_footer.append(translate('SongsPlugin.MediaItem', - 'CCLI License: ') + Settings().value('core/ccli number')) + item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + + Settings().value('core/ccli number')) + + footer_template = Settings().value('songs/footer template').replace('\n', '').replace(' ', '') + # Keep this in sync with the list in songstab.py + item.footer_html = pystache.render(footer_template, { + 'title': song.title, + 'authors_none_label': translate('OpenLP.Ui', 'Written by'), + 'authors_none': make_list(authors_none), + 'authors_words_label': AuthorType.Types[AuthorType.Words], + 'authors_words': make_list(authors_words), + 'authors_music_label': AuthorType.Types[AuthorType.Music], + 'authors_music': make_list(authors_music), + 'authors_words_music_label': AuthorType.Types[AuthorType.WordsAndMusic], + 'authors_words_music': make_list(authors_words_music), + 'authors_translation_label': AuthorType.Types[AuthorType.Translation], + 'authors_translation': make_list(authors_translation), + 'copyright': song.copyright, + 'songbook_entries': make_list(songbooks), + 'ccli_license': Settings().value('core/ccli number'), + 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License') + }) + return authors_all def service_load(self, item): diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index d1044b6c3..99e6c0efa 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -24,7 +24,7 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common import Settings, translate from openlp.core.lib import SettingsTab -from openlp.plugins.songs.lib.ui import SongStrings +from openlp.plugins.songs.lib.db import AuthorType class SongsTab(SettingsTab): @@ -50,15 +50,6 @@ class SongsTab(SettingsTab): self.add_from_service_check_box = QtWidgets.QCheckBox(self.mode_group_box) self.add_from_service_check_box.setObjectName('add_from_service_check_box') self.mode_layout.addWidget(self.add_from_service_check_box) - self.display_songbook_check_box = QtWidgets.QCheckBox(self.mode_group_box) - self.display_songbook_check_box.setObjectName('songbook_check_box') - self.mode_layout.addWidget(self.display_songbook_check_box) - self.display_written_by_check_box = QtWidgets.QCheckBox(self.mode_group_box) - self.display_written_by_check_box.setObjectName('written_by_check_box') - self.mode_layout.addWidget(self.display_written_by_check_box) - self.display_copyright_check_box = QtWidgets.QCheckBox(self.mode_group_box) - self.display_copyright_check_box.setObjectName('copyright_check_box') - self.mode_layout.addWidget(self.display_copyright_check_box) self.left_layout.addWidget(self.mode_group_box) # Chords group box self.chords_group_box = QtWidgets.QGroupBox(self.left_column) @@ -89,19 +80,36 @@ class SongsTab(SettingsTab): self.neolatin_notation_radio_button.setObjectName('neolatin_notation_radio_button') self.chords_layout.addWidget(self.neolatin_notation_radio_button) self.left_layout.addWidget(self.chords_group_box) + + # Footer group box + self.footer_group_box = QtWidgets.QGroupBox(self.left_column) + self.footer_group_box.setObjectName('footer_group_box') + self.footer_layout = QtWidgets.QVBoxLayout(self.footer_group_box) + self.footer_layout.setObjectName('chords_layout') + self.footer_info_label = QtWidgets.QLabel(self.footer_group_box) + self.footer_layout.addWidget(self.footer_info_label) + self.footer_placeholder_info = QtWidgets.QTextEdit(self.footer_group_box) + self.footer_layout.addWidget(self.footer_placeholder_info) + self.footer_desc_label = QtWidgets.QLabel(self.footer_group_box) + self.footer_layout.addWidget(self.footer_desc_label) + self.footer_edit_box = QtWidgets.QTextEdit(self.footer_group_box) + self.footer_layout.addWidget(self.footer_edit_box) + self.footer_reset_button = QtWidgets.QPushButton(self.footer_group_box) + self.footer_layout.addWidget(self.footer_reset_button, alignment=QtCore.Qt.AlignRight) + self.right_layout.addWidget(self.footer_group_box) + + self.left_layout.addStretch() self.right_layout.addStretch() self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed) self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed) self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed) - self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed) - self.display_written_by_check_box.stateChanged.connect(self.on_written_by_check_box_changed) - self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed) self.mainview_chords_check_box.stateChanged.connect(self.on_mainview_chords_check_box_changed) self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed) self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked) self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked) self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked) + self.footer_reset_button.clicked.connect(self.on_footer_reset_button_clicked) def retranslateUi(self): self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings')) @@ -110,12 +118,6 @@ class SongsTab(SettingsTab): self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit')) self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab', 'Import missing songs from Service files')) - self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer')) - self.display_written_by_check_box.setText(translate( - 'SongsPlugin.SongsTab', 'Show "Written by:" in footer for unspecified authors')) - self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab', - 'Display "{symbol}" symbol before copyright ' - 'info').format(symbol=SongStrings.CopyrightSymbol)) self.chords_info_label.setText(translate('SongsPlugin.SongsTab', 'If enabled all text between "[" and "]" will ' 'be regarded as chords.')) self.chords_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Chords')) @@ -127,6 +129,49 @@ class SongsTab(SettingsTab): self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)') self.neolatin_notation_radio_button.setText( translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)') + self.footer_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Footer')) + + # Keep this in sync with the list in mediaitem.py + const = '"{}"' + placeholders = [ + ['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False], + ['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False], + ['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), True, True], + ['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False], + ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type Words)'), True, True], + ['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False], + ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type Music)'), True, True], + ['authors_words_music_label', const.format(AuthorType.Types[AuthorType.WordsAndMusic]), False, False], + ['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type Words and Music)'), True, True], + ['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False], + ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type Translation)'), True, True], + ['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False], + ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), True, True], + ['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False], + ['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False], + ] + + placeholder_info = '\n\n'\ + .format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'), + desc=translate('SongsPlugin.SongsTab', 'Description')) + for placeholder in placeholders: + placeholder_info += '\n'\ + .format(pl=placeholder[0], des=placeholder[1], + opt=(' ¹' if placeholder[2] else '') + (' ²' if placeholder[3] else '')) + placeholder_info += '
{ph}{desc}
{{{pl}}}{des}{opt}
' + placeholder_info += '\n
¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty')) + placeholder_info += '\n
² {}:
{{#first}} True {}
{{entry}} {}
' \ + '{{#last}} True {}
'\ + .format(translate('SongsPlugin.SongsTab', 'list of entries'), + translate('SongsPlugin.SongsTab', 'for first element of list'), + translate('SongsPlugin.SongsTab', 'iterates over all entries'), + translate('SongsPlugin.SongsTab', 'for last element')) + self.footer_placeholder_info.setHtml(placeholder_info) + self.footer_placeholder_info.setReadOnly(True) + + self.footer_info_label.setText(translate('SongsPlugin.SongsTab', 'How to use Footers:')) + self.footer_desc_label.setText(translate('SongsPlugin.SongsTab', 'Footer Template (Mustache Syntax):')) + self.footer_reset_button.setText(translate('SongsPlugin.SongsTab', 'Reset Template')) def on_search_as_type_check_box_changed(self, check_state): self.song_search = (check_state == QtCore.Qt.Checked) @@ -140,15 +185,6 @@ class SongsTab(SettingsTab): def on_add_from_service_check_box_changed(self, check_state): self.update_load = (check_state == QtCore.Qt.Checked) - def on_songbook_check_box_changed(self, check_state): - self.display_songbook = (check_state == QtCore.Qt.Checked) - - def on_written_by_check_box_changed(self, check_state): - self.display_written_by = (check_state == QtCore.Qt.Checked) - - def on_copyright_check_box_changed(self, check_state): - self.display_copyright_symbol = (check_state == QtCore.Qt.Checked) - def on_mainview_chords_check_box_changed(self, check_state): self.mainview_chords = (check_state == QtCore.Qt.Checked) @@ -164,15 +200,15 @@ class SongsTab(SettingsTab): def on_neolatin_notation_button_clicked(self): self.chord_notation = 'neo-latin' + def on_footer_reset_button_clicked(self): + self.footer_edit_box.setPlainText(Settings().get_default_value('songs/footer template')); + def load(self): settings = Settings() settings.beginGroup(self.settings_section) self.tool_bar = settings.value('display songbar') self.update_edit = settings.value('update service on edit') self.update_load = settings.value('add song from service') - self.display_songbook = settings.value('display songbook') - self.display_written_by = settings.value('display written by') - self.display_copyright_symbol = settings.value('display copyright symbol') self.enable_chords = settings.value('enable chords') self.chord_notation = settings.value('chord notation') self.mainview_chords = settings.value('mainview chords') @@ -180,9 +216,6 @@ class SongsTab(SettingsTab): self.tool_bar_active_check_box.setChecked(self.tool_bar) self.update_on_edit_check_box.setChecked(self.update_edit) self.add_from_service_check_box.setChecked(self.update_load) - self.display_songbook_check_box.setChecked(self.display_songbook) - self.display_written_by_check_box.setChecked(self.display_written_by) - self.display_copyright_check_box.setChecked(self.display_copyright_symbol) self.chords_group_box.setChecked(self.enable_chords) self.mainview_chords_check_box.setChecked(self.mainview_chords) self.disable_chords_import_check_box.setChecked(self.disable_chords_import) @@ -192,6 +225,7 @@ class SongsTab(SettingsTab): self.neolatin_notation_radio_button.setChecked(True) else: self.english_notation_radio_button.setChecked(True) + self.footer_edit_box.setPlainText(settings.value('footer template')) settings.endGroup() def save(self): @@ -200,13 +234,11 @@ class SongsTab(SettingsTab): settings.setValue('display songbar', self.tool_bar) settings.setValue('update service on edit', self.update_edit) settings.setValue('add song from service', self.update_load) - settings.setValue('display songbook', self.display_songbook) - settings.setValue('display written by', self.display_written_by) - settings.setValue('display copyright symbol', self.display_copyright_symbol) settings.setValue('enable chords', self.chords_group_box.isChecked()) settings.setValue('mainview chords', self.mainview_chords) settings.setValue('disable chords import', self.disable_chords_import) settings.setValue('chord notation', self.chord_notation) + settings.setValue('footer template', self.footer_edit_box.toPlainText()) settings.endGroup() if self.tab_visited: self.settings_form.register_post_process('songs_config_updated') diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 4494ade49..08d9ea9f0 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -59,9 +59,6 @@ __default_settings__ = { 'songs/update service on edit': False, 'songs/add song from service': True, 'songs/display songbar': True, - 'songs/display songbook': False, - 'songs/display written by': True, - 'songs/display copyright symbol': False, 'songs/last directory import': '', 'songs/last directory export': '', 'songs/songselect username': '', @@ -71,6 +68,46 @@ __default_settings__ = { 'songs/chord notation': 'english', # Can be english, german or neo-latin 'songs/mainview chords': False, 'songs/disable chords import': False, + 'songs/footer template': """{{title}}
+ +{{#authors_none}} + {{#first}}{{authors_none_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_none}} +{{#authors_words_music}} + {{#first}}{{authors_words_music_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_words_music}} +{{#authors_words}} + {{#first}}{{authors_words_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_words}} +{{#authors_music}} + {{#first}}{{authors_music_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_music}} +{{#authors_translation}} + {{#first}}{{authors_translation_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_translation}} + +{{#copyright}} + © {{copyright}}
+{{/copyright}} + +{{#songbook_entries}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/songbook_entries}} + +{{#ccli_license}} + {{ccli_license_label}}: {{ccli_license}}
+{{/ccli_license}}""", } diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index d5b06b7dc..557891c71 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -34,6 +34,49 @@ from openlp.plugins.songs.lib.mediaitem import SongMediaItem from tests.helpers.testmixin import TestMixin +__default_settings__ = { + 'songs/footer template': """{{title}}
+ +{{#authors_none}} + {{#first}}{{authors_none_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_none}} +{{#authors_words_music}} + {{#first}}{{authors_words_music_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_words_music}} +{{#authors_words}} + {{#first}}{{authors_words_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_words}} +{{#authors_music}} + {{#first}}{{authors_music_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_music}} +{{#authors_translation}} + {{#first}}{{authors_translation_label}}: {{/first}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/authors_translation}} + +{{#copyright}} + © {{copyright}}
+{{/copyright}} + +{{#songbook_entries}} + {{entry}}{{^last}}, {{/last}} + {{#last}}
{{/last}} +{{/songbook_entries}} + +{{#ccli_license}} + {{ccli_license_label}}: {{ccli_license}}
+{{/ccli_license}}""" +} + class TestMediaItem(TestCase, TestMixin): """ @@ -60,6 +103,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.display_copyright_symbol = False self.setup_application() self.build_settings() + Settings().extend_default_settings(__default_settings__) QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) def tearDown(self): @@ -304,61 +348,43 @@ class TestMediaItem(TestCase, TestMixin): # and False for 'core/ccli number' (ccli will cause traceback if true) mocked_settings = MagicMock() - mocked_settings.value.side_effect = [True, False] + mocked_settings.value.side_effect = [False, "", "0"] MockedSettings.return_value = mocked_settings - mock_song = MagicMock() - mock_song.title = 'My Song' - mock_song.authors_songs = [] - mock_author = MagicMock() - mock_author.display_name = 'my author' - mock_author_song = MagicMock() - mock_author_song.author = mock_author - mock_song.authors_songs.append(mock_author_song) - mock_song.copyright = 'My copyright' - service_item = ServiceItem(None) + with patch('pystache.Renderer.render') as MockedRenderer: + mock_song = MagicMock() + mock_song.title = 'My Song' + mock_song.authors_songs = [] + mock_author = MagicMock() + mock_author.display_name = 'my author' + mock_author_song = MagicMock() + mock_author_song.author = mock_author + mock_song.authors_songs.append(mock_author_song) + mock_song.copyright = 'My copyright' + mock_song.songbook_entries = [] + service_item = ServiceItem(None) + MockedRenderer.return_value = "" - # WHEN: I generate the Footer with default settings - author_list = self.media_item.generate_footer(service_item, mock_song) + # WHEN: I generate the Footer with default settings + author_list = self.media_item.generate_footer(service_item, mock_song) - # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', 'My copyright'], - 'The array should be returned correctly with a song, one author and copyright') - self.assertEqual(author_list, ['my author'], - 'The author list should be returned correctly with one author') + # THEN: I get nothing real returned + self.assertEqual(service_item.footer_html, "", 'pystache isnt in scope') - @patch(u'openlp.plugins.songs.lib.mediaitem.Settings') - def test_build_song_footer_one_author_hide_written_by(self, MockedSettings): - """ - Test build songs footer with basic song and one author - """ - # GIVEN: A Song and a Service Item, mocked settings: False for 'songs/display written by' - # and False for 'core/ccli number' (ccli will cause traceback if true) - - mocked_settings = MagicMock() - mocked_settings.value.side_effect = [False, False] - MockedSettings.return_value = mocked_settings - - mock_song = MagicMock() - mock_song.title = 'My Song' - mock_song.authors_songs = [] - mock_author = MagicMock() - mock_author.display_name = 'my author' - mock_author_song = MagicMock() - mock_author_song.author = mock_author - mock_song.authors_songs.append(mock_author_song) - mock_song.copyright = 'My copyright' - service_item = ServiceItem(None) - - # WHEN: I generate the Footer with default settings - author_list = self.media_item.generate_footer(service_item, mock_song) - - # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'my author', 'My copyright'], - 'The array should be returned correctly with a song, one author and copyright,' - 'text Written by should not be part of the text.') - self.assertEqual(author_list, ['my author'], - 'The author list should be returned correctly with one author') + # AND: The psytache function was called with the following arguments + MockedRenderer.assert_called_once_with('', {'authors_music_label': 'Music', + 'authors_none': [{'first': True, 'entry': 'my author', + 'last': True}], 'authors_words_music': False, + 'authors_words': False, 'authors_music': False, + 'authors_translation_label': 'Translation', + 'authors_words_music_label': 'Words and Music', + 'title': 'My Song', 'ccli_license': "0", + 'copyright': 'My copyright', 'authors_none_label': 'Written by', + 'ccli_license_label': 'CCLI License', + 'authors_translation': False, 'authors_words_label': 'Words', + 'songbook_entries': False}) + self.assertEqual(author_list, ['my author'], + 'The author list should be returned correctly with one author') def test_build_song_footer_two_authors(self): """ @@ -387,6 +413,7 @@ class TestMediaItem(TestCase, TestMixin): mock_author_song.author_type = AuthorType.Translation mock_song.authors_songs.append(mock_author_song) mock_song.copyright = 'My copyright' + mock_song.songbook_entries = [] service_item = ServiceItem(None) # WHEN: I generate the Footer with default settings @@ -394,7 +421,7 @@ class TestMediaItem(TestCase, TestMixin): # THEN: I get the following Array returned self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author', - 'Translation: translator', 'My copyright'], + 'Translation: translator', '© My copyright'], 'The array should be returned correctly with a song, two authors and copyright') self.assertEqual(author_list, ['another author', 'my author', 'translator'], 'The author list should be returned correctly with two authors') @@ -407,6 +434,7 @@ class TestMediaItem(TestCase, TestMixin): mock_song = MagicMock() mock_song.title = 'My Song' mock_song.copyright = 'My copyright' + mock_song.songbook_entries = [] service_item = ServiceItem(None) Settings().setValue('core/ccli number', '1234') @@ -414,7 +442,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'], + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 1234'], 'The array should be returned correctly with a song, an author, copyright and ccli') # WHEN: I amend the CCLI value @@ -422,7 +450,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I would get an amended footer string - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'], + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 4321'], 'The array should be returned correctly with a song, an author, copyright and amended ccli') def test_build_song_footer_base_songbook(self): @@ -448,15 +476,8 @@ class TestMediaItem(TestCase, TestMixin): # WHEN: I generate the Footer with default settings self.media_item.generate_footer(service_item, song) - # THEN: The songbook should not be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright']) - - # WHEN: I activate the "display songbook" option - self.media_item.display_songbook = True - self.media_item.generate_footer(service_item, song) - # THEN: The songbook should be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12, Thy songbook #502A']) + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A']) def test_build_song_footer_copyright_enabled(self): """ @@ -467,6 +488,7 @@ class TestMediaItem(TestCase, TestMixin): mock_song = MagicMock() mock_song.title = 'My Song' mock_song.copyright = 'My copyright' + mock_song.songbook_entries = [] service_item = ServiceItem(None) # WHEN: I generate the Footer with default settings @@ -483,13 +505,14 @@ class TestMediaItem(TestCase, TestMixin): mock_song = MagicMock() mock_song.title = 'My Song' mock_song.copyright = 'My copyright' + mock_song.songbook_entries = [] service_item = ServiceItem(None) # WHEN: I generate the Footer with default settings self.media_item.generate_footer(service_item, mock_song) # THEN: The copyright symbol should not be in the footer - self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright']) + self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright']) def test_authors_match(self): """