doc: Add developer documentation using Sphinx

- This is completely reworked but based on /Developer page in the FreedomBox
  Manual.

- This documentation can be made available as static site on
  https://docs.freedombox.org and the /Developer page in the FreedomBox Manual
  can be dropped.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2019-10-25 23:32:17 -07:00 committed by James Valleroy
parent 108f7b8d46
commit 80498919fb
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
32 changed files with 1655 additions and 0 deletions

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ doc/manual/*/*.html
doc/manual/*/*.xml
!doc/manual/*/*.raw.xml
doc/plinth.1
doc/dev/_build
\#*
.#*
*~

3
debian/copyright vendored
View File

@ -10,6 +10,9 @@ Copyright: 2011-2019 FreedomBox Authors
License: AGPL-3+
Files: doc/*.xml
doc/*.rst
doc/*.png
doc/*.jpg
Copyright: 2011-2019 FreedomBox Authors
License: CC-BY-SA-4.0

41
doc/dev/Makefile Normal file
View File

@ -0,0 +1,41 @@
#
# 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/>.
#
#
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXAUTOBUILD = sphinx-autobuild
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
livehtml: Makefile
@$(SPHINXAUTOBUILD) -b html -r "\\.#.*" $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html"

0
doc/dev/_static/.gitkeep Normal file
View File

View File

@ -0,0 +1,15 @@
{%- extends "alabaster/layout.html" %}
{%- block footer %}
<div class="footer">
{% if show_copyright %}&copy;{{ copyright }} | {% endif %}
Licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/">
CC BY-SA 4.0</a> license
{%- if show_source and has_source and sourcename %}
{% if show_copyright or theme_show_powered_by %}|{% endif %}
<a href="{{ pathto('_sources/' + sourcename, true)|e }}"
rel="nofollow">{{ _('Page source') }}</a>
{%- endif %}
</div>
{% endblock %}

209
doc/dev/conf.py Normal file
View File

@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
#
# 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/>.
#
"""
Configuration file for the Sphinx documentation builder.
This file does only contain a selection of the most common options. For a full
list see the documentation: http://www.sphinx-doc.org/en/master/config
"""
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('../../'))
# -- Project information -----------------------------------------------------
# pylint: disable=invalid-name
project = 'FreedomBox'
copyright = '2019, FreedomBox Authors'
author = 'FreedomBox Authors'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = ''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'fixed_sidebar': True,
'show_related': True,
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'FreedomBoxdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'FreedomBox.tex', 'FreedomBox Documentation',
'FreedomBox Authors', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, 'freedombox', 'FreedomBox Documentation', [author],
1)]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'FreedomBox', 'FreedomBox Documentation', author,
'FreedomBox', 'One line description of project.', 'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'django': ('https://docs.djangoproject.com/en/stable/',
'https://docs.djangoproject.com/en/stable/_objects/'),
}
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for autodoc extension -------------------------------------------
autodoc_default_options = {
'special-members': '__init__',
}

34
doc/dev/index.rst Normal file
View File

@ -0,0 +1,34 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
FreedomBox Developer Documentation
==================================
This manual is meant for developers intending to develop app for FreedomBox. It
provides a step by step tutorial and an API reference.
.. toctree::
:maxdepth: 3
:caption: Contents:
tutorial/index
reference/index
External References
===================
#. :doc:`Django Documentation - Getting Started <django:intro/index>`
#. `Debian Packaging Portal <https://wiki.debian.org/Packaging>`_
#. `systemd System and Service Manager <https://www.freedesktop.org/wiki/Software/systemd/>`_
#. `Bootstrap - CSS Library <http://getbootstrap.com/css/>`_
#. `FreedomBox User Manual <https://wiki.debian.org/FreedomBox/Manual>`_
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -0,0 +1,10 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Action Utils
^^^^^^^^^^^^
Several utilities to help with the implementation of actions and diagnostic
tests are implemented in this module.
.. automodule:: plinth.action_utils
:members:

View File

@ -0,0 +1,16 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Actions
^^^^^^^
FreedomBox's web front does not directly change any aspect of the underlying
operating system. Instead, it calls upon **actions**, as shell commands. Actions
live in ``/usr/share/plinth/actions`` directory. They require no interaction
beyond passing command line arguments or taking sensitive arguments via stdin.
They change the operation of the services and apps of the FreedomBox and nothing
else. These actions are also directly usable by a skilled administrator.
The following documentation for the ``actions`` module.
.. automodule:: plinth.actions
:members: run, superuser_run, run_as_user, _run

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
App
---
.. autoclass:: plinth.app.App
:members:

View File

@ -0,0 +1,37 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
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>.diagnose()
^^^^^^^^^^^^^^^^^^^^^^^
Optional. Called when the user invokes system-wide diagnostics by visiting
**System -> Diagnositcs**. This method must return an array of diagnostic
results. Each diagnostic result must be a two-tuple with first element as a
string that is shown to the user as name of the test and second element is the
result of the test. It must be one of ``passed``, ``failed``, ``error``. Example
return value:
.. code-block:: python3
[('Check http://localhost/app is reachable', 'passed'),
('Check configuration is sane', 'passed')]
<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``.

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Daemon
^^^^^^
.. autoclass:: plinth.daemon.Daemon
:members:

View File

@ -0,0 +1,10 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Domain Name
^^^^^^^^^^^
.. autoclass:: plinth.modules.names.components.DomainName
:members:
.. autoclass:: plinth.modules.names.components.DomainType
:members:

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Firewall
^^^^^^^^
.. autoclass:: plinth.modules.firewall.components.Firewall
:members:

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Frontpage
^^^^^^^^^
.. autoclass:: plinth.frontpage.Shortcut
:members:

View File

@ -0,0 +1,27 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Components
----------
.. toctree::
:caption: Available components:
menu
daemon
firewall
webserver
frontpage
domain
letsencrypt
Base Classes
^^^^^^^^^^^^
.. autoclass:: plinth.app.Component
:members:
.. autoclass:: plinth.app.LeaderComponent
:members:
.. autoclass:: plinth.app.FollowerComponent
:members:

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Let's Encrypt
^^^^^^^^^^^^^
.. autoclass:: plinth.modules.letsencrypt.components.LetsEncrypt
:members:

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Menu
^^^^
.. autoclass:: plinth.menu.Menu
:members:

View File

@ -0,0 +1,10 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Webserver
^^^^^^^^^
.. autoclass:: plinth.modules.apache.components.Webserver
:members:
.. autoclass:: plinth.modules.apache.components.Uwsgi
:members:

View File

@ -0,0 +1,13 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Forms
-----
.. autoclass:: plinth.forms.AppForm
:members:
.. autoclass:: plinth.forms.DomainSelectionForm
:members:
.. autoclass:: plinth.forms.CheckboxSelectMultipleWithReadOnly
:members:

View File

@ -0,0 +1,23 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
=========
Reference
=========
This section describes the FreedomBox API that is most frequently used by apps.
Note that since FreedomBox is under development and has not yet declared a
stable API, this API is subject to change. This is not usually a problem because
all the FreedomBox apps currently reside in FreedomBox source repository itself
and are updated when the API is updated.
.. toctree::
app
components/index
app_module
actions
action_utils
views
forms
.. automodule:: plinth.modules.ttrss

View File

@ -0,0 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Views
-----
.. autoclass:: plinth.views.AppView
:members:

View File

@ -0,0 +1,65 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 1: Beginning
-----------------
Before we begin
^^^^^^^^^^^^^^^
FreedomBox Service (Plinth) is a web interface built using Python3 and Django.
FreedomBox apps are simply Django applications within the project. Hence, for
the most part, writing a FreedomBox app is all about :doc:`writing a Django
application <django:intro/tutorial01>`.
You should start by reading the :doc:`Django tutorial <django:intro/index>`. All
the concepts described there are applicable for how FreedomBox and its apps are
be built.
Picking an app
^^^^^^^^^^^^^^
We must first, of course, pick an application to add to FreedomBox. For the
purpose of this tutorial, let us pick Transmission. Transmission daemon handles
Bitorrent file sharing. BitTorrent is a peer-to-peer file sharing protocol.
.. important:: Choosing an app
When choosing an application we must make sure that it respects users'
freedom and privacy. By choosing to use FreedomBox, users have explicitly made
a choice to keep the data with themselves, to not provide privacy compromising
data to centralized entities and to use Free Software that respects their
Software Freedom. These are not properties of *some* of the applications in
FreedomBox but all applications *must* adhere to these principles. Apps should
not even ask the users questions to this effect, because users have already
made a choice.
Packaging the application
^^^^^^^^^^^^^^^^^^^^^^^^^
Majority of the effort in creating an application for FreedomBox is to package
it for Debian and get it uploaded to Debian repositories. Going through the
process of packaging itself is outside the scope of this tutorial. It is,
however, well documented elsewhere. You should start at the `Debian Packaging
Portal <https://wiki.debian.org/Packaging>`_.
Debian packaging might seem like an unnecessary process that takes time with its
adherence to standards, review process, legal checks, etc. However, upon close
examination, one will find that without these steps the goals of the FreedomBox
project cannot be met. Some of the advantages of Debian packaging are listed
below:
* Legal check ensures that proprietary licensed code or code with bad licenses
does not inadvertently creep in.
* Libraries have to be packaged separately easing security handling. When a
security vulnerability is identified in a library, just the library will have
to be updated and not all the applications that depend on it.
* Upgrades become smoother. The dependency handling of the packaging system,
configuration handling tools, tools to deal with various types of well known
files help with ensuring a proper upgrade.
* Collaborative maintenance teams ensure that the package is well cared for even
if you get busy with other work and can't spend time on your package.
Following standards and using common infrastructure is critical to enable this
development methodology.

61
doc/dev/tutorial/code.rst Normal file
View File

@ -0,0 +1,61 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Full Code
---------
Transmission app is already included in FreedomBox. Here is the full source for
the module for reference.
plinth/modules/transmission/__init__.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/__init__.py
:language: python3
plinth/modules/transmission/forms.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/forms.py
:language: python3
plinth/modules/transmission/manifest.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/manifest.py
:language: python3
plinth/modules/transmission/urls.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/urls.py
:language: python3
plinth/modules/transmission/views.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/views.py
:language: python3
plinth/modules/transmission/data/etc/plinth/modules-enabled/transmission
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/data/etc/plinth/modules-enabled/transmission
:language: text
plinth/modules/transmission/data/etc/apache2/conf-available/transmission-plinth.conf
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/data/etc/apache2/conf-available/transmission-plinth.conf
:language: apache
plinth/modules/transmission/tests/__init__.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../plinth/modules/transmission/tests/__init__.py
:language: python3
actions/transmission
^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: ../../../actions/transmission
:language: python3

View File

@ -0,0 +1,181 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 4: Components
------------------
Managing a daemon
^^^^^^^^^^^^^^^^^
Transmission, like many services in the FreedomBox, requires a daemon to be
running in the system to work. When the app is enabled, the daemon should be
enabled. When the app is disabled, the daemon should be disabled. We should also
show the status of whether the daemon is running in the app's view. All of these
concerns are automatically handled by the framework if a
:class:`~plinth.daemon.Daemon` component is added to the app. Let us do that in
our app's class.
.. code-block:: python3
from plinth.daemon import Daemon
managed_services = ['transmission-daemon']
class TransmissionApp(app_module.App):
...
def __init__(self):
...
daemon = Daemon('daemon-transmission', managed_services[0])
self.add(daemon)
The first argument to instantiate the :class:`~plinth.daemon.Daemon` class is a
unique ID. The second is the name of the `systemd
<https://www.freedesktop.org/wiki/Software/systemd/>`_ unit file which manages
the daemon.
Managing web server configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Transmission provides a web interface to the user. This web interface needs to
be proxied through a web server for security and access control. We will need to
write a configuration snippet for Apache, the default web server on FreedomBox.
This configuration snippet needs to be activated when our app is enabled. The
configuration snippet needs to be deactivated when our app is disabled. All of
these concerns are automatically handled by the framework if a
:class:`~plinth.modules.apache.components.Webserver` component is added to the
app. Let us do that in our app's class.
.. code-block:: python3
from plinth.modules.apache.components import Webserver
class TransmissionApp(app_module.App):
...
def __init__(self):
...
webserver = Webserver('webserver-transmission', 'transmission-plinth')
self.add(webserver)
The first argument to instantiate the
:class:`~plinth.modules.apache.components.Webserver` class is a unique ID. The
second is the name of the Apache2 web server configuration snippet that contains
the directives to proxy Transmission web interface via Apache2. We then need to
create the configuration file itself in ``tranmission-freedombox.conf``.
.. code-block:: apache
## On all sites, provide Transmission on a default path: /transmission
<Location /transmission>
ProxyPass http://localhost:9091/transmission
</Location>
Managing the firewall
^^^^^^^^^^^^^^^^^^^^^
FreedomBox has a tight firewall that closes off all TCP/UDP ports by default. If
a service needs to available to users on a port, it needs to open the ports in
firewalld, the default firewall configuration manager in FreedomBox. When the
app is enabled, the ports need to opened and when the app is disabled, the ports
needs to be closed. The FreedomBox framework again provides a component for
handling these operations. In case of our app, there is no need to open a
special port since the web ports are always kept open. However, it is still good
to specify that we operate on http/https ports so that users can be provided
this information along with additional information on whether the service is
available over Internet. Create the
:class:`~plinth.modules.firewall.components.Firewall` component during app
initialization.
.. code-block:: python3
from plinth.modules.firewall.components import Firewall
class TransmissionApp(app_module.App):
...
def __init__(self):
...
firewall = Firewall('firewall-transmission', name,
ports=['http', 'https'], is_external=True)
self.add(firewall)
The first parameter is a unique ID. Second one is the name of the app that as
shown to the user in the firewall status page. Third argument is the list of
services known to firewalld as listed in ``/usr/lib/firewalld/services/``.
Custom services can also be written. The final argument decides whether the
service should be made available by FreedomBox from external networks,
essentially the Internet.
User authentication and authorization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We wish that only users of FreedomBox should access the web interface of our
app. Further, only users belonging to a specially created group are the only
ones who should be able access the app. Again, FreedomBox handles all of this
and we simply need to declare and use. First we need to register a user group
with the FreedomBox framework in ``__init.py__``.
.. code-block:: python3
group = ('bit-torrent', 'Download files using BitTorrent applications')
def init():
...
register_group(group)
Then in the Apache configuration snippet, we can mandate that only users of this
group (and, of course, admin users) should be allowed to access our app. In the
file ``tranmission-freedombox.conf``, add the following.
.. code-block:: apache
<Location /transmission>
...
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin" "bit-torrent"
</IfModule>
</Location>
Showing a shortcut in the front page
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The app view we have created is only accessible by administrators of FreedomBox
since only they can configure the app. Other users who have access to this app
should have a way of discovering the app. This is done by providing a link in
the front page of FreedomBox web interface. This is the page that user's see
when they visit FreedomBox. To provide this shortcut, a
:class:`~plinth.frontpage.Shortcut` component can added to the app.
.. code-block:: python3
from plinth import frontpage
group = ('bit-torrent', 'Download files using BitTorrent applications')
class TransmissionApp(app_module.App):
...
def __init__(self):
...
shortcut = frontpage.Shortcut(
'shortcut-transmission', name, short_description=short_description,
icon='transmission', url='/transmission', clients=clients,
login_required=True, allowed_groups=[group[0]])
self.add(shortcut)
The first parameter, as usual, is a unique ID. The next three parameters are
basic information about the app similar to the menu item. The URL parameter
specifies the URL that the user should be directed to when the shortcut is
clicked. This is the web interface provided by our app. The next parameter
provides a list of clients. This is useful for the FreedomBox mobile app when
the information is used to suggest installing mobile apps. This is described in
a later section of this tutorial. The next parameter specifies whether anonymous
users who are not logged into FreedomBox should be shown this shortcut. The
final parameter further restricts to which group of users this shortcut must be
shown.

View File

@ -0,0 +1,231 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 5: Customizing
-------------------
Customizing the application page
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The view that we have written above requires a template file. A default template
file is provided by the framework. In some cases, we will need to customize this
template. Let us create a custom template file in ``transmission.html``.
.. code-block:: django
{% extends "app.html" %}
{% load i18n %}
{% block configuration %}
{{ block.super }}
<h3>{% trans "Custom Section" %}</h3>
<p>
{% blocktrans trimmed %}
Custom paragraph content.
{% endblocktrans %}
</p>
{% endblock %}
This template extends an existing template known as ``app.html``. This template
is available in FreedomBox core to provide all the basic layout, styling, menus,
JavaScript and CSS libraries required for a typical app view. We will override
the configuration area after inheriting from the app template and keep the rest
as is. ``{{ block.super }}`` adds back the overwritten content in the
``configuration`` block.
Yet again, there is nothing special about the way this template is written. This
is a regular Django template. See :doc:`Django Template documentation
<django:topics/templates>`.
For styling and UI components, FreedomBox uses the Twitter Bootstrap project.
See `Bootstrap documentation <http://getbootstrap.com/css/>`_ for reference.
To start using our custom template, we need to pass this to our view. In
``views.py``, add the following line:
.. code-block:: python3
class TransmissionAppView(AppView):
...
template_name = 'transmission.html'
Writing a configuration form
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Our app needs some configuration. So, we need to write a configuration form to
provide options to the user. Add the following to ``forms.py``.
.. code-block:: python3
from django import forms
from plinth.forms import AppForm
class TransmissionForm(AppForm): # 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.
.. tip: Too many options
Resist the temptation to create a lot of configuration options. Although this
will put more control in the hands of the users, it will make FreedomBox less
usable. FreedomBox is a consumer product. Our target users are not technically
savvy and we have make most of the decisions on behalf of the user to make the
interface as simple and easy to use as possible.
Applying the changes from the form
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The view we have created needs to display the form and process the form after
the user submits it. Let us implement that in ``views.py``.
.. code-block:: python3
from django.contrib import messages
from plinth import actions, views
from .forms import TransmissionForm
class TransmissionAppView(views.AppView):
...
form_class = TransmissionForm
def get_initial(self):
"""Get the current settings from Transmission server."""
status = super().get_initial()
configuration = actions.superuser_run('transmission',
['get-configuration'])
configuration = json.loads(configuration)
status.update({
key.translate(str.maketrans({
'-': '_'
})): value
for key, value in configuration.items()
})
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']:
new_configuration = {
'download-dir': new_status['download_dir'],
}
actions.superuser_run('transmission', ['merge-configuration'],
input=json.dumps(new_configuration).encode())
messages.success(self.request, 'Configuration updated')
return super().form_valid(form)
We check to make sure that the configuration value has actually changed after
the form is submitted. Although FreedomBox's operations are idempotent, meaning
that running them twice will not be problematic, we still wish to avoid
unnecessary operations for the sake of speed.
We are actually performing the operation using *actions*. We will implement this
action a bit later.
After we perform the operation, we will show a message on the response page that
the action was successful or that nothing happened. We use the Django messaging
framework to accomplish this. See :doc:`Django messaging framework
<django:ref/contrib/messages>` for more information.
Writing actions
^^^^^^^^^^^^^^^
The actual work of performing the configuration change is carried out by an
*action*. Actions are independent scripts that run with higher privileges
required to perform a task. They are placed in a separate directory and invoked
as scripts via sudo. For our application we need to write an action that can
enable and disable the web configuration. We will do this by creating a file
``actions/transmission``.
.. code-block:: python3
import argparse
import json
import sys
from plinth import action_utils
TRANSMISSION_CONFIG = '/etc/transmission-daemon/settings.json'
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('get-configuration',
help='Return the current configuration')
subparsers.add_parser(
'merge-configuration',
help='Merge JSON configuration from stdin with existing')
subparsers.required = True
return parser.parse_args()
def subcommand_get_configuration(_):
"""Return the current configuration in JSON format."""
configuration = open(TRANSMISSION_CONFIG, 'r').read()
print(configuration)
def subcommand_merge_configuration(arguments):
"""Merge given JSON configuration with existing configuration."""
configuration = sys.stdin.read()
configuration = json.loads(configuration)
current_configuration = open(TRANSMISSION_CONFIG, 'r').read()
current_configuration = json.loads(current_configuration)
new_configuration = current_configuration
new_configuration.update(configuration)
new_configuration = json.dumps(new_configuration, indent=4, sort_keys=True)
open(TRANSMISSION_CONFIG, 'w').write(new_configuration)
action_utils.service_reload('transmission-daemon')
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()
This is a simple Python3 program that parses command line arguments. While
Python3 is preferred, it can be written in other languages also. It may use
various helper utilities provided by the FreedomBox framework in
:obj:`plinth.action_utils` to easily perform it's duties.
This script is automatically installed to ``/usr/share/plinth/actions`` by
FreedomBox's installation script ``setup.py``. Only from here will there is a
possibility of running the script under ``sudo``. If you are writing an
application that resides indenpendently of FreedomBox's source code, your app's
``setup.py`` script will need to take care of copying the file to this target
location.

View File

@ -0,0 +1,95 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 8: Finishing
-----------------
Adding a License
^^^^^^^^^^^^^^^^
FreedomBox is licensed under the GNU Affero General Public License Version 3 or
later. FreedomBox apps, which run as modules under FreedomBox Service (Plinth),
also need to be under the same license or under a compatible license. The
license of our app needs to clear for our app to be accepted by users and other
developers. Let us add license headers to our application.
.. code-block:: python3
#
# 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/>.
#
The above header needs to be present in every file of the application. It is
suitable for Python files. However, in template files, we need to modify it
slightly.
.. code-block:: django
{% extends "base.html" %}
{% comment %}
#
# 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/>.
#
{% endcomment %}
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.
For the most part, the code we have written so far, is already compliant with
the coding standards. This includes variable/method naming, indentation,
document strings, comments, etc. One thing we have to add are the module
documentation strings. Let us add those. In ``__init__.py`` add the top:
.. code-block:: python3
"""
FreedomBox app to configure Transmission.
"""
Contributing code to FreedomBox
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``HACKING.md`` and ``CONTRIBUTING.md`` files in the FredomBox source code
have tips on how to contribute code to the project. Be sure to read them if you
are submitting your app for including on the project.
Here is ``HACKING.md``:
.. literalinclude:: ../../../HACKING.md
:language: md
Here is ``CONTRIBUTING.md``:
.. literalinclude:: ../../../CONTRIBUTING.md
:language: md

View File

@ -0,0 +1,48 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
=====================================
Tutorial: Writing Apps for FreedomBox
=====================================
This tutorial covers writing an app for FreedomBox. FreedomBox is a pure blend
of Debian with a web interface, that configures its apps. We shall discuss
various aspects of building an app for FreedomBox, by creating an example app.
The app that is discussed in the tutorial already available in FreedomBox so you
can also study it's full source code.
There are two parts to writing a FreedomBox app. First is to make sure that the
app is available as a Debian package uploaded to its repositories. This is the
majority of the work involved. However, if an app is already available in Debian
repositories, the whole task is simplified.. The second part of writing an app
for FreedomBox is to provide a thin web interface layer for configuring and
managing the app. This is done by extending FreedomBox's user interface to
provide visibility to the app and to let the user control its operations in a
highly simplified way. This layer is what we typically refer to as a 'FreedomBox
app'.
FreedomBox apps can either be distributed to the end user as part of FreedomBox
Service (Plinth) source code by submitting the apps to the project or they can
distributed independently. This tutorial covers writing an app that is meant to
be distributed as part of FreedomBox Service (Plinth). However, writing
independent FreedomBox apps is also very similar and most of this tutorial is
applicable.
.. note:: The term *App*
The term app, in this tutorial, is used to mean multiple concepts. A service
or an application available to end users in FreedomBox is a combination of
Debian package and a web interface layer. The web interface layer is also
called a FreedomBox app which is very similar to and built upon a Django
application.
.. toctree::
beginning
skeleton
view
components
customizing
setup
other
finishing
code

222
doc/dev/tutorial/other.rst Normal file
View File

@ -0,0 +1,222 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
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
from plinth.clients import validate
clients = validate([{
'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 .manifest import clients
clients = clients
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
Adding backup/restore functionality
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Each app in FreedomBox needs to provide the ability to backup its configuration
and data. Apart from providing durability to users' data, this allows the user
to migrate from one machine to another. FreedomBox framework provides a simple
declarative mechanism to allow the app to be backed up and restored. In
``manifest.py``, add:
.. code-block:: python3
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({
'data': {
'directories': ['/var/lib/transmission-daemon/.config']
},
'secrets': {
'files': ['/etc/transmission-daemon/settings.json']
},
'services': ['transmission-daemon']
})
The data and secrets information specifies which list of files and directories
FreedomBox framework needs to backup. The list of services specifies which
daemons should be stopped during the backup process. In ``__init__.py``, add:
.. code-block:: python3
from .manifest import backup
Creating diagnostics
^^^^^^^^^^^^^^^^^^^^
When the app does not work as expected, the user should known what is happening
with the app. The FreedomBox framework provides an API for running and showing
diagnostics results. The app has to implement a method for actually running the
diagnostics and return the results as a list. FreedomBox then takes care of
calling the diagnostics method and displaying the list in a formatted manner.
To implement the diagnostics, a method called ``diagnose()`` has to be available
as ``<app-module>.diagnose()``. It must return a list in which each item is the
result of a test performed. The item itself is a two-tuple containing the
display name of the test followed by the result as ``passed``, ``failed`` or
``error``.
.. code-block:: python3
def diagnose():
"""Run diagnostics and return the results."""
results = []
results.extend(action_utils.diagnose_url_on_all(
'https://{host}/transmission', extra_options=['--no-check-certificate']))
return results
Now that we have implemented diagnostics, we also need to show a diagnostics
button in the App's page. Adding an attribute to the
:class:`~plinth.views.AppView` will take care of this.
.. code-block:: python3
class TransmissionView(views.AppView):
...
diagnostics_module_name = 'transmission'
There are several helpers available to implement some of the common diagnostic
tests. For our application we wish to implement a test to check whether the
``/transmission`` URL is accessible. Since this is a commonly performed test,
there is a helper method available and we have used it in the above code. The
``{host}`` tag replaced with various IP addresses, hostnames and domain names by
the helper to produce different kinds of URLs and they are all tested. Results
for all tests are returned which we then pass on to the framework.
The user can trigger the diagnostics test by going to **System -> Diagnostics**
page. This runs diagnostics for all the applications. Users can also run
diagnostics specifically for this app from the app's page. A diagnostics button
is shown by the `app.html` template automatically when
``diagnostics_module_name`` attribute is set in the app's ``AppView`` derived
from :obj:`plinth.views.AppView`.
.. code-block:: django
{% include "diagnostics_button.html" with module="ttrss" enabled=True %}
Logging
^^^^^^^
Sometimes we may feel the need to write some debug messages to the console and
system logs. Doing this in FreedomBox is just like doing this any other Python
application.
.. code-block:: python3
import logging
logger = logging.getLogger(__name__)
def example_method():
logger.debug('A debug level message')
logger.info('Showing application page - %s', request.method)
try:
something()
except Exception as exception:
# Print stack trace
logger.exception('Encountered an exception - %s', exception)
For more information see Python :doc:`logging framework <howto/logging>`
documentation.
Internationalization
^^^^^^^^^^^^^^^^^^^^
Every string message that is visible to the user must be localized to user's
native language. For this to happen, our app needs to be internationalized. This
requires marking the user visible messages for translation. FreedomBox apps use
the Django's localization methods to make that happen.
.. code-block:: python3
from django.utils.translation import ugettext_lazy as _
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>.')
]
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
:obj:`~django.utils.translation.ugettext_lazy` in some cases and we use the
regular :obj:`~django.utils.translation.ugettext` in other cases. This is
because in the second case the :obj:`~django.utils.translation.gettext` lookup
is made once and reused for every user looking at the interface. These users may
each have a different language set for their interface. Lookup made for one
language for a user should not be used for other users. The ``_lazy`` methods
provided by Django makes sure that the return value is an object that will
actually be converted to string at the final moment when the string is being
displayed. In the first case, the lookup is made and string is returned
immediately.
All of this is the usual way internationalization is done in Django. See
:doc:`Internationalization and localization <django:topics/i18n/index>`
documentation for more information.

View File

@ -0,0 +1,40 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 6: Setup
-------------
Installing packages required for the app
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
So far, we haven't dealt with installing the packages needed for Transmission to
work. Nor did we take care of performing the initial configuration for
Transmission. FreedomBox takes care of installing all the Debian packages
required for our app to work. All we need to do is specify the list of the
Debian packages required in the ``setup()`` method that is called during
installation:
.. code-block:: python3
managed_packages = ['transmission-daemon']
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages)
new_configuration = {
'rpc-whitelist-enabled': False,
'rpc-authentication-required': False
}
helper.call('post', actions.superuser_run, 'transmission',
['merge-configuration'],
input=json.dumps(new_configuration).encode())
helper.call('post', app.enable)
The first time this app's view is accessed, FreedomBox shows an app installation
page and allows the user to install the app. After the app installation is
completed, the user is shown the app's configuration page.
In case of our app Transmission, first we are installing the Debian packages,
then performing the first time configuration on the app using the action script
and finally enabling the app.

View File

@ -0,0 +1,102 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 2: Skeleton
----------------
Let us get started with creating our FreedomBox app.
Creating the project structure
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create a directory structure as follows with empty files. We will fill them up
in a step-by-step manner::
─┬ <plinth_root>/
├─┬ plinth/
│ └─┬ modules/
│ └─┬ transmission/
│ ├─ __init__.py
│ ├─ forms.py
│ ├─ manifest.py
│ ├─ urls.py
│ ├─ views.py
│ ├─┬ data/
│ │ └─┬ etc/
│ │ ├─┬ plinth/
│ │ │ └─┬ modules-enabled/
│ │ │ └─ transmission
│ │ └─┬ apache2/
│ │ └─┬ conf-available/
│ │ └─ transmission-freedombox.conf
│ └─┬ tests
│ └─ __init__.py
└─┬ actions/
└─ transmission
The file ``__init__.py`` indicates that the directory in which it is present is
a Python module. For now, it is an empty file.
FreedomBox's setup script ``setup.py`` will automatically install the
``plinth/modules/transmission`` directory (along with other files described
later) to an appropriate location. If you are creating an app that stays
independent and outside of FreedomBox source tree, then ``setup.py`` script in
your source tree will need to install it to a proper location on the system. The
``plinth/modules/`` directory is a Python3 `namespace package
<https://www.python.org/dev/peps/pep-0420/>`_. So, you can install it with the
``plinth/modules/`` directory structure into any Python path and still be
discovered as ``plinth.modules.*``.
Tell FreedomBox that our app exists
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The first thing to do is tell FreedomBox that our app exists. This is done by
writing a small file with the Python import path to our app and placing it in
``plinth/modules/transmission/data/etc/plinth/modules-enabled/``. Let us create
this file ``transmission``::
plinth.modules.transmission
This file is automatically installed to ``/etc/plinth/modules-enabled/`` by
FreedomBox's installation script ``setup.py``. If we are writing a module that
resides independently outside the FreedomBox's source code, the setup script
will need to copy it to the target location. Further, it is not necessary for
the app to be part of the ``plinth.modules`` namespace. It can, for example, be
``freedombox_transmission``.
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 later.
.. code-block:: python3
from plinth import app as app_module
class TransmissionApp(app_module.App):
"""FreedomBox app for Transmission."""
app_id = 'transmission'
def __init__(self):
"""Create components for the app."""
super().__init__()
As soon as FreedomBox Service (Plinth) starts, it will load all the enabled
modules. After this, it gives a chance to each of the modules to initialize
itself by calling the ``init()`` method if there is such a method available as
``<module>.init()``. The app class must be instantiated here.
.. code-block:: python3
app = None
def init():
"""Initialize the Transmission module."""
global app
app = TransmissionApp()
setup_helper = globals()['setup_helper']
if setup_helper.get_state() != 'needs-setup' and app.is_enabled():
app.set_enabled(True)

112
doc/dev/tutorial/view.rst Normal file
View File

@ -0,0 +1,112 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
Part 3: View
------------
Writing the URLs
^^^^^^^^^^^^^^^^
For a user to visit our app in FreedomBox, we need to provide a URL. When the
user visits this URL, a view is executed and a page is displayed. In ``urls.py``
write the following:
.. code-block:: python3
from django.conf.urls import url
from .views import TransmissionAppView
urlpatterns = [
url(r'^apps/transmission/$', TransmissionAppView.as_view(), name='index'),
]
This routes the ``/apps/transmission/`` URL to a view called
``TransmissionAppView`` defined in ``plinth/modules/transmission/views.py``.
This is no different than how routing URLs is done in Django. See :doc:`Django
URL dispatcher <django:topics/http/urls>` for more information.
Adding a menu item
^^^^^^^^^^^^^^^^^^
We have added a URL to be handled by our app but this does not yet show up to be
a link in FreedomBox web interface. Let us add a link in the apps list. In
``__init__.py`` add the following:
.. code-block:: python3
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')
self.add(menu_item)
What this does is add a menu item component into the our app. In FreedomBox
framework, an app is made up of many simple components. When operations such as
enable/disable are performed on the app, they will be applied on all the
components. In case of menu components, FreedomBox framework takes care of
presenting them appropriately. The component captures various details about the
menu item we want to present.
* The first parameter is simply a unique ID for the component.
* The second parameter is the display name to use for our menu item which
happens to be the name of the app as well.
* The third parameter is a short description for the menu item.
* The fourth parameter is the name of the icon to use when showing the menu
item. An SVG file and a PNG should be created in the ``static/theme/icons/``
directory.
* The fifth parameter is the URL that the user should be directed to when the
menu item is clicked. This is a Django URL name and we have already created a
URL with this name. Note that when including our app's URLs, FreedomBox will
automatically set the name of the module as the Django URL namespace. Hence it
is ``transmission:index`` and not just ``index``.
* We wish to add our menu item to the list of apps in the apps page which is why
we have specified ``apps`` as the parent URL for the this app in the final
parameter.
Writing a view
^^^^^^^^^^^^^^
We have a URL pointing to our view. We have also added a menu item in the apps
section of the web interface that points to our view. We now need to create a
view to show the app page for our app. In ``views.py``, let us add a view.
.. code-block:: python3
from plinth import views
from plinth.modules import transmission
class TransmissionAppView(views.AppView):
"""Serve configuration page."""
name = transmission.name
description = transmission.description
app_id = 'transmission'
The base view :class:`~plinth.views.AppView` takes care of a lot of details for
us. First, it shows basic information about the app like name, description,
desktop/mobiles clients for the service (described later), link to the manual
page (described later), link to diagnostics button, etc. Then it shows the
status of the app whether it is running and can also present a form for
configuration. It also presents a way to enable/disable the app.