mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +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/>`_
|
||||
|
||||
#. `Bootstrap - CSS Library <http://getbootstrap.com/css/>`_
|
||||
#. `Bootstrap - CSS Library <http://getbootstrap.com/>`_
|
||||
|
||||
#. `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
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Optional. This module property must contain a list of all apps that this
|
||||
application depends on. The application is specified as string containing the
|
||||
full module load path. For example, ``names``.
|
||||
application depends on. The application is specified as string which is the
|
||||
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`
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -17,6 +19,8 @@ function normally.
|
||||
|
||||
from plinth import app as app_module
|
||||
|
||||
from . import manifest
|
||||
|
||||
class TransmissionApp(app_module.App):
|
||||
...
|
||||
|
||||
@ -28,15 +32,45 @@ function normally.
|
||||
icon_filename='transmission',
|
||||
short_description=_('BitTorrent Web Client'),
|
||||
description=description,
|
||||
manual_page='Transmission', clients=clients)
|
||||
manual_page='Transmission',
|
||||
clients=manifest.clients,
|
||||
donation_url='https://transmissionbt.com/donate/')
|
||||
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.
|
||||
page. The donation_url encourages our users to contribute to upstream projects
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -64,18 +64,21 @@ provide options to the user. Add the following to ``forms.py``.
|
||||
from django import forms
|
||||
|
||||
|
||||
class TransmissionForm(forms.Form): # pylint: disable=W0232
|
||||
class TransmissionForm(DirectorySelectForm): # pylint: disable=W0232
|
||||
"""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
|
||||
directory for our Transmission app. This is how a regular Django form is built.
|
||||
See :doc:`Django Forms documentation <django:topics/forms/index>` for more
|
||||
information.
|
||||
def __init__(self, *args, **kw):
|
||||
validator = DirectoryValidator(username=SYSTEM_USER,
|
||||
check_creatable=True)
|
||||
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
|
||||
|
||||
@ -100,8 +103,9 @@ the user submits it. Let us implement that in ``views.py``.
|
||||
from .forms import TransmissionForm
|
||||
|
||||
class TransmissionAppView(views.AppView):
|
||||
...
|
||||
"""Serve configuration page."""
|
||||
form_class = TransmissionForm
|
||||
app_id = 'transmission'
|
||||
|
||||
def get_initial(self):
|
||||
"""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',
|
||||
['get-configuration'])
|
||||
configuration = json.loads(configuration)
|
||||
status.update({
|
||||
key.translate(str.maketrans({
|
||||
'-': '_'
|
||||
})): value
|
||||
for key, value in configuration.items()
|
||||
})
|
||||
status['storage_path'] = configuration['download-dir']
|
||||
status['hostname'] = socket.gethostname()
|
||||
|
||||
return status
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Apply the changes submitted in the form."""
|
||||
old_status = form.initial
|
||||
new_status = form.cleaned_data
|
||||
|
||||
if old_status['download_dir'] != new_status['download_dir']:
|
||||
if old_status['storage_path'] != new_status['storage_path']:
|
||||
new_configuration = {
|
||||
'download-dir': new_status['download_dir'],
|
||||
'download-dir': new_status['storage_path'],
|
||||
}
|
||||
|
||||
actions.superuser_run('transmission', ['merge-configuration'],
|
||||
|
||||
@ -32,9 +32,11 @@ Coding standards
|
||||
|
||||
For readability and easy collaboration it is important to follow common coding
|
||||
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
|
||||
on our application and fix any errors and warnings. Better yet, integrate these
|
||||
tools into your favorite IDE for on-the-fly checking.
|
||||
and ``flake8`` tools to check if the there are any violations. ``yapf`` and
|
||||
``isort`` tools are used to automatically format the code to ensure that all
|
||||
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
|
||||
the coding standards. This includes variable/method naming, indentation,
|
||||
|
||||
@ -3,67 +3,6 @@
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -139,16 +78,17 @@ the Django's localization methods to make that happen.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
name = _('Transmission')
|
||||
class TransmissionApp(app_module.App):
|
||||
...
|
||||
|
||||
short_description = _('BitTorrent Web Client')
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
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>.')
|
||||
]
|
||||
info = app_module.Info(...
|
||||
name=_('Transmission'),
|
||||
...
|
||||
short_description=_('BitTorrent Web Client'),
|
||||
...)
|
||||
|
||||
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
|
||||
|
||||
@ -67,7 +67,7 @@ Creating the App class
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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.
|
||||
|
||||
.. 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
|
||||
|
||||
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):
|
||||
...
|
||||
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
menu_item = menu.Menu('menu-transmission', name, short_description,
|
||||
'transmission', 'transmission:index',
|
||||
parent_url_name='apps')
|
||||
menu_item = menu.Menu('menu-transmission', 'Transmission',
|
||||
'BitTorrrent Web Client', 'transmission',
|
||||
'transmission:index', parent_url_name='apps')
|
||||
self.add(menu_item)
|
||||
|
||||
What this does is add a menu item component into the our app. In FreedomBox
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user