Eden ASP

Eden ASP is a rapid application development (RAD) kit for web-based, database-driven humanitarian and emergency management applications, originally derived from the Sahana Eden Humanitarian Management Platform.

Eden ASP builds on the web2py web application framework, and is written in the Python programming language (version 3.6+). It also uses HTML5, JavaScript, and SCSS to generate web contents, as well as XSLT to handle certain data formats.

This documentation is aimed at application developers, and included in the source code.

Introduction into Eden ASP

Basic Concepts

This page explains the basic concepts, structure and operations of Eden ASP, and introduces the fundamental terminology used throughout this documentation.

Client and Server

Eden ASP is a web application, which means it is run as a server program and is accessed remotely by client programs connected over the network.

_images/client_server.png

Most of the time, the client program will be a web browser - but it could also be a mobile app, or another type of program accessing web services. Many clients can be connected to the server at the same time.

Client and server communicate using the HTTP protocol, in which the client sends a request to the server, the server processes the request and produces a response (e.g. a HTML page) that is sent back to the client, and then the client processes the response (e.g. by rendering the HTML page on the screen).

Note

Responding to HTTP requests is Eden ASP’s fundamental mode of operation.

Web2Py and PyDAL

Eden ASP builds on the web2py web application framework, which consists of three basic components: a HTTP server, the application runner and various libraries, and a database abstraction layer.

_images/web2py_stack.png

The HTTP server (also commonly called “web server”) manages client connections. Web2py comes with a built-in HTTP server (Rocket), but production environments typically deploy a separate front-end HTTP server (e.g. nginx) that connects to web2py through a WSGI plugin or service (e.g. uWSGI).

_images/web2py_stack_prod.png

The application runner (gluon) decodes the HTTP request, then calls certain Python functions in the Eden ASP application with the request data as input, and from their output renders the HTTP response. Additionally, gluon provides a number of libraries to generate interactive web contents and process user input.

The database abstraction layer (PyDAL) provides a generic interface to the database, as well as a mapping between Python objects and the tables and records in the database (ORM, object-relational mapping). For production environments, the preferred database back-end is PostgreSQL with the PostGIS extension, but SQLite and MariaDB/MySQL are also supported.

Application Structure

Web2py applications like Eden ASP implement the MVC (model-view-controller) application model, meaning that the application code is separated in:

  • models defining the data(base) structure,

  • views implementing the user interface,

  • controllers implementing the logic connecting models and views

This is somewhat reflected by the directory layout of Eden ASP:

_images/directory_layout.png

Note

This directory layout can be somewhat misleading about where certain functionality can be found in the code:

The controllers directory contains Python scripts implementing the logic of the application. In Eden ASP, these controllers delegate much of that logic to core modules.

The models directory contains Python scripts to configure the application and define the database structure. In Eden ASP, the former is largely delegated to configuration templates, and the latter is reduced to the instantiation of a model loader, which then loads the actual data models from s3db modules if and when they are actually needed.

The Request Cycle

Eden ASP runs in cycles triggered by incoming HTTP requests.

_images/request_cycle.png

When an HTTP request is received, web2py parses and translates it into a global request object.

For instance, the request URI is translated like:

https://www.example.com/[application]/[controller]/[function]/[args]?[vars]

…and its elements stored as properties of the request object (e.g. request.controller and request.function). These values determine which function of the application is to be executed.

Web2py also generates a global response object, which can be written to in order to set parameters for the eventual HTTP response.

Web2py then runs the Eden ASP application:

  1. executes all scripts in the models/ directory in lexical (ASCII) order.

  2. executes the script in the controllers/ directory that corresponds to request.controller, and then calls the function defined by that script that corresponds to request.function.

    E.g. if request.controller is “dvr” and request.function is “person”, then the controllers/dvr.py script will be executed, and then the person() function defined in that script will be invoked.

  3. takes the output of the function call to compile the view template configured as response.view.

These three steps are commonly referred to as the request cycle.

Building Applications

Setting up for Development

This page describes how you can set up a local Eden ASP instance for application development on your computer.

Note

This guide assumes that you are working in a Linux environment (shell commands are for bash).

If you are working with another operating system, you can still take this as a general guideline, but commands may be different, and additional installation steps could be required.

Note

This guide further assumes that you have Python (version 3.6 or later) installed, which comes bundled with the pip package installer - and that you are familiar with the Python programming language.

Additionally, you will need to have git installed.

Prerequisites

Eden ASP requires a couple of Python libraries, which can be installed with the pip installer.

As a minimum, lxml and python-dateutil must be installed:

sudo pip install lxml python-dateutil

The following are also required for normal operation:

sudo pip install pyparsing requests xlrd xlwt openpyxl reportlab shapely geopy

Some specialist functionality may require additional libraries, e.g.:

sudo pip install qrcode docx-mailmerge

Tip

The above commands use sudo pip to install the libraries globally. If you want to install them only in your home directory, you can omit sudo.

Installing web2py

To install web2py, clone it directly from GitHub:

git clone --recursive https://github.com/web2py/web2py.git ~/web2py

Tip

You can of course choose any other target location than ~/web2py for the clone - just remember to use the correct path in subsequent commands.

Change into the web2py directory, and reset the repository (including all submodules) to the supported stable version (currently 2.24.1):

cd ~/web2py
git reset --hard 7685d373
git submodule update --recursive

Installing Eden ASP

To install Eden ASP, clone it directly from GitHub:

git clone --recursive https://github.com/aqmaster/eden-asp.git ~/eden

Tip

You can of course choose any other target location than ~/eden for the clone - just remember to use the correct path in subsequent commands.

