forked from openlp/openlp
Fix various issues and return current theme in API:
- New API method to return current theme - Use single method to set service items via both position and uuid - Update tests to match above new features - Fix issue where chords were not being returned to the API - Fix the clear/backspace icon (the old one doesn't exist in Debian)
This commit is contained in:
parent
e8db9dd524
commit
181b4fdcc9
|
@ -0,0 +1,5 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
|
rev: 3.8.1
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
|
@ -18,6 +18,7 @@
|
||||||
# You should have received a copy of the GNU General Public License #
|
# You should have received a copy of the GNU General Public License #
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
import logging
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
@ -26,6 +27,7 @@ from openlp.core.api.versions import v1
|
||||||
from openlp.core.api.versions import v2
|
from openlp.core.api.versions import v2
|
||||||
from openlp.core.api.main import main_views
|
from openlp.core.api.main import main_views
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
|
@ -36,3 +38,9 @@ v2.register_blueprints(app)
|
||||||
|
|
||||||
def register_blueprint(blueprint, url_prefix=None):
|
def register_blueprint(blueprint, url_prefix=None):
|
||||||
app.register_blueprint(blueprint, url_prefix)
|
app.register_blueprint(blueprint, url_prefix)
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_server_error(error):
|
||||||
|
log.error('Unhandled HTTP error: {error}'.format(error=error))
|
||||||
|
return 'Internal server error', 500
|
||||||
|
|
|
@ -50,9 +50,9 @@ def controller_text_api():
|
||||||
if current_item.is_text():
|
if current_item.is_text():
|
||||||
if frame['verse']:
|
if frame['verse']:
|
||||||
item['tag'] = str(frame['verse'])
|
item['tag'] = str(frame['verse'])
|
||||||
item['chords_text'] = str(frame.get('chords_text', ''))
|
|
||||||
item['text'] = frame['text']
|
item['text'] = frame['text']
|
||||||
item['html'] = current_item.get_rendered_frame(index)
|
item['html'] = current_item.rendered_slides[index]['text']
|
||||||
|
item['chords'] = current_item.rendered_slides[index]['chords']
|
||||||
elif current_item.is_image() and not frame.get('image', '') and \
|
elif current_item.is_image() and not frame.get('image', '') and \
|
||||||
Registry().get('settings_thread').value('api/thumbnails'):
|
Registry().get('settings_thread').value('api/thumbnails'):
|
||||||
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
||||||
|
@ -176,6 +176,20 @@ def get_themes():
|
||||||
return jsonify(theme_list)
|
return jsonify(theme_list)
|
||||||
|
|
||||||
|
|
||||||
|
@controller_views.route('/theme', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_theme():
|
||||||
|
"""
|
||||||
|
Get the current theme
|
||||||
|
"""
|
||||||
|
theme_level = Registry().get('settings').value('themes/theme level')
|
||||||
|
if theme_level == ThemeLevel.Service:
|
||||||
|
theme = Registry().get('settings').value('servicemanager/service theme')
|
||||||
|
else:
|
||||||
|
theme = Registry().get('settings').value('themes/global theme')
|
||||||
|
return jsonify(theme)
|
||||||
|
|
||||||
|
|
||||||
@controller_views.route('/theme', methods=['POST'])
|
@controller_views.route('/theme', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def set_theme():
|
def set_theme():
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
##########################################################################
|
##########################################################################
|
||||||
import logging
|
import logging
|
||||||
from openlp.core.api.lib import login_required
|
|
||||||
|
|
||||||
from flask import jsonify, request, abort, Blueprint
|
from flask import jsonify, request, abort, Blueprint
|
||||||
|
|
||||||
|
from openlp.core.api.lib import login_required
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
|
from openlp.core.common.utils import is_uuid
|
||||||
|
|
||||||
|
|
||||||
service_views = Blueprint('service', __name__)
|
service_views = Blueprint('service', __name__)
|
||||||
|
@ -57,35 +58,24 @@ def service_items():
|
||||||
|
|
||||||
|
|
||||||
@service_views.route('/show', methods=['POST'])
|
@service_views.route('/show', methods=['POST'])
|
||||||
|
@service_views.route('/show/<item_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def service_set():
|
def service_set(item_id=None):
|
||||||
data = request.json
|
data = request.json
|
||||||
if not data:
|
item_id = item_id or data.get('id') or data.get('uid')
|
||||||
log.error('Missing request data')
|
if not item_id:
|
||||||
|
log.error('Missing item id')
|
||||||
abort(400)
|
abort(400)
|
||||||
|
if is_uuid(item_id):
|
||||||
|
Registry().get('service_manager').servicemanager_set_item_by_uuid.emit(item_id)
|
||||||
|
return '', 204
|
||||||
try:
|
try:
|
||||||
id = int(data.get('id', -1))
|
id = int(item_id)
|
||||||
|
Registry().get('service_manager').servicemanager_set_item.emit(id)
|
||||||
|
return '', 204
|
||||||
except ValueError:
|
except ValueError:
|
||||||
log.error('Invalid data passed ' + data)
|
log.error('Invalid item id: ' + item_id)
|
||||||
abort(400)
|
abort(400)
|
||||||
Registry().get('service_manager').servicemanager_set_item.emit(id)
|
|
||||||
return '', 204
|
|
||||||
|
|
||||||
|
|
||||||
@service_views.route('/show_id', methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def service_set_by_uid():
|
|
||||||
data = request.json
|
|
||||||
if not data:
|
|
||||||
log.error('Missing request data')
|
|
||||||
abort(400)
|
|
||||||
try:
|
|
||||||
id = str(data.get('uid', ''))
|
|
||||||
except ValueError:
|
|
||||||
log.error('Invalid data passed ' + data)
|
|
||||||
abort(400)
|
|
||||||
Registry().get('service_manager').servicemanager_set_item_by_uuid.emit(id)
|
|
||||||
return '', 204
|
|
||||||
|
|
||||||
|
|
||||||
@service_views.route('/progress', methods=['POST'])
|
@service_views.route('/progress', methods=['POST'])
|
||||||
|
|
|
@ -104,12 +104,20 @@ class WebSocketWorker(ThreadWorker, RegistryProperties, LogMixin):
|
||||||
"""
|
"""
|
||||||
Stop the websocket server
|
Stop the websocket server
|
||||||
"""
|
"""
|
||||||
if hasattr(self.server, 'ws_server'):
|
try:
|
||||||
self.server.ws_server.close()
|
if hasattr(self.server, 'ws_server'):
|
||||||
elif hasattr(self.server, 'server'):
|
self.server.ws_server.close()
|
||||||
self.server.server.close()
|
elif hasattr(self.server, 'server'):
|
||||||
self.event_loop.stop()
|
self.server.server.close()
|
||||||
self.event_loop.close()
|
except RuntimeError:
|
||||||
|
# Sometimes it is already closed
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.event_loop.stop()
|
||||||
|
self.event_loop.close()
|
||||||
|
except RuntimeError:
|
||||||
|
# Sometimes it is already closed
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WebSocketServer(RegistryProperties, LogMixin):
|
class WebSocketServer(RegistryProperties, LogMixin):
|
||||||
|
|
|
@ -430,7 +430,7 @@ def main():
|
||||||
translators = LanguageManager.get_translators(language)
|
translators = LanguageManager.get_translators(language)
|
||||||
for translator in translators:
|
for translator in translators:
|
||||||
if not translator.isEmpty():
|
if not translator.isEmpty():
|
||||||
app.installTranslator(translator)
|
application.installTranslator(translator)
|
||||||
if not translators:
|
if not translators:
|
||||||
log.debug('Could not find translators.')
|
log.debug('Could not find translators.')
|
||||||
if args and not args.no_error_form:
|
if args and not args.no_error_form:
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
The :mod:`~openlp.core.display.window` module contains the display window
|
The :mod:`~openlp.core.display.window` module contains the display window
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
|
@ -47,3 +48,11 @@ def wait_for(check_func, error_message='Timed out waiting for completion', timeo
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
app.process_events()
|
app.process_events()
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def is_uuid(uuid):
|
||||||
|
if not isinstance(uuid, (str, bytes)):
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
re.match(r'^[0-9A-F]{8}-[0-9A-F]{4}-[14][0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$', uuid, re.IGNORECASE) is not None
|
||||||
|
)
|
||||||
|
|
|
@ -221,8 +221,9 @@ class ServiceItem(RegistryProperties):
|
||||||
rendered_slide = {
|
rendered_slide = {
|
||||||
'title': raw_slide['title'],
|
'title': raw_slide['title'],
|
||||||
'text': render_tags(page),
|
'text': render_tags(page),
|
||||||
|
'chords': render_tags(page, can_render_chords=True),
|
||||||
'verse': index,
|
'verse': index,
|
||||||
'footer': self.footer_html,
|
'footer': self.footer_html
|
||||||
}
|
}
|
||||||
self._rendered_slides.append(rendered_slide)
|
self._rendered_slides.append(rendered_slide)
|
||||||
display_slide = {
|
display_slide = {
|
||||||
|
|
|
@ -64,7 +64,8 @@ class UiIcons(metaclass=Singleton):
|
||||||
'authentication': {'icon': 'fa.exclamation-triangle', 'attr': 'red'},
|
'authentication': {'icon': 'fa.exclamation-triangle', 'attr': 'red'},
|
||||||
'address': {'icon': 'fa.book'},
|
'address': {'icon': 'fa.book'},
|
||||||
'back': {'icon': 'fa.step-backward'},
|
'back': {'icon': 'fa.step-backward'},
|
||||||
'backspace': {'icon': 'fa-times-circle'},
|
'backspace': {'icon': 'fa.times'},
|
||||||
|
# 'backspace': {'icon': 'fa.caret-square-o-left'},
|
||||||
'bible': {'icon': 'fa.book'},
|
'bible': {'icon': 'fa.book'},
|
||||||
'blank': {'icon': 'fa.times-circle'},
|
'blank': {'icon': 'fa.times-circle'},
|
||||||
'blank_theme': {'icon': 'fa.file-image-o'},
|
'blank_theme': {'icon': 'fa.file-image-o'},
|
||||||
|
@ -181,10 +182,10 @@ class UiIcons(metaclass=Singleton):
|
||||||
else:
|
else:
|
||||||
setattr(self, key, qta.icon(icon))
|
setattr(self, key, qta.icon(icon))
|
||||||
except Exception:
|
except Exception:
|
||||||
import sys
|
log.exception('Unexpected error for icon: {icon}'.format(icon=icon))
|
||||||
log.error('Unexpected error: %s' % sys.exc_info())
|
|
||||||
setattr(self, key, qta.icon('fa.exclamation-circle', color='red'))
|
setattr(self, key, qta.icon('fa.exclamation-circle', color='red'))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
log.exception('Unexpected error for icon: {icon}'.format(icon=icon))
|
||||||
setattr(self, key, qta.icon('fa.exclamation-circle', color='red'))
|
setattr(self, key, qta.icon('fa.exclamation-circle', color='red'))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -104,9 +104,12 @@ def test_controller_get_themes_retrieves_themes_list(flask_client, settings):
|
||||||
assert type(res) is list
|
assert type(res) is list
|
||||||
|
|
||||||
|
|
||||||
def test_controller_set_theme_does_not_accept_get(flask_client):
|
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')
|
||||||
res = flask_client.get('/api/v2/controller/theme')
|
res = flask_client.get('/api/v2/controller/theme')
|
||||||
assert res.status_code == 405
|
assert res.status_code == 200
|
||||||
|
assert res.get_json() == 'Default'
|
||||||
|
|
||||||
|
|
||||||
def test_controller_set_theme_aborts_if_no_theme(flask_client, settings):
|
def test_controller_set_theme_aborts_if_no_theme(flask_client, settings):
|
||||||
|
|
|
@ -101,7 +101,7 @@ def test_get_random_user_agent_default():
|
||||||
user_agent = get_random_user_agent()
|
user_agent = get_random_user_agent()
|
||||||
|
|
||||||
# THEN: The user agent is a Linux (or ChromeOS) user agent
|
# THEN: The user agent is a Linux (or ChromeOS) user agent
|
||||||
assert 'NetBSD'in user_agent, 'The user agent should be the default user agent'
|
assert 'NetBSD' in user_agent, 'The user agent should be the default user agent'
|
||||||
|
|
||||||
|
|
||||||
def test_get_web_page_no_url():
|
def test_get_web_page_no_url():
|
||||||
|
|
|
@ -95,5 +95,5 @@ class TestPluginManager(TestCase, TestMixin):
|
||||||
assert 'images' in plugin_names, 'There should be a "images" plugin'
|
assert 'images' in plugin_names, 'There should be a "images" plugin'
|
||||||
assert 'media' in plugin_names, 'There should be a "media" plugin'
|
assert 'media' in plugin_names, 'There should be a "media" plugin'
|
||||||
assert 'custom' in plugin_names, 'There should be a "custom" plugin'
|
assert 'custom' in plugin_names, 'There should be a "custom" plugin'
|
||||||
assert 'songusage'in plugin_names, 'There should be a "songusage" plugin'
|
assert 'songusage' in plugin_names, 'There should be a "songusage" plugin'
|
||||||
assert 'alerts' in plugin_names, 'There should be a "alerts" plugin'
|
assert 'alerts' in plugin_names, 'There should be a "alerts" plugin'
|
||||||
|
|
|
@ -24,7 +24,7 @@ Interface tests to test the themeManager class and related methods.
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
from openlp.core.common.utils import wait_for
|
from openlp.core.common.utils import wait_for, is_uuid
|
||||||
|
|
||||||
|
|
||||||
def test_wait_for(registry):
|
def test_wait_for(registry):
|
||||||
|
@ -42,3 +42,18 @@ def test_wait_for(registry):
|
||||||
|
|
||||||
# THEN: the functions got called
|
# THEN: the functions got called
|
||||||
assert mock_func.call_count == 2
|
assert mock_func.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_uuid_not_str():
|
||||||
|
"""Test that a non-string UUID returns False"""
|
||||||
|
assert not is_uuid(10), 'is_uuid() should return False when given something that is not a string'
|
||||||
|
|
||||||
|
|
||||||
|
def test_uuid_not_valid():
|
||||||
|
"""Test that an invalid string UUID returns False"""
|
||||||
|
assert not is_uuid('this is a string'), 'is_uuid() should return False when given an invalid string'
|
||||||
|
|
||||||
|
|
||||||
|
def test_uuid_valid():
|
||||||
|
"""Test that an valid string UUID returns True"""
|
||||||
|
assert is_uuid('2596ac84-9735-11ea-a665-8fa61362d04a'), 'is_uuid() should return True when given a valid UUID'
|
||||||
|
|
Loading…
Reference in New Issue