From 72ce23e3772b8d10a21fcb5d6390a756c07223ce Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 10 Feb 2020 17:41:37 -0800 Subject: [PATCH] app: Introduce Info component to store basic app information Reviewed-by: James Valleroy --- doc/dev/reference/components/index.rst | 1 + doc/dev/reference/components/info.rst | 7 +++ doc/dev/tutorial/components.rst | 35 +++++++++++ plinth/app.py | 87 ++++++++++++++++++++++++++ plinth/tests/test_app.py | 36 ++++++++++- 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 doc/dev/reference/components/info.rst diff --git a/doc/dev/reference/components/index.rst b/doc/dev/reference/components/index.rst index 16c03c09f..3e92003b7 100644 --- a/doc/dev/reference/components/index.rst +++ b/doc/dev/reference/components/index.rst @@ -6,6 +6,7 @@ Components .. toctree:: :caption: Available components: + info menu daemon firewall diff --git a/doc/dev/reference/components/info.rst b/doc/dev/reference/components/info.rst new file mode 100644 index 000000000..827430fed --- /dev/null +++ b/doc/dev/reference/components/info.rst @@ -0,0 +1,7 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +Info +^^^^ + +.. autoclass:: plinth.app.Info + :members: diff --git a/doc/dev/tutorial/components.rst b/doc/dev/tutorial/components.rst index a743aa31a..c22b3eaaf 100644 --- a/doc/dev/tutorial/components.rst +++ b/doc/dev/tutorial/components.rst @@ -3,6 +3,41 @@ Part 4: Components ------------------ +Each :class:`~plinth.app.App` contains various :class:`~plinth.app.Component` +components that each provide one small functionality needed by the app. Each of +these components are instantiated and added to the app as children. + +Providing basic information about the app +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We need to provide some basic information about the application for the app to +function normally. + +.. code-block:: python3 + + from plinth import app as app_module + + class TransmissionApp(app_module.App): + ... + + def __init__(self): + ... + + info = app_module.Info(app_id=self.app_id, version=1, + name=_('Transmission'), + icon_filename='transmission', + short_description=_('BitTorrent Web Client'), + description=description, + manual_page='Transmission', clients=clients) + self.add(info) + +The first argument is app_id that is same as the ID for the app. The version is +the version number for this app that must be incremented whenever setup() method +needs to be called again. name, icon_filename, short_description, description, +manual_page and clients provide information that is shown on the app's main +page. More information the parameters is available in :class:`~plinth.app.Info` +class documentation. + Managing a daemon ^^^^^^^^^^^^^^^^^ diff --git a/plinth/app.py b/plinth/app.py index 64097b359..87ffa235c 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -243,3 +243,90 @@ class LeaderComponent(Component): def is_enabled(self): """Return if the component is enabled.""" raise NotImplementedError + + +class Info(FollowerComponent): + """Component to capture basic information about an app.""" + def __init__(self, app_id, version, is_essential=False, depends=None, + name=None, icon=None, icon_filename=None, + short_description=None, description=None, manual_page=None, + clients=None): + """Store the basic properties of an app as a component. + + Each app must contain at least one component of this type to provide + basic information about the app such as it's version number. + + Instead of polluting the list of properties of an app, this component + stores them separately. This component can also be safely passed around + to template etc. without exposing the methods of an app and without + creating unnecessarily cyclic dependencies. + + 'app_id' must be the unique ID of the app to which this information + belongs. + + 'version' is the monotonically increasing positive integer starting at + 1. It represents the version number of the app. It is used by the setup + mechanism. When an app's version number is increased, the setup + mechanism assumes that the setup() method of the app needs to run + again. This is used to upgrade/change configuration/setup of a app + when a new version of the app is deployed on users' machine. + + 'is_essential' is a boolean that marks the app as mandatory for the + basic functions of the system. If True, this app will be installed and + setup during the first run of FreedomBox even before first setup wizard + is shown to the user. + + 'depends' is the list of other apps that this app depends on. Apps from + this list are guaranteed to be initialized before initializing the app + to which this component belongs. + + 'name' is the user visible name of this app. It is shown as the title + of the app in the list of apps and when viewing app details. It should + be a lazily translated Django string. + + 'icon' is the name of icon to use with this app from a predetermined + list of icons. This is currently an icon class name from the Fork + Awesome font. It is used when showing the app in the System section. + Each app typically has either an 'icon' or 'icon_filename' property + set. + + 'icon_filename' is the name of the icon file, without the suffix, to be + used with this app. A .svg file (used in the web interface) and a .png + file (currently used by Android App) must be provided by the app. It is + used in the primary app page and on the app listing page. Each app + typically has either an 'icon' or 'icon_filename' property set. + + 'short_description' is the user visible generic name of the app. For + example, for the 'Tor' app the short description is 'Anonymity + Network'. It is shown along with the name of the app in the list of + apps and when viewing the app's main page. It should be a lazily + translated Django string. + + 'description' is the user visible full description of the app. It is + shown along in the app page along with other app details. It should be + a list of lazily translated Django strings. Each string is rendered as + a paragraph on the page. It may contain HTML tags to provide links + to external content. + + 'manual_page' is the optional name of the page for this app in the user + manual. If provided, a 'Learn more...' link appears in the app page for + this app. + + 'clients' is the list of applications that can be used with the + services provided by this app. This is used to suggest installation of + compatible clients on desktop, web and mobile. This is a list of + dictionaries who structure is documented in plinth.clients. + + """ + self.component_id = app_id + '-info' + self.app_id = app_id + self.version = version + self.is_essential = is_essential + self.depends = depends or [] + self.name = name + self.icon = icon + self.icon_filename = icon_filename + self.short_description = short_description + self.description = description + self.manual_page = manual_page + self.clients = clients diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py index b91332251..e014b3ca7 100644 --- a/plinth/tests/test_app.py +++ b/plinth/tests/test_app.py @@ -23,7 +23,7 @@ from unittest.mock import patch import pytest -from plinth.app import App, Component, FollowerComponent, LeaderComponent +from plinth.app import App, Component, FollowerComponent, Info, LeaderComponent class AppTest(App): @@ -282,3 +282,37 @@ def test_leader_component_is_enabled(): component = LeaderComponent('test-leader-1') with pytest.raises(NotImplementedError): assert component.is_enabled() + + +def test_info_initialization_without_args(): + """Test initializing the Info component without arguments.""" + info = Info('test-app', 3) + assert info.component_id == 'test-app-info' + assert info.app_id == 'test-app' + assert info.version == 3 + assert not info.is_essential + assert info.depends == [] + assert info.name is None + assert info.icon is None + assert info.icon_filename is None + assert info.short_description is None + assert info.description is None + assert info.manual_page is None + assert info.clients is None + + +def test_info_initialization_with_args(): + """Test initializing the Info component with arguments.""" + info = Info('test-app', 3, is_essential=True, depends=['test-app-2'], + name='Test App', icon='fa-test', icon_filename='test-icon', + short_description='For Test', description='Test description', + manual_page='Test', clients=['test']) + assert info.is_essential + assert info.depends == ['test-app-2'] + assert info.name == 'Test App' + assert info.icon == 'fa-test' + assert info.icon_filename == 'test-icon' + assert info.short_description == 'For Test' + assert info.description == 'Test description' + assert info.manual_page == 'Test' + assert info.clients == ['test']