Configure Eden ASP as a web2py application by adding a symbolic link to the eden directory under web2py/applications:

cd ~/web2py/applications
ln -s ~/eden eden

The name of this symbolic link (eden) becomes the web2py application name, and will later be used in URLs to access the application.

Tip

You can also clone Eden ASP into the ~/web2py/applications/eden directory - then you will not need the symbolic link.

Configuring Eden ASP

Before running Eden ASP the first time, you need to create a configuration file. To do so, copy the 000_config.py template into Eden ASP’s models folder:

cd ~/eden
cp modules/templates/000_config.py models

Open the ~/eden/models/000_config.py file in an editor and adjust any settings as needed.

For development, you do not normally need to change anything, except setting the following to True (or removing the line altogether):

Editing models/000_config.py
FINISHED_EDITING_CONFIG_FILE = True

That said, it normally makes sense to also turn on debug mode for development:

Editing models/000_config.py
settings.base.debug = True

First run

The first start of Eden ASP will set up the database, creating all tables and populating them with some data.

This is normally done by running the noop.py script in the web2py shell:

cd ~/web2py
python web2py.py -S eden -M -R applications/eden/static/scripts/tools/noop.py

This will give a console output similar to this:

Console output during first run
WARNING:  S3Msg unresolved dependency: pyserial required for Serial port modem usage
WARNING:  Setup unresolved dependency: ansible required for Setup Module
WARNING: Error when loading optional dependency: google-api-python-client
WARNING: Error when loading optional dependency: translate-toolkit

*** FIRST RUN - SETTING UP DATABASE ***

Setting Up System Roles...
Setting Up Scheduler Tasks...
Creating Database Tables (this can take a minute)...
Database Tables Created. (3.74 sec)

Please be patient whilst the database is populated...

Importing default/base...
Imports for default/base complete (1.99 sec)

Importing default...
Imports for default complete (5.20 sec)

Importing default/users...
Imports for default/users complete (0.04 sec)

Updating database...
Location Tree update completed (0.63 sec)
Demographic Data aggregation completed (0.01 sec)

Pre-populate complete (7.90 sec)

Creating indexes...

*** FIRST RUN COMPLETE ***

You can ignore the WARNING messages here about unresolved, optional dependencies.

Starting the server

In a development environment, we normally use the built-in HTTP server (Rocket) of web2py, which can be launched with:

cd ~/web2py
python web2py.py --no_gui -a [password]

Replace [password] here with a password of your choosing - this password is needed to access web2py’s application manager (e.g. to view error tickets).

Once the server is running, it will give you a localhost URL to access it:

Console output of web2py after launch
web2py Web Framework
Created by Massimo Di Pierro, Copyright 2007-2023
Version 2.24.1-stable+timestamp.2023.03.22.21.39.14
Database drivers available: sqlite3, MySQLdb, psycopg2, imaplib, pymysql, pyodbc

please visit:
        http://127.0.0.1:8000/
use "kill -SIGTERM 10921" to shutdown the web2py server

