mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-04-29 10:10:19 +00:00
doc: dev: Update the tutorial to reflect latest API/code
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Veiko Aasa <veiko17@disroot.org>
This commit is contained in:
parent
838a2ede8c
commit
b378305f58
@ -26,7 +26,7 @@ External References
|
|||||||
|
|
||||||
#. `systemd System and Service Manager <https://www.freedesktop.org/wiki/Software/systemd/>`_
|
#. `systemd System and Service Manager <https://www.freedesktop.org/wiki/Software/systemd/>`_
|
||||||
|
|
||||||
#. `Bootstrap - CSS Library <http://getbootstrap.com/css/>`_
|
#. `Bootstrap - CSS Library <http://getbootstrap.com/>`_
|
||||||
|
|
||||||
#. `FreedomBox User Manual <https://wiki.debian.org/FreedomBox/Manual>`_
|
#. `FreedomBox User Manual <https://wiki.debian.org/FreedomBox/Manual>`_
|
||||||
|
|
||||||
|
|||||||
@ -6,17 +6,51 @@ App Module
|
|||||||
These methods are optionally provided by the module in which an app is
|
These methods are optionally provided by the module in which an app is
|
||||||
implemented and FreedomBox calls/uses them if they are present.
|
implemented and FreedomBox calls/uses them if they are present.
|
||||||
|
|
||||||
<app-module>.init()
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Optional. This method is called by FreedomBox soon after all the applications
|
|
||||||
are loaded. The ``init()`` call order guarantees that other applications that
|
|
||||||
this application depends on will be initialized before this application is
|
|
||||||
initialized.
|
|
||||||
|
|
||||||
<app-module>.depends
|
<app-module>.depends
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Optional. This module property must contain a list of all apps that this
|
Optional. This module property must contain a list of all apps that this
|
||||||
application depends on. The application is specified as string containing the
|
application depends on. The application is specified as string which is the
|
||||||
full module load path. For example, ``names``.
|
final part of the full module load path. For example, ``names``. Dependencies
|
||||||
|
are part of the :class:`~plinth.app.Info` component. Need for this attribute at
|
||||||
|
the module level will be removed in the future.
|
||||||
|
|
||||||
|
|
||||||
|
<app-module>.is_essential
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Optional. If an app must be installed and configured by FreedomBox without user
|
||||||
|
intervention, this attribute must be set to True. This attribute is part of the
|
||||||
|
:class:`~plinth.app.Info` component. Need for this attribute at the module level
|
||||||
|
will be removed in the future.
|
||||||
|
|
||||||
|
<app-module>.version
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Optional. Version number of an app. Increasing the version number of an app
|
||||||
|
triggers the setup() logic allowing the app to run upgrade scripts. This
|
||||||
|
attribute is part of the :class:`~plinth.app.Info` component. Need for this
|
||||||
|
attribute at the module level will be removed in the future.
|
||||||
|
|
||||||
|
<app-module>.managed_packages
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Optional. This must contain the list of all packages that this app deals with.
|
||||||
|
This is mostly needed to enforce better security. This information may be moved
|
||||||
|
to a separate component in the future.
|
||||||
|
|
||||||
|
<app-module>.managed_services
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Optional. This must contain the list of all services that this app deals with.
|
||||||
|
This is mostly needed to enforce better security. This information is part of
|
||||||
|
the :class:`~plinth.daemon.Daemon` component. Need for this attribute at the
|
||||||
|
module level will be removed in the future.
|
||||||
|
|
||||||
|
<app-module>.managed_paths
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Optional. This must contain the list of all file system paths that this app
|
||||||
|
deals with. This is mostly used by the
|
||||||
|
:class:`~plinth.modules.letsencrypt.components.LetsEncrypt` component to enforce
|
||||||
|
better security. This requirement may be removed in the future.
|
||||||
|
|||||||
@ -5,7 +5,9 @@ Part 4: Components
|
|||||||
|
|
||||||
Each :class:`~plinth.app.App` contains various :class:`~plinth.app.Component`
|
Each :class:`~plinth.app.App` contains various :class:`~plinth.app.Component`
|
||||||
components that each provide one small functionality needed by the app. Each of
|
components that each provide one small functionality needed by the app. Each of
|
||||||
these components are instantiated and added to the app as children.
|
these components are instantiated and added to the app as children. The
|
||||||
|
:class:`~plinth.menu.Menu` object added in the previous step is one such
|
||||||
|
component.
|
||||||
|
|
||||||
Providing basic information about the app
|
Providing basic information about the app
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -17,6 +19,8 @@ function normally.
|
|||||||
|
|
||||||
from plinth import app as app_module
|
from plinth import app as app_module
|
||||||
|
|
||||||
|
from . import manifest
|
||||||
|
|
||||||
class TransmissionApp(app_module.App):
|
class TransmissionApp(app_module.App):
|
||||||
...
|
...
|
||||||
|
|
||||||
@ -28,15 +32,45 @@ function normally.
|
|||||||
icon_filename='transmission',
|
icon_filename='transmission',
|
||||||
short_description=_('BitTorrent Web Client'),
|
short_description=_('BitTorrent Web Client'),
|
||||||
description=description,
|
description=description,
|
||||||
manual_page='Transmission', clients=clients)
|
manual_page='Transmission',
|
||||||
|
clients=manifest.clients,
|
||||||
|
donation_url='https://transmissionbt.com/donate/')
|
||||||
self.add(info)
|
self.add(info)
|
||||||
|
|
||||||
The first argument is app_id that is same as the ID for the app. The version is
|
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
|
the version number for this app that must be incremented whenever setup() method
|
||||||
needs to be called again. name, icon_filename, short_description, description,
|
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
|
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`
|
page. The donation_url encourages our users to contribute to upstream projects
|
||||||
class documentation.
|
in order ensure their long term sustainability. More information about the
|
||||||
|
parameters is available in :class:`~plinth.app.Info` class documentation.
|
||||||
|
|
||||||
|
The description of app should provide basic information on what the app is about
|
||||||
|
and how to use it. It is impractical, however, to explain everything about the
|
||||||
|
app in a few short paragraphs. So, we need to write a page about the app in the
|
||||||
|
FreedomBox manual. This page will be available to the users from within the
|
||||||
|
FreedomBox web interface. To make this happen, let us write a `manual page entry
|
||||||
|
<https://wiki.debian.org/FreedomBox/Manual/Transmission>`_ for our app in the
|
||||||
|
`FreedomBox Wiki <https://wiki.debian.org/FreedomBox/Manual>`_ and then provide
|
||||||
|
a link to it from app page.
|
||||||
|
|
||||||
|
It would be helpful to our users if we can show how they can use our app. If
|
||||||
|
there are desktop and mobile clients that can used to access our service, we
|
||||||
|
need to list them and present them. Let's add this information to
|
||||||
|
``manifest.py``.
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
clients = [{
|
||||||
|
'name': _('Transmission'),
|
||||||
|
'platforms': [{
|
||||||
|
'type': 'web',
|
||||||
|
'url': '/transmission'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
|
||||||
|
Since our app is a simple web application with no clients needed, we just list
|
||||||
|
that.
|
||||||
|
|
||||||
Managing a daemon
|
Managing a daemon
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@ -64,18 +64,21 @@ provide options to the user. Add the following to ``forms.py``.
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
class TransmissionForm(forms.Form): # pylint: disable=W0232
|
class TransmissionForm(DirectorySelectForm): # pylint: disable=W0232
|
||||||
"""Transmission configuration form"""
|
"""Transmission configuration form"""
|
||||||
download_dir = forms.CharField(
|
|
||||||
label='Download directory',
|
|
||||||
help_text='Directory where downloads are saved. If you change the '
|
|
||||||
'default directory, ensure that the new directory exists '
|
|
||||||
'and is writable by "debian-transmission" user.')
|
|
||||||
|
|
||||||
This creates a Django form that shows a single option to set the download
|
def __init__(self, *args, **kw):
|
||||||
directory for our Transmission app. This is how a regular Django form is built.
|
validator = DirectoryValidator(username=SYSTEM_USER,
|
||||||
See :doc:`Django Forms documentation <django:topics/forms/index>` for more
|
check_creatable=True)
|
||||||
information.
|
super(TransmissionForm,
|
||||||
|
self).__init__(title=_('Download directory'),
|
||||||
|
default='/var/lib/transmission-daemon/downloads',
|
||||||
|
validator=validator, *args, **kw)
|
||||||
|
|
||||||
|
This uses a utility provided by the framework and creates a Django form that
|
||||||
|
shows a single option to set the download directory for our Transmission app.
|
||||||
|
This is similar to how a regular Django form is built. See :doc:`Django Forms
|
||||||
|
documentation <django:topics/forms/index>` for more information.
|
||||||
|
|
||||||
.. tip: Too many options
|
.. tip: Too many options
|
||||||
|
|
||||||
@ -100,8 +103,9 @@ the user submits it. Let us implement that in ``views.py``.
|
|||||||
from .forms import TransmissionForm
|
from .forms import TransmissionForm
|
||||||
|
|
||||||
class TransmissionAppView(views.AppView):
|
class TransmissionAppView(views.AppView):
|
||||||
...
|
"""Serve configuration page."""
|
||||||
form_class = TransmissionForm
|
form_class = TransmissionForm
|
||||||
|
app_id = 'transmission'
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""Get the current settings from Transmission server."""
|
"""Get the current settings from Transmission server."""
|
||||||
@ -109,22 +113,18 @@ the user submits it. Let us implement that in ``views.py``.
|
|||||||
configuration = actions.superuser_run('transmission',
|
configuration = actions.superuser_run('transmission',
|
||||||
['get-configuration'])
|
['get-configuration'])
|
||||||
configuration = json.loads(configuration)
|
configuration = json.loads(configuration)
|
||||||
status.update({
|
status['storage_path'] = configuration['download-dir']
|
||||||
key.translate(str.maketrans({
|
status['hostname'] = socket.gethostname()
|
||||||
'-': '_'
|
|
||||||
})): value
|
|
||||||
for key, value in configuration.items()
|
|
||||||
})
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Apply the changes submitted in the form."""
|
"""Apply the changes submitted in the form."""
|
||||||
old_status = form.initial
|
old_status = form.initial
|
||||||
new_status = form.cleaned_data
|
new_status = form.cleaned_data
|
||||||
|
if old_status['storage_path'] != new_status['storage_path']:
|
||||||
if old_status['download_dir'] != new_status['download_dir']:
|
|
||||||
new_configuration = {
|
new_configuration = {
|
||||||
'download-dir': new_status['download_dir'],
|
'download-dir': new_status['storage_path'],
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.superuser_run('transmission', ['merge-configuration'],
|
actions.superuser_run('transmission', ['merge-configuration'],
|
||||||
|
|||||||
@ -32,9 +32,11 @@ Coding standards
|
|||||||
|
|
||||||
For readability and easy collaboration it is important to follow common coding
|
For readability and easy collaboration it is important to follow common coding
|
||||||
standards. FreedomBox uses the Python coding standards and uses the ``pylint``
|
standards. FreedomBox uses the Python coding standards and uses the ``pylint``
|
||||||
and ``flake8`` tools to check if the there are any violations. Run these tools
|
and ``flake8`` tools to check if the there are any violations. ``yapf`` and
|
||||||
on our application and fix any errors and warnings. Better yet, integrate these
|
``isort`` tools are used to automatically format the code to ensure that all
|
||||||
tools into your favorite IDE for on-the-fly checking.
|
developers produce similarly formatted code. Run these tools on our application
|
||||||
|
and fix any errors and warnings. Better yet, integrate these tools into your
|
||||||
|
favorite IDE for on-the-fly checking.
|
||||||
|
|
||||||
For the most part, the code we have written so far, is already compliant with
|
For the most part, the code we have written so far, is already compliant with
|
||||||
the coding standards. This includes variable/method naming, indentation,
|
the coding standards. This includes variable/method naming, indentation,
|
||||||
|
|||||||
@ -3,67 +3,6 @@
|
|||||||
Part 7: Other Changes
|
Part 7: Other Changes
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Showing information about app clients
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
It would be helpful to our users if we can show how they can use our app. If
|
|
||||||
there are desktop and mobile clients that can used to access our service, we
|
|
||||||
need to list them and present them. Let's add this information to
|
|
||||||
``manifest.py``.
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
clients = [{
|
|
||||||
'name': _('Transmission'),
|
|
||||||
'platforms': [{
|
|
||||||
'type': 'web',
|
|
||||||
'url': '/transmission'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
|
|
||||||
Since our app is a simple web application with no clients needed, we just list
|
|
||||||
that. We need to include this into the main app view. In ``__init__.py``, add:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
from . import manifest
|
|
||||||
|
|
||||||
In ``views.py``, add:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
from plinth.modules import transmission
|
|
||||||
|
|
||||||
class TransmissionAppView(views.AppView):
|
|
||||||
...
|
|
||||||
clients = transmission.clients
|
|
||||||
|
|
||||||
Writing a manual page
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The description of app should provide basic information on what the app is about
|
|
||||||
and how to use it. It is impractical, however, to explain everything about the
|
|
||||||
app in a few short paragraphs. So, we need to write a page about the app in the
|
|
||||||
FreedomBox manual. This page will be available to the users from within the
|
|
||||||
FreedomBox web interface. To make this happen, let us write a `manual page entry
|
|
||||||
<https://wiki.debian.org/FreedomBox/Manual/Transmission>`_ for our app in the
|
|
||||||
`FreedomBox Wiki <https://wiki.debian.org/FreedomBox/Manual>`_ and then provide
|
|
||||||
a link to it from app page. In ``__init__.py``, add:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
manual_page = 'Transmission'
|
|
||||||
|
|
||||||
Then, in ``views.py``, add:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
from plinth.modules import transmission
|
|
||||||
|
|
||||||
class TransmissionAppView(views.AppView):
|
|
||||||
...
|
|
||||||
manual_page = transmission.manual_page
|
|
||||||
|
|
||||||
Creating diagnostics
|
Creating diagnostics
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@ -139,16 +78,17 @@ the Django's localization methods to make that happen.
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
name = _('Transmission')
|
class TransmissionApp(app_module.App):
|
||||||
|
...
|
||||||
|
|
||||||
short_description = _('BitTorrent Web Client')
|
def __init__(self):
|
||||||
|
...
|
||||||
|
|
||||||
description = [
|
info = app_module.Info(...
|
||||||
_('BitTorrent is a peer-to-peer file sharing protocol. '
|
name=_('Transmission'),
|
||||||
'Transmission daemon handles Bitorrent file sharing. Note that '
|
...
|
||||||
'BitTorrent is not anonymous.'),
|
short_description=_('BitTorrent Web Client'),
|
||||||
_('Access the web interface at <a href="/transmission">/transmission</a>.')
|
...)
|
||||||
]
|
|
||||||
|
|
||||||
Notice that the app's name, description, etc. are wrapped in the ``_()`` method
|
Notice that the app's name, description, etc. are wrapped in the ``_()`` method
|
||||||
call. This needs to be done for the rest of our app. We use the
|
call. This needs to be done for the rest of our app. We use the
|
||||||
|
|||||||
@ -67,7 +67,7 @@ Creating the App class
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In the FreedomBox framework, each app must be a class derived from the
|
In the FreedomBox framework, each app must be a class derived from the
|
||||||
:class:`plinth.app.App`. Let us to that in ``__init__.py``. We will fill up the
|
:class:`plinth.app.App`. Let us do that in ``__init__.py``. We will fill up the
|
||||||
class later.
|
class later.
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|||||||
@ -36,26 +36,15 @@ a link in FreedomBox web interface. Let us add a link in the apps list. In
|
|||||||
|
|
||||||
from plinth.menu import main_menu
|
from plinth.menu import main_menu
|
||||||
|
|
||||||
name = 'Transmission'
|
|
||||||
|
|
||||||
short_description = 'BitTorrent Web Client'
|
|
||||||
|
|
||||||
description = [
|
|
||||||
'BitTorrent is a peer-to-peer file sharing protocol. '
|
|
||||||
'Transmission daemon handles Bitorrent file sharing. Note that '
|
|
||||||
'BitTorrent is not anonymous.',
|
|
||||||
'Access the web interface at <a href="/transmission">/transmission</a>.'
|
|
||||||
]
|
|
||||||
|
|
||||||
class TransmissionApp(app_module.App):
|
class TransmissionApp(app_module.App):
|
||||||
...
|
...
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
menu_item = menu.Menu('menu-transmission', name, short_description,
|
menu_item = menu.Menu('menu-transmission', 'Transmission',
|
||||||
'transmission', 'transmission:index',
|
'BitTorrrent Web Client', 'transmission',
|
||||||
parent_url_name='apps')
|
'transmission:index', parent_url_name='apps')
|
||||||
self.add(menu_item)
|
self.add(menu_item)
|
||||||
|
|
||||||
What this does is add a menu item component into the our app. In FreedomBox
|
What this does is add a menu item component into the our app. In FreedomBox
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user