From 66c9f8eb82ad160edb688f01be95a1f0c6e2bd54 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sun, 4 Jun 2017 01:31:47 +0200 Subject: [PATCH 01/14] 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): """ From 88642395ee2ac699dcc21aeea54ce2b1c2d47e1c Mon Sep 17 00:00:00 2001 From: Johannes Thomas Meyer Date: Sun, 4 Jun 2017 02:42:41 +0200 Subject: [PATCH 02/14] added a placeholder to show the alternate title of a song Fixes: https://launchpad.net/bugs/1694430 --- openlp/plugins/songs/lib/mediaitem.py | 1 + openlp/plugins/songs/lib/songstab.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 1d944d9c7..34493dbe2 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -667,6 +667,7 @@ class SongMediaItem(MediaManagerItem): # Keep this in sync with the list in songstab.py item.footer_html = pystache.render(footer_template, { 'title': song.title, + 'alternate_title': song.alternate_title, 'authors_none_label': translate('OpenLP.Ui', 'Written by'), 'authors_none': make_list(authors_none), 'authors_words_label': AuthorType.Types[AuthorType.Words], diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index 99e6c0efa..ce07eb618 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -134,7 +134,9 @@ class SongsTab(SettingsTab): # Keep this in sync with the list in mediaitem.py const = '"{}"' placeholders = [ + # placeholder, description, can be empty, is a list ['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False], + ['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate 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], From 0f3d86f0d1d158ee8d03e0c4564bd0b8c1704c94 Mon Sep 17 00:00:00 2001 From: Johannes Thomas Meyer Date: Sun, 4 Jun 2017 02:53:33 +0200 Subject: [PATCH 03/14] added a placeholder to show the topics of a song - and included the empty-description for lists always --- openlp/plugins/songs/lib/mediaitem.py | 3 ++- openlp/plugins/songs/lib/songstab.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 34493dbe2..d6345a04e 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -681,7 +681,8 @@ class SongMediaItem(MediaManagerItem): 'copyright': song.copyright, 'songbook_entries': make_list(songbooks), 'ccli_license': Settings().value('core/ccli number'), - 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License') + 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License'), + 'topics': make_list([topic.name for topic in song.topics]) }) return authors_all diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index ce07eb618..b7ae4799e 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -138,19 +138,20 @@ class SongsTab(SettingsTab): ['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False], ['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate 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_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), False, True], ['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False], - ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type Words)'), True, True], + ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type Words)'), False, True], ['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False], - ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type Music)'), True, True], + ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type Music)'), False, 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_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type Words and Music)'), False, True], ['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False], - ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type Translation)'), True, True], + ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type Translation)'), False, True], ['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False], - ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), True, True], + ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), False, True], ['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False], ['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False], + ['topics', translate('SongsPlugin.SongsTab', 'Topics'), False, True], ] placeholder_info = '\n\n'\ @@ -164,7 +165,7 @@ class SongsTab(SettingsTab): 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'), + .format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty'), translate('SongsPlugin.SongsTab', 'for first element of list'), translate('SongsPlugin.SongsTab', 'iterates over all entries'), translate('SongsPlugin.SongsTab', 'for last element')) From eadabee85e745bcda7506bf81b2e518c0cd04890 Mon Sep 17 00:00:00 2001 From: Johannes Thomas Meyer Date: Sun, 4 Jun 2017 15:10:01 +0200 Subject: [PATCH 04/14] added a placeholder to show the ccli number of a song --- openlp/plugins/songs/lib/mediaitem.py | 1 + openlp/plugins/songs/lib/songstab.py | 1 + 2 files changed, 2 insertions(+) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index d6345a04e..799681fc7 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -682,6 +682,7 @@ class SongMediaItem(MediaManagerItem): 'songbook_entries': make_list(songbooks), 'ccli_license': Settings().value('core/ccli number'), 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License'), + 'ccli_number': song.ccli_number, 'topics': make_list([topic.name for topic in song.topics]) }) diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index b7ae4799e..299ca19e6 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -151,6 +151,7 @@ class SongsTab(SettingsTab): ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), False, True], ['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False], ['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False], + ['ccli_number', translate('SongsPlugin.SongsTab', 'Song CCLI Number'), True, False], ['topics', translate('SongsPlugin.SongsTab', 'Topics'), False, True], ] From 21d131191407023a7cf0fcf6e455c523c2678101 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sun, 4 Jun 2017 15:10:50 +0200 Subject: [PATCH 05/14] corrected placeholder tags in explanation --- openlp/plugins/songs/lib/songstab.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index 299ca19e6..fc31101d4 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -159,9 +159,10 @@ class SongsTab(SettingsTab): .format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'), desc=translate('SongsPlugin.SongsTab', 'Description')) for placeholder in placeholders: - placeholder_info += '\n'\ + placeholder_info += '\n'\ .format(pl=placeholder[0], des=placeholder[1], - opt=(' ¹' if placeholder[2] else '') + (' ²' if placeholder[3] else '')) + opt=(' ¹' if placeholder[2] else '') + + (' ²' if placeholder[3] else '')) placeholder_info += '
{ph}{desc}
{{{pl}}}{des}{opt}
{{{{{pl}}}}}{des}{opt}
' placeholder_info += '\n
¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty')) placeholder_info += '\n
² {}:
{{#first}} True {}
{{entry}} {}
' \ From f89d7992fa2e75868934d65e696b7b4543e37689 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Sun, 4 Jun 2017 15:42:21 +0200 Subject: [PATCH 06/14] PEP8 fixes, cleanup, added two more placeholders for footer template, added last_or_penultimate flag in list --- openlp/plugins/songs/lib/__init__.py | 29 ++++++++++++------- openlp/plugins/songs/lib/mediaitem.py | 2 ++ openlp/plugins/songs/lib/songstab.py | 26 ++++++++++------- scripts/check_dependencies.py | 3 +- setup.cfg | 2 +- .../openlp_plugins/songs/test_mediaitem.py | 25 +++++++++------- 6 files changed, 55 insertions(+), 32 deletions(-) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 97a1a7a12..f9bda3100 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -667,14 +667,23 @@ def transpose_chord(chord, transpose_value, notation): 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: + """ + converts an ordinary list into a mustache ready dict construct augmented with some information to enable special + formatting features with the first, second to last and last element. + + :param array: input list + :return: mustache ready and augmented dict + """ + if len(array) < 0: return False + + 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 + if i == len(array) - 1 or i == len(array) - 2: + result[i]['last_or_penultimate'] = True + return result diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 799681fc7..3943f0a3e 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -678,6 +678,8 @@ class SongMediaItem(MediaManagerItem): 'authors_words_music': make_list(authors_words_music), 'authors_translation_label': AuthorType.Types[AuthorType.Translation], 'authors_translation': make_list(authors_translation), + 'authors_words_all': make_list(authors_words + authors_words_music), + 'authors_music_all': make_list(authors_music + authors_words_music), 'copyright': song.copyright, 'songbook_entries': make_list(songbooks), 'ccli_license': Settings().value('core/ccli number'), diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index fc31101d4..fb7a820cc 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -98,7 +98,6 @@ class SongsTab(SettingsTab): 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) @@ -140,13 +139,17 @@ class SongsTab(SettingsTab): ['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False], ['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), False, True], ['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False], - ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type Words)'), False, True], + ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type "Words")'), False, True], ['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False], - ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type Music)'), False, True], + ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Music")'), False, True], ['authors_words_music_label', const.format(AuthorType.Types[AuthorType.WordsAndMusic]), False, False], - ['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type Words and Music)'), False, True], + ['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Words and Music")'), False, True], ['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False], - ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type Translation)'), False, True], + ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type "Translation")'), False, True], + ['authors_words_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Words" & "Words and Music")'), + False, True], + ['authors_music_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Music" & "Words and Music")'), + False, True], ['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False], ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), False, True], ['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False], @@ -166,16 +169,19 @@ class SongsTab(SettingsTab): placeholder_info += '' placeholder_info += '\n
¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty')) placeholder_info += '\n
² {}:
{{#first}} True {}
{{entry}} {}
' \ - '{{#last}} True {}
'\ + '{{#last}} True {}
{{#last_or_penultimate}} True {}
'\ .format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty'), translate('SongsPlugin.SongsTab', 'for first element of list'), translate('SongsPlugin.SongsTab', 'iterates over all entries'), - translate('SongsPlugin.SongsTab', 'for last element')) + translate('SongsPlugin.SongsTab', 'for last element'), + translate('SongsPlugin.SongsTab', 'for last and second to 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_desc_label.setText('{} ({}):' + .format(translate('SongsPlugin.SongsTab', 'Footer Template'), + translate('SongsPlugin.SongsTab', 'Mustache Syntax'))) self.footer_reset_button.setText(translate('SongsPlugin.SongsTab', 'Reset Template')) def on_search_as_type_check_box_changed(self, check_state): @@ -206,7 +212,7 @@ class SongsTab(SettingsTab): self.chord_notation = 'neo-latin' def on_footer_reset_button_clicked(self): - self.footer_edit_box.setPlainText(Settings().get_default_value('songs/footer template')); + self.footer_edit_box.setPlainText(Settings().get_default_value('songs/footer template')) def load(self): settings = Settings() diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index f2e6ebde2..7dcb45840 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -93,7 +93,8 @@ MODULES = [ 'bs4', 'mako', 'uno', - 'six' + 'six', + 'pystache' ] diff --git a/setup.cfg b/setup.cfg index 0ecc03ae8..1291f1b6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[pep8] +[pycodestyle] exclude=resources.py,vlc.py max-line-length = 120 ignore = E402,E722 diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index 557891c71..9b88a96ad 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -354,6 +354,8 @@ class TestMediaItem(TestCase, TestMixin): with patch('pystache.Renderer.render') as MockedRenderer: mock_song = MagicMock() mock_song.title = 'My Song' + mock_song.alternate_title = '' + mock_song.ccli_number = '' mock_song.authors_songs = [] mock_author = MagicMock() mock_author.display_name = 'my author' @@ -372,17 +374,18 @@ class TestMediaItem(TestCase, TestMixin): self.assertEqual(service_item.footer_html, "", 'pystache isnt in scope') # 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', + MockedRenderer.assert_called_once_with('', {'authors_translation': [], 'authors_music_label': 'Music', + 'copyright': 'My copyright', 'songbook_entries': [], + 'alternate_title': '', 'topics': [], 'authors_music_all': [], + 'authors_words_label': 'Words', 'authors_music': [], + 'authors_words_music': [], 'ccli_number': '', + 'authors_none_label': 'Written by', 'title': 'My Song', '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}) + 'authors_none': [{'last_or_penultimate': True, 'last': True, + 'entry': 'my author', 'first': True}], + 'ccli_license_label': 'CCLI License', 'authors_words': [], + 'ccli_license': '0', 'authors_translation_label': 'Translation', + 'authors_words_all': []}) self.assertEqual(author_list, ['my author'], 'The author list should be returned correctly with one author') @@ -463,6 +466,8 @@ class TestMediaItem(TestCase, TestMixin): song.copyright = 'My copyright' song.authors_songs = [] song.songbook_entries = [] + song.alternate_title = '' + song.topics = [] song.ccli_number = '' book1 = MagicMock() book1.name = "My songbook" From a1e1092b10d68799113c3a1d3ae3190610d8177d Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 12 Jun 2017 19:43:24 +0200 Subject: [PATCH 07/14] Use Mako instead of Pystache --- openlp/plugins/songs/lib/__init__.py | 23 -------- openlp/plugins/songs/lib/mediaitem.py | 37 +++++++----- openlp/plugins/songs/lib/songstab.py | 14 ++--- openlp/plugins/songs/songsplugin.py | 85 +++++++++++++++------------ scripts/check_dependencies.py | 3 +- 5 files changed, 76 insertions(+), 86 deletions(-) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index f9bda3100..6798a39c0 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -664,26 +664,3 @@ def transpose_chord(chord, transpose_value, notation): else: transposed_chord += note + rest return transposed_chord - - -def make_list(array): - """ - converts an ordinary list into a mustache ready dict construct augmented with some information to enable special - formatting features with the first, second to last and last element. - - :param array: input list - :return: mustache ready and augmented dict - """ - if len(array) < 0: - return False - - 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 - if i == len(array) - 1 or i == len(array) - 2: - result[i]['last_or_penultimate'] = True - return result diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 3943f0a3e..b314473f3 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -23,7 +23,7 @@ import logging import os import shutil -import pystache +import mako from PyQt5 import QtCore, QtWidgets from sqlalchemy.sql import and_, or_ @@ -31,13 +31,13 @@ from sqlalchemy.sql import and_, or_ from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \ check_item_selected, create_separated_list -from openlp.core.lib.ui import create_widget_action +from openlp.core.lib.ui import create_widget_action, critical_error_message_box from openlp.core.common.languagemanager import get_natural_key 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, make_list +from openlp.plugins.songs.lib import VerseType, clean_string, delete_song 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 @@ -663,30 +663,37 @@ class SongMediaItem(MediaManagerItem): item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + Settings().value('core/ccli number')) - footer_template = Settings().value('songs/footer template').replace('\n', '').replace(' ', '') + footer_template = Settings().value('songs/footer template') # Keep this in sync with the list in songstab.py - item.footer_html = pystache.render(footer_template, { + vars = { 'title': song.title, 'alternate_title': song.alternate_title, 'authors_none_label': translate('OpenLP.Ui', 'Written by'), - 'authors_none': make_list(authors_none), + 'authors_none': authors_none, 'authors_words_label': AuthorType.Types[AuthorType.Words], - 'authors_words': make_list(authors_words), + 'authors_words': authors_words, 'authors_music_label': AuthorType.Types[AuthorType.Music], - 'authors_music': make_list(authors_music), + 'authors_music': authors_music, 'authors_words_music_label': AuthorType.Types[AuthorType.WordsAndMusic], - 'authors_words_music': make_list(authors_words_music), + 'authors_words_music': authors_words_music, 'authors_translation_label': AuthorType.Types[AuthorType.Translation], - 'authors_translation': make_list(authors_translation), - 'authors_words_all': make_list(authors_words + authors_words_music), - 'authors_music_all': make_list(authors_music + authors_words_music), + 'authors_translation': authors_translation, + 'authors_words_all': authors_words + authors_words_music, + 'authors_music_all': authors_music + authors_words_music, 'copyright': song.copyright, - 'songbook_entries': make_list(songbooks), + 'songbook_entries': songbooks, 'ccli_license': Settings().value('core/ccli number'), 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License'), 'ccli_number': song.ccli_number, - 'topics': make_list([topic.name for topic in song.topics]) - }) + 'topics': [topic.name for topic in song.topics] + } + + try: + item.footer_html = mako.template.Template(footer_template).render_unicode(**vars).replace('\n', '') + except mako.exceptions.SyntaxException: + log.error('Failed to render Song footer html:\n' + mako.exceptions.text_error_template().render()) + critical_error_message_box(message=translate('SongsPlugin.MediaItem', + 'Failed to render Song footer html.\nSee log for details')) return authors_all diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index fb7a820cc..575b77bbd 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -135,7 +135,7 @@ class SongsTab(SettingsTab): placeholders = [ # placeholder, description, can be empty, is a list ['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False], - ['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate Title'), False, False], + ['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate Title'), True, False], ['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False], ['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), False, True], ['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False], @@ -168,20 +168,14 @@ class SongsTab(SettingsTab): (' ²' if placeholder[3] else '')) placeholder_info += '' placeholder_info += '\n
¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty')) - placeholder_info += '\n
² {}:
{{#first}} True {}
{{entry}} {}
' \ - '{{#last}} True {}
{{#last_or_penultimate}} True {}
'\ - .format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty'), - translate('SongsPlugin.SongsTab', 'for first element of list'), - translate('SongsPlugin.SongsTab', 'iterates over all entries'), - translate('SongsPlugin.SongsTab', 'for last element'), - translate('SongsPlugin.SongsTab', 'for last and second to last element')) + placeholder_info += '\n
² {}'.format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty')) 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('{} ({}):' + self.footer_desc_label.setText('{} ({}):' .format(translate('SongsPlugin.SongsTab', 'Footer Template'), - translate('SongsPlugin.SongsTab', 'Mustache Syntax'))) + translate('SongsPlugin.SongsTab', 'Mako Syntax'))) self.footer_reset_button.setText(translate('SongsPlugin.SongsTab', 'Reset Template')) def on_search_as_type_check_box_changed(self, check_state): diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 08d9ea9f0..e0f3b208a 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -68,46 +68,59 @@ __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}}
+ '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}} +%if authors_none: + <% + authors = ", ".join(authors_none) + %> + ${authors_none_label}: ${authors}
+%endif -{{#copyright}} - © {{copyright}}
-{{/copyright}} +%if authors_words_music: + <% + authors = ", ".join(authors_words_music) + %> + ${authors_words_music_label}: ${authors}
+%endif -{{#songbook_entries}} - {{entry}}{{^last}}, {{/last}} - {{#last}}
{{/last}} -{{/songbook_entries}} +%if authors_words: + <% + authors = ", ".join(authors_words) + %> + ${authors_words_label}: ${authors}
+%endif -{{#ccli_license}} - {{ccli_license_label}}: {{ccli_license}}
-{{/ccli_license}}""", +%if authors_music: + <% + authors = ", ".join(authors_music) + %> + ${authors_music_label}: ${authors}
+%endif + +%if authors_translation: + <% + authors = ", ".join(authors_translation) + %> + ${authors_translation_label}: ${authors}
+%endif + +%if copyright: + © ${copyright}
+%endif + +%if songbook_entries: + <% + entries = ", ".join(songbook_entries) + %> + ${entries}
+%endif + +%if ccli_license: + ${ccli_license_label} ${ccli_license}
+%endif +""", } diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 7dcb45840..f2e6ebde2 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -93,8 +93,7 @@ MODULES = [ 'bs4', 'mako', 'uno', - 'six', - 'pystache' + 'six' ] From 5fbcbb30473940e1955e8e141ed631f7cf642a01 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 12 Jun 2017 20:04:17 +0200 Subject: [PATCH 08/14] Fix test --- .../openlp_plugins/songs/test_mediaitem.py | 120 ++++++++++-------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index 9b88a96ad..6804acb4d 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -35,46 +35,59 @@ from openlp.plugins.songs.lib.mediaitem import SongMediaItem from tests.helpers.testmixin import TestMixin __default_settings__ = { - 'songs/footer template': """{{title}}
+ '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}} +%if authors_none: + <% + authors = ", ".join(authors_none) + %> + ${authors_none_label}: ${authors}
+%endif -{{#copyright}} - © {{copyright}}
-{{/copyright}} +%if authors_words_music: + <% + authors = ", ".join(authors_words_music) + %> + ${authors_words_music_label}: ${authors}
+%endif -{{#songbook_entries}} - {{entry}}{{^last}}, {{/last}} - {{#last}}
{{/last}} -{{/songbook_entries}} +%if authors_words: + <% + authors = ", ".join(authors_words) + %> + ${authors_words_label}: ${authors}
+%endif -{{#ccli_license}} - {{ccli_license_label}}: {{ccli_license}}
-{{/ccli_license}}""" +%if authors_music: + <% + authors = ", ".join(authors_music) + %> + ${authors_music_label}: ${authors}
+%endif + +%if authors_translation: + <% + authors = ", ".join(authors_translation) + %> + ${authors_translation_label}: ${authors}
+%endif + +%if copyright: + © ${copyright}
+%endif + +%if songbook_entries: + <% + entries = ", ".join(songbook_entries) + %> + ${entries}
+%endif + +%if ccli_license: + ${ccli_license_label} ${ccli_license}
+%endif +""" } @@ -344,14 +357,13 @@ class TestMediaItem(TestCase, TestMixin): """ Test build songs footer with basic song and one author """ - # GIVEN: A Song and a Service Item, mocked settings: True for 'songs/display written by' - # and False for 'core/ccli number' (ccli will cause traceback if true) + # GIVEN: A Song and a Service Item, mocked settings mocked_settings = MagicMock() mocked_settings.value.side_effect = [False, "", "0"] MockedSettings.return_value = mocked_settings - with patch('pystache.Renderer.render') as MockedRenderer: + with patch('mako.template.Template.render_unicode') as MockedRenderer: mock_song = MagicMock() mock_song.title = 'My Song' mock_song.alternate_title = '' @@ -365,27 +377,23 @@ class TestMediaItem(TestCase, TestMixin): 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) - # THEN: I get nothing real returned - self.assertEqual(service_item.footer_html, "", 'pystache isnt in scope') - - # AND: The psytache function was called with the following arguments - MockedRenderer.assert_called_once_with('', {'authors_translation': [], 'authors_music_label': 'Music', - 'copyright': 'My copyright', 'songbook_entries': [], - 'alternate_title': '', 'topics': [], 'authors_music_all': [], - 'authors_words_label': 'Words', 'authors_music': [], - 'authors_words_music': [], 'ccli_number': '', - 'authors_none_label': 'Written by', 'title': 'My Song', - 'authors_words_music_label': 'Words and Music', - 'authors_none': [{'last_or_penultimate': True, 'last': True, - 'entry': 'my author', 'first': True}], - 'ccli_license_label': 'CCLI License', 'authors_words': [], - 'ccli_license': '0', 'authors_translation_label': 'Translation', - 'authors_words_all': []}) + # THEN: The mako function was called with the following arguments + args = {'authors_translation': [], 'authors_music_label': 'Music', + 'copyright': 'My copyright', 'songbook_entries': [], + 'alternate_title': '', 'topics': [], 'authors_music_all': [], + 'authors_words_label': 'Words', 'authors_music': [], + 'authors_words_music': [], 'ccli_number': '', + 'authors_none_label': 'Written by', 'title': 'My Song', + 'authors_words_music_label': 'Words and Music', + 'authors_none': ['my author'], + 'ccli_license_label': 'CCLI License', 'authors_words': [], + 'ccli_license': '0', 'authors_translation_label': 'Translation', + 'authors_words_all': []} + MockedRenderer.assert_called_once_with(**args) self.assertEqual(author_list, ['my author'], 'The author list should be returned correctly with one author') From f5f4226891e269dc81cfd26b495d77669b0f151c Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 12 Jun 2017 20:09:50 +0200 Subject: [PATCH 09/14] PEP8 fixes --- openlp/plugins/songs/songsplugin.py | 10 +++++----- setup.cfg | 2 +- .../functional/openlp_plugins/songs/test_mediaitem.py | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 3968a184a..4052265f6 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -73,35 +73,35 @@ ${title}
%if authors_none: <% - authors = ", ".join(authors_none) + authors = ", ".join(authors_none) %> ${authors_none_label}: ${authors}
%endif %if authors_words_music: <% - authors = ", ".join(authors_words_music) + authors = ", ".join(authors_words_music) %> ${authors_words_music_label}: ${authors}
%endif %if authors_words: <% - authors = ", ".join(authors_words) + authors = ", ".join(authors_words) %> ${authors_words_label}: ${authors}
%endif %if authors_music: <% - authors = ", ".join(authors_music) + authors = ", ".join(authors_music) %> ${authors_music_label}: ${authors}
%endif %if authors_translation: <% - authors = ", ".join(authors_translation) + authors = ", ".join(authors_translation) %> ${authors_translation_label}: ${authors}
%endif diff --git a/setup.cfg b/setup.cfg index 1291f1b6a..0ecc03ae8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[pycodestyle] +[pep8] exclude=resources.py,vlc.py max-line-length = 120 ignore = E402,E722 diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index f5e85dcca..e4f9a5e5c 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -40,35 +40,35 @@ ${title}
%if authors_none: <% - authors = ", ".join(authors_none) + authors = ", ".join(authors_none) %> ${authors_none_label}: ${authors}
%endif %if authors_words_music: <% - authors = ", ".join(authors_words_music) + authors = ", ".join(authors_words_music) %> ${authors_words_music_label}: ${authors}
%endif %if authors_words: <% - authors = ", ".join(authors_words) + authors = ", ".join(authors_words) %> ${authors_words_label}: ${authors}
%endif %if authors_music: <% - authors = ", ".join(authors_music) + authors = ", ".join(authors_music) %> ${authors_music_label}: ${authors}
%endif %if authors_translation: <% - authors = ", ".join(authors_translation) + authors = ", ".join(authors_translation) %> ${authors_translation_label}: ${authors}
%endif From 51e644711d061829cb5bf646d0079fd1762ab98b Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 12 Jun 2017 22:53:08 +0200 Subject: [PATCH 10/14] Remove blank lines --- openlp/core/lib/serviceitem.py | 1 - openlp/plugins/songs/lib/songstab.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index d107c47e6..016109024 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -277,7 +277,6 @@ class ServiceItem(RegistryProperties): else: log.error('Invalid value renderer: {item}'.format(item=self.service_item_type)) self.title = clean_tags(self.title) - if not self.footer_html: self.footer_html = '
'.join([_f for _f in self.raw_footer if _f]) diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index 575b77bbd..585c4266e 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -129,7 +129,6 @@ class SongsTab(SettingsTab): 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 = [ @@ -157,7 +156,6 @@ class SongsTab(SettingsTab): ['ccli_number', translate('SongsPlugin.SongsTab', 'Song CCLI Number'), True, False], ['topics', translate('SongsPlugin.SongsTab', 'Topics'), False, True], ] - placeholder_info = '\n\n'\ .format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'), desc=translate('SongsPlugin.SongsTab', 'Description')) From 33e3bcd1d5df7405f0c120e0b3a0e7954782e922 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Tue, 13 Jun 2017 10:17:05 +0200 Subject: [PATCH 11/14] Adjust display of available placeholders to mako syntax --- openlp/plugins/songs/lib/songstab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index 585c4266e..77aba8b38 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -160,7 +160,7 @@ class SongsTab(SettingsTab): .format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'), desc=translate('SongsPlugin.SongsTab', 'Description')) for placeholder in placeholders: - placeholder_info += '\n'\ + placeholder_info += '\n'\ .format(pl=placeholder[0], des=placeholder[1], opt=(' ¹' if placeholder[2] else '') + (' ²' if placeholder[3] else '')) From af57ef066b3ec8b0ad7454efc67283fdc0d7acc5 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Tue, 13 Jun 2017 10:24:55 +0200 Subject: [PATCH 12/14] Only save footer template if it has been changed --- openlp/plugins/songs/lib/songstab.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index 77aba8b38..71e041453 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -241,7 +241,9 @@ class SongsTab(SettingsTab): 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()) + # Only save footer template if it has been changed. This allows future updates + if self.footer_edit_box.toPlainText() != Settings().get_default_value('songs/footer template'): + settings.setValue('footer template', self.footer_edit_box.toPlainText()) settings.endGroup() if self.tab_visited: self.settings_form.register_post_process('songs_config_updated') From 99f0d37253932f08f008e20c5b939b6bbb9d2f68 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Fri, 8 Feb 2019 14:39:40 +0100 Subject: [PATCH 13/14] Remove blank lines --- openlp/plugins/songs/lib/mediaitem.py | 3 --- openlp/plugins/songs/lib/songstab.py | 2 -- 2 files changed, 5 deletions(-) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 8be8e4b34..dcf30d07f 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -697,7 +697,6 @@ class SongMediaItem(MediaManagerItem): if 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') # Keep this in sync with the list in songstab.py vars = { @@ -722,14 +721,12 @@ class SongMediaItem(MediaManagerItem): 'ccli_number': song.ccli_number, 'topics': [topic.name for topic in song.topics] } - try: item.footer_html = mako.template.Template(footer_template).render_unicode(**vars).replace('\n', '') except mako.exceptions.SyntaxException: log.error('Failed to render Song footer html:\n' + mako.exceptions.text_error_template().render()) critical_error_message_box(message=translate('SongsPlugin.MediaItem', 'Failed to render Song footer html.\nSee log for details')) - 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 7183f6aee..512490938 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -84,7 +84,6 @@ 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') @@ -101,7 +100,6 @@ class SongsTab(SettingsTab): 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) From c038476b80777d94ea7288c419871ab02171bf14 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 8 Apr 2019 23:06:05 +0200 Subject: [PATCH 14/14] Fix songstab --- openlp/plugins/songs/lib/songstab.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/plugins/songs/lib/songstab.py b/openlp/plugins/songs/lib/songstab.py index cbafa9c40..d08a1b470 100644 --- a/openlp/plugins/songs/lib/songstab.py +++ b/openlp/plugins/songs/lib/songstab.py @@ -122,7 +122,6 @@ class SongsTab(SettingsTab): 'Import missing songs from Service files')) self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab', 'Add Songbooks as first slide')) - self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer')) 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'))
{ph}{desc}
{{{{{pl}}}}}{des}{opt}
${{{pl}}}{des}{opt}