Append the application name eden to the URL (http://127.0.0.1:8000/eden), and open that address in your web browser to access Eden ASP.

The first run will have installed two demo user accounts, namely:

  • admin@example.com (a user with the system administrator role)

  • normaluser@example.com (an unprivileged user account)

…each with the password testing. So you can login and explore the functionality.

Using PostgreSQL

to be written

About Templates

Global Config

Many features and behaviors of Eden ASP can be controlled by settings.

These settings are stored in a global S3Config instance - which is accessible through current as current.deployment_settings.

from gluon import current

settings = current.deployment_settings

Note

In the models and controllers context, current.deployment_settings is accessible simply as settings.

Deployment Settings

S3Config comes with meaningful defaults where possible.

However, some settings will need to be adjusted to configure the application for a particular system environment - or to enable, disable, configure, customize or extend features in the specific context of the deployment.

This configuration happens in a machine-specific configuration file:

models/000_config.py

Note

models/000_config.py is not part of the code base, and must be created before the application can be started. An annotated example can be found in the *modules/templates directory.

The configuration file is a Python script that is executed for every request cycle:

models/000_config.py (partial example)
# -*- coding: utf-8 -*-

"""
    Machine-specific settings
"""

# Remove this line when this file is ready for 1st run
FINISHED_EDITING_CONFIG_FILE = True

# Select the Template
settings.base.template = "MYAPP"

# Database settings
settings.database.db_type = "postgres"
#settings.database.host = "localhost"
#settings.database.port = 3306
settings.database.database = "myapp"
#settings.database.username = "eden"
#settings.database.password = "password"

# Do we have a spatial DB available?
settings.gis.spatialdb = True

settings.base.migrate = True
#settings.base.fake_migrate = True

settings.base.debug = True
#settings.log.level = "WARNING"
#settings.log.console = False
#settings.log.logfile = None
#settings.log.caller_info = True

# =============================================================================
# Import the settings from the Template
#
settings.import_template()

# =============================================================================
# Over-rides to the Template may be done here
#
# After 1st_run, set this for Production
#settings.base.prepopulate = 0

# =============================================================================
VERSION = 1

# END =========================================================================

Templates

Deployment configurations use configuration templates, which provide pre-configured settings, customizations and extensions suitable for a concrete deployment scenario. The example above highlights how these templates are applied.

Important

Implementing configuration templates is the primary strategy to build applications with Eden ASP.

Templates are Python packages located in the modules/templates directory:

_images/template_location.png

Each template package must contain a module config.py which defines a config-function :

modules/templates/MYAPP/config.py
def config(settings):

    T = current.T

    settings.base.system_name = T("My Application")
    settings.base.system_name_short = T("MyApp")

    ...

This config function is called from models/000_config.py (i.e. for every request cycle) with the current.deployment_settings instance as parameter, so that it can modify the global settings as needed.

Note

The template directory must also contain an __init__.py file (which can be empty) in order to become a Python package!

Cascading Templates

It is possible for a deployment configuration to apply multiple templates in a cascade, so that they complement each other:

Cascading templates (in models/000_config.py)
# Select the Template
settings.base.template = ("locations.DE", "MYAPP")

This is useful to separate e.g. locale-specific settings from use-case configurations, so that both can be reused independently across multiple deployments.

About Controllers

Controllers are functions defined inside Python scripts in the controllers directory, which handle HTTP requests and produce a response.

Basic Request Routing

Web2py maps the first three elements of the URL path to controllers as follows:

https:// server.domain.tld / application / controller / function

The application refers to the subdirectory in web2py’s application directory, which in the case of Eden ASP is normally eden (it is possible to name it differently, however).

The controller refers to a Python script in the controllers directory inside the application, which is executed.

For instance:

https:// server.domain.tld / eden / my / page

executes the script:

controllers / my.py

The function refers to a parameter-less function defined in the controller script, which is subsequently called. In the example above, that would mean this function:

In controllers/my.py
def page():
    ...
    return output

If the output format is HTML, the output of the controller function is further passed to the view compiler to render the HTML which is then returned to the client in the HTTP response.

Every controller having its own URL also means that every page in the web GUI has its own controller - and Eden ASP (like any web2py application) is a multi-page application (MPA). Therefore, in the context of the web GUI, the terms “controller function” and “page” are often used synonymously.

That said, not every controller function actually produces a web page. Some controllers exclusively serve non-interactive requests.

CRUD Controllers

The basic database functions create, read, update and delete (short: CRUD) are implemented in Eden ASP as one generic function:

In controllers/my.py
def page():

    return crud_controller()

This single function call automatically generates web forms to create and update records, displays filterable tables, generates pivot table reports and more - including a generic RESTful API for non-interactive clients.

If called without parameters, crud_controller will interpret controller and function of the page URL as prefix and name of the database table which to provide the functionality for, i.e. in the above example, CRUD functions would be provided for the table my_page.

It is possible to override the default table, by passing prefix and name explicitly to crud_controller, e.g.:

In controllers/my.py
def page():

    return crud_controller("org", "organisation")

…will provide CRUD functions for the org_organisation table instead.

Resources and Components

As explained above, a crud_controller is a database end-point that maps to a certain table or - depending on the request - certain records in that table.

This context data set (consisting of a table and a query) is referred to as the resource addressed by the HTTP request and served by the controller.

Apart from the data set in the primary table (called master), a resource can also include data in related tables that reference the master (e.g. via foreign keys or link tables) and which have been declared (usually in the data model) as components in the context of the master table.

An example for this would be addresses (component) of a person (master).

CRUD URLs and Methods

The crud_controller extends web2py’s URL schema with two additional path elements:

https:// server.domain.tld / a / c / f / record / method

Here, the record is the primary key (id) of a record in the table served by the crud_controller function - while the method specifies how to access that record, e.g. read or update.

For instance, the following URL:

https:// server.domain.tld / eden / org / organisation / 4 / update

…accesses the workflow to update the record #4 in the org_organisation table (with HTTP GET to retrieve the update-form, and POST to submit it and perform the update).

Without a record key, the URL accesses the table itself - as some methods, like create, only make sense in the table context:

https:// server.domain.tld / eden / org / organisation / create

The crud_controller comes pre-configured with a number of standard methods, including:

Method

Target

Description

create

Table

Create a new record (form)

read

Record

View a record (read-only representation)

update

Record

Update a record (form)

delete

Record

Delete a record

list

Table

A tabular view of records

report

Table

Pivot table report with charts

timeplot

Table

Statistics over a time axis

map

Table

Show location context of records on a map

summary

Table

Meta-method with list, report, map on the same page (tabs)

import

Table

Import records from spreadsheets

organize

Table

Calendar-based manipulation of records

Note

Both models and templates can extend the crud_controller by adding further methods, or overriding the standard methods with specific implementations.

Default REST API

If no method is specified in the URL, then the crud_controller will treat the request as RESTful - i.e. the HTTP verb (GET, PUT, POST or DELETE) determines the access method, e.g.:

GET https:// server.domain.tld / eden / org / organisation / 3.xml

…produces a XML representation of the record #3 in the org_organisation table. A POST request to the same URL, with XML data in the request body, will update the record.

This REST API is a simpler, lower-level interface that is primarily used by certain client-side scripts, e.g. the map viewer. It does not implement complete CRUD workflows, but rather each function individually (stateless).

Note

A data format extension in the URL is required for the REST API, as it can produce and process multiple data formats (extensible). Without extension, HTML format will be assumed and one of the interactive read, update, delete or list methods will be chosen to handle the request instead.

The default REST API could be used to integrate Eden ASP with other applications, but normally such integrations require process-specific end points (rather than just database end points) - which would be implemented as explicit methods instead.

Component URLs

URLs served by a crud_controller can also directly address a component. For that, the record parameter would be extended like:

https:// server.domain.tld / a / c / f / record / component / method

Here, the component is the declared name (alias) of the component in the context of the master table - usually the name of the component table without prefix, e.g.:

https:// server.domain.tld / eden / pr / person / 16 / address

…would produce a list of all addresses (pr_address table) that are related to the pr_person record #16. Similar, replacing list with create would access the workflow to create new addresses in the context of that person record.

Note

The /list method can be omitted here - if the end-point is a table rather than a single record, then the crud_controller will automatically apply the list method for interactive data formats.

To access a particular record in a component, the primary key (id) of the component record can be appended, as in:

https:// server.domain.tld / eden / pr / person / 16 / address / 2 / read

…to read the pr_address record #2 in the context of the pr_person record #16 (if the specified component record does not reference that master record, the request will result in a HTTP 404 status).

Note

The default REST API always serves the master table, even if the URL addresses a component (however, the XML/JSON will include the component).

Implementing Templates

Settings

Customising resources

Customising controllers

Pre-populating data

Configuring Auth

Advanced Topics

Themes

Models in templates

Re-routing controllers

Reference Guide

The current Object

The current object holds thread-local global variables. It can be imported into any context:

from gluon import current
Objects accessible through current

Attribute

Type

Explanation

current.db

DAL

the database

current.s3db

DataModel

the model loader

current.deployment_settings

S3Config

deployment settings

current.auth

AuthS3

global authentication/authorisation service

current.gis

GIS

global GIS service

current.msg

S3Msg

global messaging service

current.xml

S3XML

global XML decoder/encoder service

current.request

Request

web2py’s global request object

current.response

Response

web2py’s global response object

current.T

TranslatorFactory

String Translator (for i18n)

current.messages

Messages

Common labels (internationalised)

current.ERROR

Messages

Common error messages (internationalised)

Services

Services are thread-local global singleton objects, instantiated during the models run.

They can be accessed through current , e.g.:

from gluon import current

s3db = current.s3db

This section describes the services, and their most relevant functions.

Model Loader s3db

The s3db model loader provides access to database tables and other named objects defined in dynamically loaded models.

The model loader can be accessed through current:

from gluon import current

s3db = current.s3db
Accessing Tables and Objects

A table or other object defined in a dynamically loaded data model can be accessed by name either as attribute or as key of current.s3db:

Example: accessing the org_organisation table using attribute-pattern
table = s3db.org_organisation
Example: accessing the org_organisation table using key-pattern
tablename = "org_organisation"
table = s3db[tablename]

Either pattern will raise an AttributeError if the table or object is not defined, e.g. when the module is disabled.

Both access methods build on the lower-level table() method:

s3db.table(tablename, default=None, db_only=False)

Access a named object (usually a Table instance) defined in a dynamically loaded model.

Parameters:
  • tablename (str) – the name of the table (or object)

  • default – the default to return if the table (or object) is not defined

  • db_only (bool) – return only Table instances, not other objects with the given name

Note

If an Exception instance is passed as default, it will be raised rather than returned.

Table Settings

Table settings are used to configure entity-specific behaviors, e.g. forms, list fields, CRUD callbacks and access rules. The following functions can be used to manage table settings:

s3db.configure(tablename, **attr)

Add or modify table settings.

Parameters:
  • tablename (str) – the name of the table

  • attr – table settings as key-value pairs

Example: configuring table settings
s3db.configure("org_organisation",
               insertable = False,
               list_fields = ["name", "acronym", "website"],
               )
s3db.get_config(tablename, key, default=None)

Inspect table settings.

Parameters:
  • tablename (str) – the name of the table

  • key (str) – the settings-key

  • default – the default value if setting is not defined for the table

Returns:

the current value of the setting, or default

Example: inspecting table settings
if s3db.get_config("org_organisation", "insertable", True):
    # ...
else:
    # ...
s3db.clear_config(tablename, *keys)

Remove table settings.

Parameters:
  • tablename (str) – the name of the table

  • keys – the keys for the settings to remove

Example: removing table settings
s3db.clear_config("org_organisation", "list_fields")

Warning

If clear_config is called without keys, all settings for the table will be removed!

Declaring Components

The add_components method can be used to declare components.

s3db.add_components(tablename, **links)

Declare components for a table.

Parameters:
  • tablename (str) – the name of the table

  • links – component links

Example: declaring components
s3db.add_components("org_organisation",

                    # A 1:n component with foreign key
                    org_office = "organisation_id",

                    # A 1:n component with foreign key, single entry
                    org_facility = {"joinby": "organisation_id",
                                    "multiple": False,
                                    },

                    # A m:n component with link table
                    project_project = {"link": "project_organisation",
                                       "joinby": "organisation_id",
                                       "key": "project_id",
                                       },
                    )
URL Method Handlers
s3db.set_method(tablename, component=None, method=None, action=None)

Configure a URL method for a table, or a component in the context of the table

Parameters:
  • tablename (str) – the name of the table

  • component (str) – component alias

  • method (str) – name of the method (to use in URLs)

  • action – function or other callable to invoke for this method, receives the CRUDRequest instance and controller keyword parameters as arguments

Example: defining and configuring a handler for a URL method for a table
def check_in_func(r, **attr):
    """ Handler for check_in method """

    # Produce some output...

    # Return output to view
    return {}

# Configure check_in_func as handler for the "check_in" method
# (i.e. for URLs like /eden/pr/person/5/check_in):
s3db.set_method("pr_person", method="check_in", action=check_in_func)

Tip

If a CRUDMethod class is specified as action, it will be instantiated when the method is called (lazy instantiation).

s3db.get_method(tablename, component=None, method=None)

Get the handler for a URL method for a table, or a component in the context of the table

Parameters:
  • tablename (str) – the name of the table

  • component (str) – component alias

  • method (str) – name of the method

Returns:

the handler configured for the method (or None)

CRUD Callbacks

to be written

Authentication and Authorisation auth

Global authentication/authorisation service, accessible through current.auth.

from gluon import current

auth = current.auth
User Status and Roles
auth.s3_logged_in()

Check whether the user is logged in; attempts a HTTP Basic Auth login if not.

Returns bool:

whether the user is logged in or not

auth.s3_has_role(role, for_pe=None, include_admin=True)

Check whether the user has a certain role.

Parameters:
  • role (str|int) – the UID/ID of the role

  • for_pe (int) – the pe_id of a realm entity

  • include_admin (bool) – return True for ADMIN even if role is not explicitly assigned

Returns bool:

whether the user has the role (for the realm)

Access Permissions

Access methods:

Method Name

Meaning

create

create new records

read

read records

update

update existing records

delete

delete records

review

review unapproved records

approve

approve records

auth.s3_has_permission(method, table, record_id=None, c=None, f=None):

Check whether the current user has permission to perform an action in the given context.

Parameters:
  • method (str) – the access method

  • table (str|Table) – the table

  • record_id (int) – the record ID

  • c (str) – the controller name (if not specified, current.request.controller will be used)

  • f (str) – the function name (if not specified, current.request.function will be used)

Returns bool:

whether the intended action is permitted

Geospatial Information and Maps gis

Messaging msg

XML Encoder/Decoder xml

Settings

Built-in Data Models

Core Models

Core models form the basis of the Eden ASP database, defining base entities Persons, Organisations and Locations that represent the fundamental elements of the user world.

These models are required for essential system functionality, and therefore cannot be disabled.

_images/er_core.png
Persons and Groups - pr

This data model describes individual persons and groups of persons.

Database Structure
_images/pr.png
Description

Table

Type

Description

pr_address

Object Component

Addresses

pr_contact

Object Component

Contact information (Email, Phone, …)

pr_group

Main Entity

Groups of persons

pr_group_member_role

Taxonomy

Role of the group member within the group

pr_group_membership

Relationship

Group membership

pr_group_status

Taxonomy

Status of the group

pr_group_tag

Key-Value

Tags for groups

pr_identity

Component

A person’s identities (ID documents)

pr_image

Object Component

Images (e.g. Photos)

pr_pentity

Object Table (Super-Entity)

All entities representing persons

pr_person

Main Entity

Individual persons

pr_person_details

Subtable

Additional fields for pr_person

pr_person_tag

Key-Value

Tags for persons

pr_person_user

Link Table

Link between a person and a user account

Organisations and Sites - org

to be written

Human Resources

to be written

User Accounts and Roles - auth

to be written

Geospatial Information and Maps - gis

to be written

Document Management - doc

to be written

Extensions

Extension models implement data elements for non-essential system functionality.

Content Management

The Content Management System (cms) is a place to store all kinds of user-editable contents. Its main entity is the Post (=content item), which can be linked to various core entities. Posts are also DocEntities, i.e. can have attachments.

_images/er_cms.png

The CMS was originally designed for news and discussion feeds, but is more commonly used for informative page contents including, but not limited to:

  • page intros

  • legal, contact and privacy information pages

  • guidance on forms or form elements

  • group announcements

  • online user guides

…as well as for notification templates.

Deployment Settings

to be written

Project Tracking

The main purpose of the project module is to track contexts of project-based business activities and collaboration.

Projects can be both multi-location and multi-organisation, through qualified links describing exactly how the respective location or organisation is involved.

Activities represent concrete actions within a project, with place, time and type.

Various categories are available for both activities and projects, e.g. themes, sectors, and hazards addressed.

Additionally, the module provides a basic task management, which can also be used standalone for simple TODO lists.

_images/er_project.png

Deployment Settings

to be written

Business Data Models

The models implement data structures for specific business cases. Typically, they have been developed for actual deployments, and then (often only partially) generalized.

Note

Some of these models may be under active development, and thus this documentation not always fully up-to-date - please study the current code before planning your project.

Disease Tracking

This module implements data elements to track disease outbreaks, both on the individual case level, and in mass testing. It was originally developed for Ebola Virus Disease outbreaks, and has later been re-used during the COVID-19 pandemic.

_images/er_disease.png
Training Courses and Events

to be written

Standard CRUD Methods

Data Tables

Tabular view of records (end-point: /list, and default for table end-point without method and interactive data format).

_images/list_filter.png

Data Table View with Filter Form

Form-based CRUD

Simple, form-based Create, Read, Update and Delete functions.

Create

End-point: /create

_images/create.png

Create-form

Read

End-point: [id]/read

_images/read_tabs.png

Read view with component tabs

Update

End-point: [id]/update

_images/update_tabs.png

Update-form on tab

Delete

End-point: [id]/delete

Map

Filterable Map (end-point: /map).

_images/map_filter.png

Map with filter form

Pivottable Reports

User-definable pivot tables with chart option (end-point: /report).

_images/report.png

Pivot Table Report

_images/report_charts.png

Pivot Table with Chart

Note

This method requires configuration.

Timeplot

Aggregation and visualisation of one or more numeric facts over a time axis (endpoint: /timeplot).

_images/timeplot.png

Configuration

The timeplot_options table setting is used to configure the report:

Example of timeplot_options configuration
 facts = [(T("Number of Tests"), "sum(tests_total)"),
          (T("Number of Positive Test Results"), "sum(tests_positive)"),
          (T("Number of Reports"), "count(id)"),
          ]

 timeframes = [("All up to now", "", "", ""),
               ("Last 6 Months", "-6months", "", "weeks"),
               ("Last 3 Months", "-3months", "", "weeks"),
               ("Last Month", "-1month", "", "days"),
               ("Last Week", "-1week", "", "days"),
               ]

 timeplot_options = {
     "facts": facts,
     "timestamp": [(T("per interval"), "date,date"),
                   (T("cumulative"), "date"),
                   ],
     "time": timeframes,
     "defaults": {"fact": facts[:2],
                  "timestamp": "date,date",
                  "time": timeframes[-1],
                  },
     }

 s3db.configure("disease_testing_report",
                timeplot_options = timeplot_options,
                )

The attributes of the timeplot_options setting are as follows:

Option

Type

Explanation

facts

list

The selectable facts as tuples (label, expression)

timestamp

list

Selectable time stamps as tuples (label, expr)

If expr contains two comma-separated field selectors, it is
interpreted as “start,end”.

If expr is a single field selector, it is interpreted as
start date; in this case events are treated as open-ended,
and hence facts cumulating over time.

time

list

List of time frames as tuples (label, start, end, slots)

start and end can be either absolute dates (ISO-format),

A relative start is relative to now.

A relative end is relative to start, or, if no start
is specified, it is relative to now.

start "" means the date of the earliest recorded
event, end "" means now.

The slots length is the default for the time frame, but can
be overridden with an explicit slot-selector (see below).

slots

list

List of tuples (label, expr)

A separate selector for the slot length is rendered only if
this option is configured.

Otherwise, the slot length is fixed to that specified by the
selected time frame option.

defaults

dict

Default values for the timeplot options

Same attributes as the top-level attributes, each taking a
single item of the respective list (except fact, which
accepts a list).
Relative Time Expressions

The start and end parameters for the time frame of the report support relative expressions of the form [<|>][+|-]{n}[year|month|week|day|hour]s.

The n is an integer, e.g.:

"-1 year"    # one year back
"+2 weeks"   # two weeks onward

Additionally, the < and > markers can be added to indicate the start/end of the respective calendar period, e.g.:

"<-1 year"   # one year back, 1st of January
">+2 weeks"  # two weeks onward, Sunday

In this context, weeks go from Monday (first day) to Sunday (last day).

Note

Even when using < and > markers, the rule that end is relative to start still applies.

This can be confusing when using these markers for both interval ends, e.g. the time frame for January 1st to December 31st of last year is not:

("<-1 year", ">-1 year")

but actually:

("<-1 year", ">+0 years")

…namely, from the beginning of last year to the end of that same year.

More intuitive in this case is to specify: ("<-1 year", "+1 year").

Summary

Meta-method with multiple other methods on the same page (on tabs), and a common filter form (end-point: /summary).

_images/summary.png

Summary view with table, report and map tabs, and common filter form.

Note

This method requires configuration.

Organizer

Calendar-based view and manipulation of records (end-point: /organize)

_images/organize.png

Organizer (Weekly Agenda View)

Note

This method requires configuration of start and end date fields, as well as of popup contents.

Spreadsheet Importer

Interactive Spreadsheet (CSV/XLS) Importer with review and record selection (end-point: /import).

_images/import_upload.png

Spreadsheet Importer, Upload Dialog

_images/import_commit.png

Spreadsheet Importer, Review and Record Selection

User Interface Elements

Form Widgets

Filter Widgets and Forms

DataTable

The DataTable widget represents a set of records as an interactive HTML table.

DataTables are one of the most common UI features in EdenASP, and a standard aspect of interactive CRUD.

The DataTable class implements the server-side functions to configure, build and update a DataTable. The client-side parts are implemented by the s3.ui.datatable.js script, using jQuery datatables.

Overview
class DataTable(rfields, data, table_id=None, orderby=None)
Parameters:
  • rfields – the table columns, [S3ResourceField, …]

  • data – the data, [{colname: value, …}, …]

  • table_id – the DOM ID for the <table> element

  • orderby – the DAL orderby expression that was used to extract the data

Note

The first column should be the record ID.

html(totalrows, filteredrows, **attr)

Builds the data table HTML.

Parameters:
  • totalrows – total number of rows available

  • filteredrows – total number of rows matching filters

  • attrbuild parameters

Returns:

the HTML for the data table widget

Return type:

FORM

json(totalrows, filteredrows, draw, **attr)

Builds a JSON object to update the data table.

Parameters:
  • totalrows – total number of rows available

  • filteredrows – total number of rows matching filters

  • draw – unaltered copy of “draw” parameter sent from the client

  • attrbuild parameters

Returns:

the JSON data

Return type:

str

Example

Typically, DataTable views are implemented in CRUD methods.

The following example implements a DataTable view for the org_facility table, including server-side pagination and Ajax-filtering, like this:

_images/dt_example.png

class FacilityList(CRUDMethod):

    def apply_method(self, r, **attr):

        get_vars = r.get_vars

        # -----------------------------------------------------------------
        # Pagination

        page_length = 25
        if r.interactive:
            # Default limits when page is first loaded
            # - extracting twice the page length here to fill the cache,
            #   so no Ajax-request is required for the first two pages
            start, limit = 0, 2 * page_length
        else:
            # Dynamic limits for subsequent Ajax-requests
            start, limit = self._limits(get_vars, default_limit=page_length)

        # -----------------------------------------------------------------
        # Extract the data, applying client-side filters/sorting

        resource = current.s3db.resource("org_facility")
        fields = ["id", "name", "organisation_id", "location_id"]

        query, orderby, left = resource.datatable_filter(fields, get_vars)
        if query is not None:
            totalrows = resource.count()
            resource.add_filter(query)

        data = resource.select(fields,
                               start = start,
                               limit = limit,
                               left = left,
                               orderby = orderby,
                               count = True,
                               represent = True,
                               )

        filteredrows = data.numrows
        if query is None:
            totalrows = filteredrows

        # -----------------------------------------------------------------
        # Set up the DataTable

        from core import DataTable
        dt = DataTable(data.rfields, data.rows, "facility_list")

        # -----------------------------------------------------------------
        # Configure row actions (before building the DataTable)

        current.response.s3.actions = [{"label": "Read",
                                        "url": URL(args = ["[id]", "read"]),
                                        "_class": "action-btn"
                                        },
                                       ]

        # -----------------------------------------------------------------
        # Build the DataTable

        # Rendering parameters to pass to .html() and .json()
        dtargs = {"dt_pagination": True,
                  "dt_pageLength": page_length,
                  "dt_base_url": URL(args=[], vars={}),
                  }

        if r.interactive:
            # This is the initial page load request
            # - build the HTML:
            dt_html = dt.html(totalrows, filteredrows, **dtargs)
            output = {"items": dt_html}

        elif r.representation == "aadata":
            # Client-side script uses the "aadata" extension to request updates
            # - generate a JSON response:
            draw = int(r.get_vars.get("draw", 1))
            output = dt.json(totalrows, filteredrows, draw, **dtargs)

        else:
            r.error(405, current.ERROR.BAD_FORMAT)

        # View template, includes dataTables.html
        current.response.view = "list.html"

        return output

Note

The view template must include the dataTables.html template to add the necessary JavaScript for the DataTable widget.

Build Parameters

Both build methods html() and json() accept the same set of keyword arguments to control the build of the DataTable. Most of these arguments are optional (see example above for a typical minimum set).

Basic configuration

Basic parameters for the data table.

Keyword

Type

Default

Explanation

dt_ajax_url

str

None

URL for Ajax requests

dt_base_url

str

None

Base URL for exports, usually the resource default URL without any method or query part

dt_dom

str

None

The jQuery datatable “dom” option,
determines the order in which elements are displayed

dt_formkey

str

None

A form key (XSRF protection for Ajax requests)

Pagination

Parameters for pagination (server-side pagination requires dt_ajax_url).

Keyword

Type

Default

Explanation

dt_pagination

bool

True

Enable/disable pagination

dt_pageLength

int

25

Default number of records that will be shown per page
- the user can change this using the length menu

dt_lengthMenu

tuple

[[25, 50, -1], [25, 50, “All”]]

The menu options for the page length

dt_pagingType

str

deployment setting

How the pagination buttons are displayed
- settings.ui.datatables_pagingType (default full_numbers)
Searching

Parameters to control the search box.

Keyword

Type

Default

Explanation

dt_searching

bool

True

Enable/disable search-field

Note

The search box should normally be disabled when using separate filter forms.

Row Actions

Keyword

Type

Default

Explanation

dt_row_actions

list

None

list of actions (each a dict)
- overrides current.response.s3.actions

dt_action_col

int

0

The column where the action buttons will be placed

Bulk Actions

Bulk-action DataTable views render an additional column with checkboxes to select rows and then perform actions “in bulk” for all selected rows with a single button click.

_images/dt_bulk.png

Spreadsheet Importer: DataTable with bulk action column.

Keyword

Type

Default

Explanation

dt_bulk_actions

list

None

list of labels for the bulk actions

dt_bulk_col

int

0

The column in which the checkboxes will appear,
- default: insert bulk actions as first column

dt_bulk_single

bool

False

allow only one row to be selected

dt_bulk_selected

list

None

list of (pre-)selected items

Note

Bulk-actions require server-side processing of the DataTable FORM upon submit.

Grouping

Group table rows by column values.

Keyword

Type

Default

Explanation

dt_group

list

None

The column(s) that is(are) used to group the data

dt_group_totals

list

None

The number of record in each group.
- this will be displayed in parenthesis after the group title.

dt_group_titles

list

None

The titles to be used for each group.
These are a list of lists with the inner list
consisting of two values, the repr from the
db and the label to display. This can be more than
the actual number of groups (giving an empty group).

dt_group_space

bool

False

Insert a space between the group heading and the next group

dt_shrink_groups

str

None

If set then the rows within a group will be hidden
two types are supported, ‘individual’ and ‘accordion’

dt_group_types

str

None

The type of indicator for groups that can be ‘shrunk’
Permitted valies are: ‘icon’ (the default) ‘text’ and ‘none’
Contents Rendering

Keyword

Type

Default

Explanation

dt_text_maximum_len

int

80

The maximum length of text before it is condensed

dt_text_condense_len

int

75

The length displayed text is condensed down to

Styles

Keyword

Type

Default

Explanation

dt_styles

dict

None

dictionary of styles to be applied to a list of ids
- example: {“warning” : [1,3,6,,9], “alert” : [2,10,13]}

dt_col_widths

dict

None

dictionary of columns to apply a width to
- example: {1 : 15, 2 : 20}
Other Features

Keyword

Type

Default

Explanation

dt_double_scroll

bool

False

Render double scroll bars (top+bottom), only available
with settings.ui.datatables_responsive=False
Response Parameters

to be written

Deployment Settings

to be written

Card Lists

Tools

The core.tools library provides a number of tools for common application tasks, e.g. representing data, handling date and time, or importing data.

This section describes the tools, and their most relevant functions.

Bulk Importer

The BulkImporter is a tool to run a series of data import tasks from a configuration file. It is most commonly used during the first run of the application, to pre-populate the database with essential data (a process called prepop).

The individual import task handlers of the BulkImporter can also be used standalone, e.g. in upgrade/maintenance scripts, or for database administration from the CLI.

Configuration File

Configuration files for the BulkImporter are CSV-like files that must be named task.cfg, and are typically placed in the template directory to be picked up by the first-run script.

Example of tasks.cfg
# Roles
*,import_roles,auth_roles.csv
# GIS
gis,marker,gis_marker.csv,marker.xsl
gis,config,gis_config.csv,config.xsl
gis,hierarchy,gis_hierarchy.csv,hierarchy.xsl
gis,layer_feature,gis_layer_feature.csv,layer_feature.xsl

Tip

This file format differs from normal CSV in that it allows for comments, i.e. everything from # to the end of the line is ignored by the parser.

Each line in the file specifies a task for the BulkImporter. The general format of a task is:

<prefix>,<name>,<filename>,<xslt_path>

By default, tasks is the S3CSV import handler (import_csv). In this case, the task parameters are:

Parameter

Meaning

prefix

The module prefix of the table name (e.g. org)

name

The table name without module prefix (e.g. organisation)

filename

- the source file name (if located in the same directory as tasks.cfg), or
- a file system path relative to modules/templates, or
- an absolute file system path, or
- a HTTP/HTTPs URL to fetch the file from

stylesheet

- the name of the transformation stylesheet (if located in static/formats/s3csv/<prefix>), or
- a file system path relative to static/formats/s3csv, or
- a file system path starting with ./ relative to the location of the CSV file
Import Handlers

It is possible to override the default handler for a task with a prefix *, and then specifying the import handler with the name parameter, i.e.:

*,<handler>,<filename>,<arg>,<arg>,...

In this case, the number and meaning of the further parameters depends on the respective handler:

Handler

Task Format, Action

import_xml

*,import_xml,<filename>,<prefix>,<name>,<dataformat>,<source_type>
- import XML/JSON data using static/formats/<dataformat>/import.xsl
- source_type can be xml or json

import_roles

*,import_roles,<filename>
- import user roles and permissions from CSV with a special format

import_users

*,import_roles,<filename>
- import user accounts with special pre-processing of the data

import_images

*,import_images,<filename>,tablename,keyfield,imagefield
- import image files and store them in record of the specified table
- source file is a CSV file with columns id and file
- records are identified by keyfield matching the id in the source file

schedule_task

*,schedule_task,,taskname,args,vars,params
- schedule a task with the scheduler
- args, vars and params are JSON strings, but can use single quotes
- args (list) and vars (dict) are passed to the task function
- params (dict) specifies the task parameters, e.g. frequency of execution
- second task parameter (filename) is empty here (not a typo)!

It is possible to run the import task handlers standalone, e.g.:

Running a task handler function standalone
from core import BulkImporter
path = os.path.join(current.request.folder, "modules", "templates", "MYTEMPLATE", "auth_roles.csv")
error = BulkImporter.import_roles(path)

The arguments for the handler function are the same as for the task line in the tasks.cfg (except * and handler name of course). All handler functions return an error message upon failure (or a list of error messages, if there were multiple errors) - or None on success.

Note

When running task handlers standalone (e.g. in a script, or from the CLI), the import result will not automatically be committed - an explicit db.commit() is required.

Task Runner

The task runner is a BulkImporter instance. To run tasks, the perform_tasks method is called with the path where the tasks.cfg file is located:

from core import BulkImporter
bi = BulkImporter()

path = os.path.join(current.request.folder, "modules", "templates", "MYTEMPLATE")
bi.perform_tasks(path)

Important

The task runner automatically commits all imports - i.e. perform_tasks cannot be rolled back!

Template-specific Task Handlers

It is possible for templates to add further task handlers to the BulkImporter, e.g. to perform special (import or other) tasks during prepop.

Template-specific task handler for the BulkImporter, in config.py
# Define the task handler:
# - must take filename as first argument
# - further arguments are freely definable, but tasks must match
#   this signature
def special_import_handler(filename, arg1, arg2):
    ...do something with filename and args

# Configure a dict {name: function} for template-specific task handlers:
settings.base.import_handlers = {"import_special": special_import_handler}

This also allows to override existing task handlers with template-specific variants.

With this, tasks for the new handler can be added to tasks.cfg like:

*,import_special,<filename>,<arg1>,<arg2>

Note

When received by the handler, the filename will be completed with a path, (see interpretation of filename in tasks.cfg). All other parameters are passed-in unaltered.

However, the filename parameter can be left empty, and/or get ignored by the task handler, if a file name is not required for the task.

How to deploy Eden ASP applications

Eden ASP is normally deployed behind a separate front-end web server (e.g. nginx) using WSGI/uWSGI to plugin web2py. This section describes how to setup a production instance of an Eden ASP application on a Debian server.

Extending Eden ASP

Implementing Controllers

Basic Concepts

CRUDRequest

Implementing CRUD Controllers

crud_controller
prep
postp

Implementing Data Models

Basic Concepts

Model Loader s3db
Resources
Components
Super-Entities
Field Selectors and Resource Queries

Defining Tables

Subclassing DataModel
model()
defaults()
mandatory()
Exposing names
Defining Tables

Table Configuration

CRUD Hooks
Linking to Super-Entities
CRUD Strings
Adding Components
Adding Methods

Reusable Fields

Common Field Functions
Meta-Fields
Implementing Reusable Fields

Field Representation

Common Representation Functions
Foreign Key Bulk Representation (S3Represent)

Adding new themes

Adding new Form Widgets

Indices and tables