customization: Serve custom shortcuts through the REST API

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalapati 2018-09-28 11:52:06 +05:30 committed by James Valleroy
parent e1d3497886
commit 5e06017e5c
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
10 changed files with 203 additions and 8 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ debian/plinth.preinst.debhelper
debian/plinth.prerm.debhelper debian/plinth.prerm.debhelper
debian/plinth.substvars debian/plinth.substvars
debian/plinth/ debian/plinth/
*.pytest_cache/

0
custom-shortcuts.json Normal file
View File

View File

1
debian/control vendored
View File

@ -31,6 +31,7 @@ Build-Depends: debhelper (>= 11~)
, python3-django-stronghold (>= 0.3.0) , python3-django-stronghold (>= 0.3.0)
, python3-gi , python3-gi
, python3-psutil , python3-psutil
, python3-pytest
, python3-requests , python3-requests
, python3-ruamel.yaml , python3-ruamel.yaml
, python3-setuptools , python3-setuptools

View File

@ -20,12 +20,13 @@ FreedomBox app for api for android app.
import copy import copy
import json import json
import os
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse from django.http import HttpResponse
from django.templatetags.static import static from django.templatetags.static import static
from plinth import frontpage, module_loader from plinth import cfg, frontpage, module_loader
from plinth.modules import names from plinth.modules import names
@ -44,14 +45,31 @@ def shortcuts(request, **kwargs):
"""API view to return the list of frontpage services.""" """API view to return the list of frontpage services."""
# XXX: Get the module (or module name) from shortcut properly. # XXX: Get the module (or module name) from shortcut properly.
username = str(request.user) if request.user.is_authenticated else None username = str(request.user) if request.user.is_authenticated else None
response = get_shortcuts_as_json(username)
return HttpResponse(
json.dumps(response, cls=DjangoJSONEncoder),
content_type='application/json')
def get_shortcuts_as_json(username=None):
shortcuts = [ shortcuts = [
_get_shortcut_data(shortcut['id'].split('_')[0], shortcut) _get_shortcut_data(shortcut['id'].split('_')[0], shortcut)
for shortcut in frontpage.get_shortcuts(username) for shortcut in frontpage.get_shortcuts(username)
] ]
response = {'shortcuts': shortcuts} custom_shortcuts = get_custom_shortcuts()
return HttpResponse( if custom_shortcuts:
json.dumps(response, cls=DjangoJSONEncoder), shortcuts += custom_shortcuts['shortcuts']
content_type='application/json') return {'shortcuts': shortcuts}
def get_custom_shortcuts():
cfg_dir = os.path.dirname(cfg.config_file)
shortcuts_file = os.path.join(cfg_dir, 'custom-shortcuts.json')
if os.path.isfile(shortcuts_file) and os.stat(shortcuts_file).st_size:
with open(shortcuts_file) as shortcuts:
custom_shortcuts = json.load(shortcuts)
return custom_shortcuts
return None
def _get_shortcut_data(module_name, shortcut): def _get_shortcut_data(module_name, shortcut):

View File

@ -0,0 +1 @@
{"shortcuts": [{"name": "NextCloud", "short_description": "File Hosting Service", "description": "Nextcloud is a suite of client-server software for creating and using file hosting services.", "icon_url": "/plinth/custom/static/themes/default/icons/nextcloud.png", "clients": [{"name": "nextcloud", "platforms": [{"type": "web", "url": "/nextcloud"}]}]}]}

View File

@ -0,0 +1,46 @@
[Path]
# directory locations
file_root = %(root)s
config_dir = %(file_root)s/data/etc/plinth
data_dir = %(file_root)s/data/var/lib/plinth
log_dir = %(file_root)s/data/var/log/plinth
server_dir = /plinth
actions_dir = %(file_root)s/actions
doc_dir = %(file_root)s/doc
custom_static_dir = %(file_root)s/data/var/www/plinth/custom/static
# file locations
store_file = %(data_dir)s/plinth.sqlite3
status_log_file = %(log_dir)s/status.log
access_log_file = %(log_dir)s/access.log
[Network]
host = 127.0.0.1
port = 8000
# Enable the following only if Plinth is behind a proxy server. The
# proxy server should properly clean and the following HTTP headers:
# X-Forwarded-Host
# X-Forwarded-Proto
# If you enable these unnecessarily, this will lead to serious security
# problems. For more information, see
# https://docs.djangoproject.com/en/1.7/ref/settings/
#
# These are enabled by default in Plinth because the default
# configuration allows only connections from localhost
#
# Leave the values blank to disable
use_x_forwarded_host = True
secure_proxy_ssl_header = HTTP_X_FORWARDED_PROTO
[Misc]
box_name = FreedomBox
# The danube_edition changes the firstboot process and offers entering a
# voucher for a freedombox.me sub-domain. This functionality requires
# additional debian packages to be installed:
#
# pagekite, python3-requests
#
# They are not added as dependencies to keep the normal installation images
# lean, but make sure to add them if you want to build danube-edition images.
danube_edition = False

View File

