forked from openlp/openlp
Merge branch 'add_footer_to_web_api' into 'master'
Add theme data and footer to web api Closes #537 See merge request openlp/openlp!224
This commit is contained in:
commit
c6c5f77b03
@ -24,7 +24,7 @@ from openlp.core.api.lib import login_required
|
||||
from openlp.core.common import ThemeLevel
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
from flask import jsonify, request, abort, Blueprint
|
||||
from flask import jsonify, request, abort, Blueprint, Response
|
||||
|
||||
controller_views = Blueprint('controller', __name__)
|
||||
log = logging.getLogger(__name__)
|
||||
@ -117,8 +117,10 @@ def set_theme_level():
|
||||
|
||||
|
||||
@controller_views.route('/themes', methods=['GET'])
|
||||
@login_required
|
||||
def get_themes():
|
||||
"""
|
||||
Gets a list of all existing themes
|
||||
"""
|
||||
log.debug('controller-v2-themes-get')
|
||||
theme_level = Registry().get('settings').value('themes/theme level')
|
||||
theme_list = []
|
||||
@ -144,11 +146,39 @@ def get_themes():
|
||||
return jsonify(theme_list)
|
||||
|
||||
|
||||
@controller_views.route('/themes/<theme_name>', methods=['GET'])
|
||||
def get_theme_data(theme_name):
|
||||
"""
|
||||
Get a theme's data
|
||||
"""
|
||||
log.debug(f'controller-v2-theme-data-get {theme_name}')
|
||||
themes = Registry().execute('get_theme_names')[0]
|
||||
if theme_name not in themes:
|
||||
log.error('Requested non-existent theme')
|
||||
abort(404)
|
||||
theme_data = Registry().get('theme_manager').get_theme_data(theme_name).export_theme_self_contained(True)
|
||||
return Response(theme_data, mimetype='application/json')
|
||||
|
||||
|
||||
@controller_views.route('/live-theme', methods=['GET'])
|
||||
def get_live_theme_data():
|
||||
"""
|
||||
Get the live theme's data
|
||||
"""
|
||||
log.debug('controller-v2-live-theme-data-get')
|
||||
live_service_item = Registry().get('live_controller').service_item
|
||||
if live_service_item:
|
||||
theme_data = live_service_item.get_theme_data()
|
||||
else:
|
||||
theme_data = Registry().get('theme_manager').get_theme_data(None)
|
||||
self_contained_theme = theme_data.export_theme_self_contained(True)
|
||||
return Response(self_contained_theme, mimetype='application/json')
|
||||
|
||||
|
||||
@controller_views.route('/theme', methods=['GET'])
|
||||
@login_required
|
||||
def get_theme():
|
||||
"""
|
||||
Get the current theme
|
||||
Get the current theme name
|
||||
"""
|
||||
log.debug('controller-v2-theme-get')
|
||||
theme_level = Registry().get('settings').value('themes/theme level')
|
||||
|
@ -24,6 +24,7 @@ OpenLP work.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import base64
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
@ -278,6 +279,17 @@ def image_to_byte(image, base_64=True):
|
||||
return bytes(byte_array.toBase64()).decode('utf-8')
|
||||
|
||||
|
||||
def image_to_data_uri(image_path):
|
||||
"""
|
||||
Converts a image into a base64 data uri
|
||||
"""
|
||||
extension = image_path.suffix.replace('.', '')
|
||||
with open(image_path, 'rb') as image_file:
|
||||
image_bytes = image_file.read()
|
||||
image_base64 = base64.b64encode(image_bytes).decode('utf-8')
|
||||
return 'data:image/{extension};base64,{data}'.format(extension=extension, data=image_base64)
|
||||
|
||||
|
||||
def create_thumb(image_path, thumb_path, return_icon=True, size=None):
|
||||
"""
|
||||
Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb.
|
||||
|
@ -877,6 +877,7 @@ class ServiceItem(RegistryProperties):
|
||||
item['text'] = frame['text']
|
||||
item['html'] = self.rendered_slides[index]['text']
|
||||
item['chords'] = self.rendered_slides[index]['chords']
|
||||
item['footer'] = self.rendered_slides[index]['footer']
|
||||
elif self.is_image() and not frame.get('image', '') and \
|
||||
Registry().get('settings_thread').value('api/thumbnails'):
|
||||
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
||||
|
@ -23,6 +23,7 @@ Provide the theme XML and handling functions for OpenLP v2 themes.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import copy
|
||||
|
||||
from lxml import etree, objectify
|
||||
|
||||
@ -30,7 +31,7 @@ from openlp.core.common import de_hump
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.lib import get_text_file_string, str_to_bool
|
||||
from openlp.core.lib import get_text_file_string, str_to_bool, image_to_data_uri
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -390,6 +391,25 @@ class Theme(object):
|
||||
theme_data["{attr}".format(attr=attr)] = value
|
||||
return json.dumps(theme_data, cls=OpenLPJSONEncoder, base_path=theme_path, is_js=is_js)
|
||||
|
||||
def export_theme_self_contained(self, is_js=True):
|
||||
"""
|
||||
Get a self contained theme dictionary
|
||||
Same as export theme, but images is turned into a data uri
|
||||
|
||||
:param is_js: For internal use, for example with the theme js code.
|
||||
:return str: The json encoded theme object
|
||||
"""
|
||||
theme_copy = copy.deepcopy(self)
|
||||
if self.background_type == 'image':
|
||||
image = image_to_data_uri(self.background_filename)
|
||||
theme_copy.background_filename = image
|
||||
current_screen_geometry = ScreenList().current.display_geometry
|
||||
theme_copy.display_size_width = current_screen_geometry.width()
|
||||
theme_copy.display_size_height = current_screen_geometry.height()
|
||||
theme_copy.background_source = ''
|
||||
exported_theme = theme_copy.export_theme(is_js=is_js)
|
||||
return exported_theme
|
||||
|
||||
def parse(self, xml):
|
||||
"""
|
||||
Read in an XML string and parse it.
|
||||
|
@ -18,16 +18,27 @@
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
|
||||
def test_retrieve_live_item(flask_client):
|
||||
pytest.skip()
|
||||
def test_retrieve_live_item(flask_client, settings):
|
||||
"""
|
||||
Test the live-item endpoint with a mocked service item
|
||||
"""
|
||||
# GIVEN: A mocked controller with a mocked service item
|
||||
fake_live_controller = MagicMock()
|
||||
fake_live_controller.service_item = MagicMock()
|
||||
fake_live_controller.selected_row = 0
|
||||
fake_live_controller.service_item.to_dict.return_value = {'slides': [{'selected': False}]}
|
||||
Registry().register('live_controller', fake_live_controller)
|
||||
|
||||
# WHEN: The live-item endpoint is called
|
||||
res = flask_client.get('/api/v2/controller/live-item').get_json()
|
||||
assert len(res) == 0
|
||||
|
||||
# THEN: The correct item data should be returned
|
||||
assert res == {'slides': [{'selected': True}]}
|
||||
|
||||
|
||||
def test_controller_set_requires_login(settings, flask_client):
|
||||
@ -42,6 +53,11 @@ def test_controller_set_does_not_accept_get(flask_client):
|
||||
assert res.status_code == 405
|
||||
|
||||
|
||||
def test_controller_set_aborts_on_unspecified_controller(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/show')
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_controller_set_calls_live_controller(flask_client, settings):
|
||||
fake_live_controller = MagicMock()
|
||||
Registry().register('live_controller', fake_live_controller)
|
||||
@ -67,6 +83,11 @@ def test_controller_direction_does_fails_on_wrong_data(flask_client, settings):
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_controller_direction_does_fails_on_missing_data(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/progress')
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_controller_direction_calls_service_manager(flask_client, settings):
|
||||
fake_live_controller = MagicMock()
|
||||
Registry().register('live_controller', fake_live_controller)
|
||||
@ -76,9 +97,22 @@ def test_controller_direction_calls_service_manager(flask_client, settings):
|
||||
|
||||
|
||||
# Themes tests
|
||||
def test_controller_get_theme_level_returns_valid_theme_level(flask_client, settings):
|
||||
def test_controller_get_theme_level_returns_valid_theme_level_global(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 1)
|
||||
res = flask_client.get('/api/v2/controller/theme-level').get_json()
|
||||
assert res == ('global' or 'service' or 'song')
|
||||
assert res == 'global'
|
||||
|
||||
|
||||
def test_controller_get_theme_level_returns_valid_theme_level_service(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 2)
|
||||
res = flask_client.get('/api/v2/controller/theme-level').get_json()
|
||||
assert res == 'service'
|
||||
|
||||
|
||||
def test_controller_get_theme_level_returns_valid_theme_level_song(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 3)
|
||||
res = flask_client.get('/api/v2/controller/theme-level').get_json()
|
||||
assert res == 'song'
|
||||
|
||||
|
||||
def test_controller_set_theme_level_aborts_if_no_theme_level(flask_client, settings):
|
||||
@ -91,12 +125,24 @@ def test_controller_set_theme_level_aborts_if_invalid_theme_level(flask_client,
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_controller_set_theme_level_sets_theme_level(flask_client, settings):
|
||||
def test_controller_set_theme_level_sets_theme_level_global(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/theme-level', json=dict(level='global'))
|
||||
assert res.status_code == 204
|
||||
assert Registry().get('settings').value('themes/theme level') == 1
|
||||
|
||||
|
||||
def test_controller_set_theme_level_sets_theme_level_service(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/theme-level', json=dict(level='service'))
|
||||
assert res.status_code == 204
|
||||
assert Registry().get('settings').value('themes/theme level') == 2
|
||||
|
||||
|
||||
def test_controller_set_theme_level_sets_theme_level_song(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/theme-level', json=dict(level='song'))
|
||||
assert res.status_code == 204
|
||||
assert Registry().get('settings').value('themes/theme level') == 3
|
||||
|
||||
|
||||
def test_controller_get_themes_retrieves_themes_list(flask_client, settings):
|
||||
Registry().register('theme_manager', MagicMock())
|
||||
Registry().register('service_manager', MagicMock())
|
||||
@ -104,25 +150,102 @@ def test_controller_get_themes_retrieves_themes_list(flask_client, settings):
|
||||
assert type(res) is list
|
||||
|
||||
|
||||
def test_controller_get_theme_returns_current_theme(flask_client, settings):
|
||||
Registry().get('settings').setValue('themes/theme level', 1)
|
||||
Registry().get('settings').setValue('themes/global theme', 'Default')
|
||||
def test_controller_get_themes_retrieves_themes_list_service(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 2)
|
||||
mocked_service_manager = MagicMock()
|
||||
mocked_service_manager.service_theme = 'test_theme'
|
||||
Registry().register('theme_manager', MagicMock())
|
||||
Registry().register('service_manager', mocked_service_manager)
|
||||
Registry().register_function('get_theme_names', MagicMock(side_effect=[['theme1', 'test_theme', 'theme2']]))
|
||||
res = flask_client.get('api/v2/controller/themes').get_json()
|
||||
assert res == [{'name': 'theme1', 'selected': False}, {'name': 'test_theme', 'selected': True},
|
||||
{'name': 'theme2', 'selected': False}]
|
||||
|
||||
|
||||
def test_controller_get_theme_data(flask_client, settings):
|
||||
Registry().register_function('get_theme_names', MagicMock(side_effect=[['theme1', 'theme2']]))
|
||||
Registry().register('theme_manager', MagicMock())
|
||||
res = flask_client.get('api/v2/controller/themes/theme1')
|
||||
assert res.status_code == 200
|
||||
|
||||
|
||||
def test_controller_get_theme_data_invalid_theme(flask_client, settings):
|
||||
Registry().register_function('get_theme_names', MagicMock(side_effect=[['theme1', 'theme2']]))
|
||||
Registry().register('theme_manager', MagicMock())
|
||||
res = flask_client.get('api/v2/controller/themes/imaginarytheme')
|
||||
assert res.status_code == 404
|
||||
|
||||
|
||||
def test_controller_get_live_theme_data(flask_client, settings):
|
||||
fake_live_controller = MagicMock()
|
||||
theme = MagicMock()
|
||||
theme.export_theme_self_contained.return_value = '[[], []]'
|
||||
fake_live_controller.service_item.get_theme_data.return_value = theme
|
||||
Registry().register('live_controller', fake_live_controller)
|
||||
res = flask_client.get('api/v2/controller/live-theme')
|
||||
assert res.status_code == 200
|
||||
assert res.get_json() == [[], []]
|
||||
|
||||
|
||||
def test_controller_get_live_theme_data_no_service_item(flask_client, settings):
|
||||
fake_theme_manager = MagicMock()
|
||||
fake_live_controller = MagicMock()
|
||||
theme = MagicMock()
|
||||
theme.export_theme_self_contained.return_value = '[[], [], []]'
|
||||
fake_theme_manager.get_theme_data.return_value = theme
|
||||
fake_live_controller.service_item = None
|
||||
Registry().register('theme_manager', fake_theme_manager)
|
||||
Registry().register('live_controller', fake_live_controller)
|
||||
res = flask_client.get('api/v2/controller/live-theme')
|
||||
assert res.status_code == 200
|
||||
assert res.get_json() == [[], [], []]
|
||||
|
||||
|
||||
def test_controller_get_theme_returns_current_theme_global(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 1)
|
||||
settings.setValue('themes/global theme', 'Default')
|
||||
res = flask_client.get('/api/v2/controller/theme')
|
||||
assert res.status_code == 200
|
||||
assert res.get_json() == 'Default'
|
||||
|
||||
|
||||
def test_controller_get_theme_returns_current_theme_service(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 2)
|
||||
settings.setValue('servicemanager/service theme', 'Service')
|
||||
res = flask_client.get('/api/v2/controller/theme')
|
||||
assert res.status_code == 200
|
||||
assert res.get_json() == 'Service'
|
||||
|
||||
|
||||
def test_controller_set_theme_aborts_if_no_theme(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/theme')
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_controller_set_theme_sets_theme(flask_client, settings):
|
||||
def test_controller_set_theme_sets_global_theme(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 1)
|
||||
res = flask_client.post('/api/v2/controller/theme', json=dict(theme='test'))
|
||||
assert res.status_code == 204
|
||||
|
||||
|
||||
def test_controller_set_theme_sets_service_theme(flask_client, settings):
|
||||
settings.setValue('themes/theme level', 2)
|
||||
res = flask_client.post('/api/v2/controller/theme', json=dict(theme='test'))
|
||||
assert res.status_code == 204
|
||||
|
||||
|
||||
def test_controller_set_theme_returns_song_exception(flask_client, settings):
|
||||
Registry().get('settings').setValue('themes/theme level', 3)
|
||||
settings.setValue('themes/theme level', 3)
|
||||
res = flask_client.post('/api/v2/controller/theme', json=dict(theme='test'))
|
||||
assert res.status_code == 501
|
||||
|
||||
|
||||
def test_controller_clear_live(flask_client, settings):
|
||||
Registry().register('live_controller', MagicMock())
|
||||
res = flask_client.post('/api/v2/controller/clear/live')
|
||||
assert res.status_code == 204
|
||||
|
||||
|
||||
def test_controller_clear_invalid(flask_client, settings):
|
||||
res = flask_client.post('/api/v2/controller/clear/my_screen')
|
||||
assert res.status_code == 404
|
||||
|
@ -595,7 +595,8 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
'That saved a wretch like me;\n'
|
||||
'I once was lost, but now am found,\n'
|
||||
'Was blind, but now I see.',
|
||||
'title': 'Amazing Grace'
|
||||
'title': 'Amazing Grace',
|
||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': '’Twas grace that taught my heart to fear,\n'
|
||||
@ -612,7 +613,8 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
'And grace my fears relieved;\n'
|
||||
'How precious did that grace appear,\n'
|
||||
'The hour I first believed!',
|
||||
'title': 'Amazing Grace'
|
||||
'title': 'Amazing Grace',
|
||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': 'Through many dangers, toils and snares\n'
|
||||
@ -629,7 +631,8 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
'I have already come;\n'
|
||||
'’Tis grace that brought me safe thus far,\n'
|
||||
'And grace will lead me home.',
|
||||
'title': 'Amazing Grace'
|
||||
'title': 'Amazing Grace',
|
||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': 'The Lord has promised good to me,\n'
|
||||
@ -646,7 +649,8 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
'His word my hope secures;\n'
|
||||
'He will my shield and portion be\n'
|
||||
'As long as life endures.',
|
||||
'title': 'Amazing Grace'
|
||||
'title': 'Amazing Grace',
|
||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': 'Yes, when this heart and flesh shall fail,\n'
|
||||
@ -663,7 +667,8 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
'And mortal life shall cease,\n'
|
||||
'I shall possess within the veil\n'
|
||||
'A life of joy and peace.',
|
||||
'title': 'Amazing Grace'
|
||||
'title': 'Amazing Grace',
|
||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': 'When we’ve been there a thousand years,\n'
|
||||
@ -680,7 +685,8 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
'Bright shining as the sun,\n'
|
||||
'We’ve no less days to sing God’s praise\n'
|
||||
'Than when we first begun.',
|
||||
'title': 'Amazing Grace'
|
||||
'title': 'Amazing Grace',
|
||||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
}
|
||||
],
|
||||
'theme': None,
|
||||
|
@ -25,7 +25,7 @@ import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from tempfile import mkdtemp
|
||||
from unittest.mock import ANY, Mock, MagicMock, patch, call
|
||||
from unittest.mock import ANY, Mock, MagicMock, patch, call, sentinel
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
@ -66,6 +66,56 @@ def test_initial_theme_manager(registry):
|
||||
assert Registry().get('theme_manager') is not None, 'The base theme manager should be registered'
|
||||
|
||||
|
||||
@patch('openlp.core.ui.thememanager.Theme')
|
||||
def test_get_global_theme(mocked_theme, registry):
|
||||
"""
|
||||
Test the global theme method returns the theme data for the global theme
|
||||
"""
|
||||
# GIVEN: A service manager instance and the global theme
|
||||
theme_manager = ThemeManager(None)
|
||||
theme_manager.global_theme = 'global theme name'
|
||||
theme_manager._theme_list = {'global theme name': sentinel.global_theme}
|
||||
|
||||
# WHEN: Calling get_global_theme
|
||||
result = theme_manager.get_global_theme()
|
||||
|
||||
# THEN: Returned global theme
|
||||
assert result == sentinel.global_theme
|
||||
|
||||
|
||||
@patch('openlp.core.ui.thememanager.Theme')
|
||||
def test_get_theme_data(mocked_theme, registry):
|
||||
"""
|
||||
Test that the get theme data method returns the requested theme data
|
||||
"""
|
||||
# GIVEN: A service manager instance and themes
|
||||
theme_manager = ThemeManager(None)
|
||||
theme_manager._theme_list = {'theme1': sentinel.theme1, 'theme2': sentinel.theme2}
|
||||
|
||||
# WHEN: Get theme data is called with 'theme2'
|
||||
result = theme_manager.get_theme_data('theme2')
|
||||
|
||||
# THEN: Should return theme2's data
|
||||
assert result == sentinel.theme2
|
||||
|
||||
|
||||
@patch('openlp.core.ui.thememanager.Theme')
|
||||
def test_get_theme_data_missing(mocked_theme, registry):
|
||||
"""
|
||||
Test that the get theme data method returns the default theme when theme name not found
|
||||
"""
|
||||
# GIVEN: A service manager instance and themes
|
||||
theme_manager = ThemeManager(None)
|
||||
theme_manager._theme_list = {'theme1': sentinel.theme1, 'theme2': sentinel.theme2}
|
||||
mocked_theme.return_value = sentinel.default_theme
|
||||
|
||||
# WHEN: Get theme data is called with None
|
||||
result = theme_manager.get_theme_data(None)
|
||||
|
||||
# THEN: Should return default theme's data
|
||||
assert result == sentinel.default_theme
|
||||
|
||||
|
||||
@patch('openlp.core.ui.thememanager.shutil')
|
||||
@patch('openlp.core.ui.thememanager.create_paths')
|
||||
def test_save_theme_same_image(mocked_create_paths, mocked_shutil, registry):
|
||||
@ -301,7 +351,8 @@ def test_over_write_message_box_no(mocked_translate, mocked_qmessagebox_question
|
||||
defaultButton=ANY)
|
||||
|
||||
|
||||
def test_unzip_theme(registry):
|
||||
@patch('openlp.core.lib.theme.Theme.set_default_header_footer')
|
||||
def test_unzip_theme(mocked_theme_set_defaults, registry):
|
||||
"""
|
||||
Test that unzipping of themes works
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user