@ -115,10 +115,12 @@ class TestCfg(unittest.TestCase):
"""Compare two sets of configuration values.""" """Compare two sets of configuration values."""
# Note that the count of items within each section includes the number # Note that the count of items within each section includes the number
# of default items (1, for 'root'). # of default items (1, for 'root').
self.assertEqual(11, len(parser.items('Path'))) self.assertEqual(12, len(parser.items('Path')))
self.assertEqual(parser.get('Path', 'root'), cfg.root) self.assertEqual(parser.get('Path', 'root'), cfg.root)
self.assertEqual(parser.get('Path', 'file_root'), cfg.file_root) self.assertEqual(parser.get('Path', 'file_root'), cfg.file_root)
self.assertEqual(parser.get('Path', 'config_dir'), cfg.config_dir) self.assertEqual(parser.get('Path', 'config_dir'), cfg.config_dir)
self.assertEqual(
parser.get('Path', 'custom_static_dir'), cfg.custom_static_dir)
self.assertEqual(parser.get('Path', 'data_dir'), cfg.data_dir) self.assertEqual(parser.get('Path', 'data_dir'), cfg.data_dir)
self.assertEqual(parser.get('Path', 'store_file'), cfg.store_file) self.assertEqual(parser.get('Path', 'store_file'), cfg.store_file)
self.assertEqual(parser.get('Path', 'actions_dir'), cfg.actions_dir) self.assertEqual(parser.get('Path', 'actions_dir'), cfg.actions_dir)

View File

@ -0,0 +1,123 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Test module for custom shortcuts.
"""
import json
import os
import pytest
from plinth import cfg
from plinth.modules.api.views import get_shortcuts_as_json
TEST_CONFIG_DIR = \
os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data')
CUSTOM_SHORTCUTS_FILE = os.path.join(TEST_CONFIG_DIR,
'etc/plinth/custom-shortcuts.json')
NEXTCLOUD_SHORTCUT = {
'name':
'NextCloud',
'short_description':
'File Hosting Service',
'description':
'Nextcloud is a suite of client-server software for creating '
'and using file hosting services.',
'icon_url':
'/plinth/custom/static/themes/default/icons/nextcloud.png',
'clients': [{
'name': 'nextcloud',
'platforms': [{
'type': 'web',
'url': '/nextcloud'
}]
}]
}
def setup_module(module):
"""Load test configuration."""
root = os.path.dirname(os.path.realpath(__file__))
cfg_file = os.path.join(TEST_CONFIG_DIR, 'etc', 'plinth', 'plinth.config')
cfg.read(cfg_file, root)
def teardown_module(module):
"""Reset configuration."""
cfg.read()
@pytest.fixture
def no_custom_shortcuts_file():
"""Delete the custom_shortcuts file."""
if os.path.exists(CUSTOM_SHORTCUTS_FILE):
os.remove(CUSTOM_SHORTCUTS_FILE)
@pytest.fixture
def blank_custom_shortcuts_file():
"""Create a blank shortcuts file."""
open(CUSTOM_SHORTCUTS_FILE, 'w').close()
@pytest.fixture
def empty_custom_shortcuts():
"""Create a custom_shortcuts file with an empty list of shortcuts."""
with open(CUSTOM_SHORTCUTS_FILE, 'w') as shortcuts_file:
shortcuts = {'shortcuts': []}
json.dump(shortcuts, shortcuts_file)
@pytest.fixture
def nextcloud_shortcut():
with open(CUSTOM_SHORTCUTS_FILE, 'w') as shortcuts_file:
shortcuts = {'shortcuts': [NEXTCLOUD_SHORTCUT]}
json.dump(shortcuts, shortcuts_file)
def test_shortcuts_api_with_no_custom_shortcuts_file(no_custom_shortcuts_file):
get_shortcuts_as_json()
def test_shortcuts_api_with_blank_custom_shortcuts_file(
blank_custom_shortcuts_file):
get_shortcuts_as_json()
def test_shortcuts_api_with_empty_custom_shortcuts_list(
empty_custom_shortcuts):
get_shortcuts_as_json()
def test_shortcuts_api_with_custom_nextcloud_shortcut(nextcloud_shortcut):
shortcuts = get_shortcuts_as_json()
assert len(shortcuts['shortcuts']) >= 1
assert any(
shortcut['name'] == 'NextCloud' for shortcut in shortcuts['shortcuts'])
def test_retrieved_custom_shortcut_from_api_is_correct(nextcloud_shortcut):
shortcuts = get_shortcuts_as_json()
nextcloud_shortcut = [
shortcut for shortcut in shortcuts['shortcuts']
if shortcut['name'] == 'NextCloud'
]
assert nextcloud_shortcut
assert nextcloud_shortcut[0] == NEXTCLOUD_SHORTCUT

View File

@ -252,7 +252,10 @@ setuptools.setup(
('/usr/share/polkit-1/rules.d', ('/usr/share/polkit-1/rules.d',
['data/usr/share/polkit-1/rules.d/50-plinth.rules']), ['data/usr/share/polkit-1/rules.d/50-plinth.rules']),
('/usr/share/man/man1', ['doc/plinth.1']), ('/usr/share/man/man1', ['doc/plinth.1']),
('/etc/plinth', ['data/etc/plinth/plinth.config']), ('/etc/plinth', [
'data/etc/plinth/plinth.config',
'data/etc/plinth/custom-shortcuts.json'
]),
('/usr/share/augeas/lenses', ('/usr/share/augeas/lenses',
glob.glob('data/usr/share/augeas/lenses/*.aug')), glob.glob('data/usr/share/augeas/lenses/*.aug')),
('/usr/share/augeas/lenses/tests', ('/usr/share/augeas/lenses/tests',
@ -271,7 +274,7 @@ setuptools.setup(
'data/var/www/plinth/custom/static/themes/default/icons/.gitignore', 'data/var/www/plinth/custom/static/themes/default/icons/.gitignore',
# TODO Cannot be copied since a symlink is not a regular file # TODO Cannot be copied since a symlink is not a regular file
# 'data/var/www/plinth/custom/static/theme', # 'data/var/www/plinth/custom/static/theme',
]) ]),
], ],
cmdclass={ cmdclass={
'install': CustomInstall, 'install': CustomInstall,