Sahana Eden

Customization

Sahana Eden is developed using Python and JavaScript. However, amazing as it may sound, simple customization can be done without any knowledge of either programming language. In fact there is just a single concept which is essential to understand before diving in: whitespace matters.

Why Whitespace Matters

Unlike many programming languages, Python code is sensitive to whitespace. To avoid bad results as an impact of this sensitivity, make sure that you indent the code in .py files properly. This is easiest if you configure your text editor to replace Tabs with 4 spaces:

The screenshot is from Notepad++, a recommended editor on Windows, where you can find this by going to Settings -> Preferences... -> Language Menu/Tab Settings -> Tab Settings (group)

Debug mode

As a developer, you should normally  run the application in Debug mode.

This means that JavaScript & CSS files are loaded in separate, uncompressed versions and also automatically reloads the core models in modules/s3db. Changes to other files in modules/ still require a restart of Web2Py to become visible.

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

Which file do I edit?

Sahana Eden runs as a Web2Py application. The code is in the folder:

web2py/applications/eden

Inside that folder are folders for Models (define the data structure), Controllers (provide URLs to enable access to the data) & Views (HTML templates).

Each module within Sahana Eden will normally consist of one of each of these files:

  • Model: modules/s3db/modulename.py
  • Controller: controllers/modulename.py
  • View: views/modulename/index.html

In order to know which file to edit in order to change a particular function, you need to look at the URL. The Web2Py web framework maps URLs as follows:

http://host/application/controller/function

So, if you want to edit the Home page with the URL:

http://host/eden/default/index

This implies that you should look at the file eden/controllers/default.py and the index function within it which can be found by searching for the function title "def index():"

Tip: Sahana Eden makes heavy use of integrated resource controllers so the typical mapping is:

http://host/eden/module/resource

The resource refers to a table with the name module_resource in the file modules/s3db/<module>.py

Templates

When making customizations, it is better if you can retain compatibility with the upstream trunk code, so that you can 'pull' updates with less chance of conflicts. For this reason it is better if all your customizations are contained within your own folder inside private/templates/ eg.  private/templates/template_name.

If, on the other hand, you are fixing a bug or developing functionality useful to other Sahana users, then that should be done in the core code & your changes submitted upstream via a 'pull request'. See the Git chapter.

To set the template used by Sahana Eden, edit this line in the file models/000_config.py

settings.base.template = "template_name"

Once you have created a folder for your template, you can place some of these files in:

  • Module loader: private/templates/template_name/__init__.py
  • Main Configuration: private/templates/template_name/config.py
  • Prepopulate Configuration: private/templates/template_name/tasks.cfg
  • CSS Configuration: private/templates/template_name/css.cfg
  • Custom Controllers: private/templates/template_name/controllers.py
  • Custom Menus: private/templates/template_name/menus.py
  • Custom Layouts: private/templates/template_name/layouts.py
  • Custom Views: private/templates/template_name/views/

Not all of these files are needed in every template. We will now go through what each of these are used for:

__init__.py is needed if there are custom controllers, menus or layouts. It is normally empty.

config.py is needed if there are any custom deployment_settings which are common to all instances of this deployment (e.g. production, Test, Demo &/or Training) or customizations to standard controllers. Most templates will contain one of these unless the folder is just a collection of pre-populate files or just a theme.

tasks.cfg is a collection of pre-populate CSV files. These are used to configure the base system with lookup lists (e.g. Organization Types) and Map configuration. They can also contain Demo data. The CSV files can be in this folder, another folder or even downloaded from a remote server.

css.cfg is a collection of CSS files which are loaded in every page for this Theme (a template folder is often linked to a theme of the same name, however this is not mandatory. Many templates can share a common theme). In non-debug mode the files are compressed together and downloaded as one file, whereas in debug mode they are downloaded individually. The CSS files can be from libraries, such as jQueryUI, from other templates, such as the default template, or custom ones for this theme.

controllers.py allows the creation of fully custom controllers. The most common usage is to provide a fully custom homepage (default/index). Additional pages may be created as default/custom/mycustompage.

menus.py allows custom menus. 

layouts.py allows custom design for menus.

views/ folder allows custom views. For some core system pages which are commonly changed (such as the homepage, About, Contact, Help), it is sufficient to simply drop an HTML file into this folder. A Theme will normally have a customized layout.html here.

Home Page and other simple Views

One of the common changes that needs to be made is modifying the main home page, individual module home pages and the Contact/About pages.

Some of these customizations can be done in pure HTML by editing the View files. For example, you can edit the contact page at URL /eden/default/contact by copying the file: views/default/contact.html to private/templates/template_name/views/contact.html

This is a normal HTML page other than the part(s) inside double curly braces {{...}} which indicate Python code.

{{extend "layout.html"}}

This part should not be edited as it loads the generic page layout from views/layout.html or, if using a custom theme, from private/templates/template_name/views/layout.html.

Most pages, such as the main home page, include the results of Python code executed in the controller file. This is executed before the view template is parsed and dynamically generates content which is inserted in the curly brackets inside the HTML. The controller file for the main home page is controllers/default.py

The simple way to start customizing this page is to ignore some or all of this dynamic content and replace it with simple HTML content in the view template:

private/templates/template_name/views/index.html

Tip: Sahana Eden doesn't make use of many custom views in the core modules – normally the generic view templates within the views/ folder are used, such as _create.html and _list.html. Modifications are made by configuration in the Model and Controller files. However it is possible to customise these views by adding these into private/templates/template_name/views/

Edit a Field Label

This can be done by editing the 'label' attribute in the field properties within the model.

Example:

Change the text 'Year' to 'Year Founded' in the form at URL: /eden/org/organisation/create

 

You can lookup the fieldname in the main model  modules/s3db/org.py:

tablename = "org_organisation"
table = define_table(tablename,
... Field("year", label = T("Year"),

Since we are putting our customizations into our Template, we need to customize the controller in private/templates/template_name/config.py:

def customize_org_organisation(**attr):
    table = current.s3db.org_organisation
    table.year.label = T("Year Founded")
    return attr
settings.ui.customize_org_organisation = customize_org_organisation

Tip: The strings are internationalized by wrapping them inside T(). If you change the labels then you need to update any translation files that you are using, even if the translation remains the same.

For consistency, you should also edit the heading of the help tooltip, and maybe the body, if the meaning is being changed. This is found in the 'comment' attribute:

table.year.comment = DIV(_class="tooltip",
              _title="%s|%s" % (T("Year Founded"),
                                T("Year that the organization was founded.")))),


Hide a Field

It is common to want to hide a field to simplify a form to allow efficient collection of the data that you need. Hiding a field is safer than removing it completely from the database as it eases upgrades by eliminating the need for database migration.

Example:

Hide the 'Code' field from the form at URL: /eden/org/office/create

Since we are putting our customizations into our Template, we need to customize the controller in private/templates/template_name/config.py:

def customize_org_office(**attr):
    table = current.s3db.org_office
    table.code.readable  = False
    table.code.writable  = False
    return attr
settings.ui.customize_org_office = customize_org_office

Tip: Python variables are case-sensitive, as are the boolean values 'True' and 'False'.

Add a New Field

If you need to collect extra data for an existing resource, then it isn't currently possible to do this within a template. However it is simple and relatively safe to add an extra field to the model for that table.

Example:

Add the ability to store a Facebook page for Organizations.

Look at modules/s3db/org.py and add the new field to:


tablename = "org_organisation"
table = define_table(tablename,
                     ...
                     Field("facebook", label=T("Facebook Page")),

Tip: Be careful to add trailing commas to each line in the table definition as this is a common source of errors.

Edit the Menus

If you wish to edit the left (second-level) menus (e.g. relabeling, reordering or hiding entries) then create the file private/templates/template_name/menus.py. We can copy from modules/s3menus.py and override the menu for just the modules that we need.

Example:

In the Organization Registry Module, re-name 'Offices' as 'Venues' and hide the 'Search' & 'Map' options:


from s3layouts import *
try:
    from .layouts import *
except ImportError:
    pass
import s3menus as default

class S3OptionsMenu(default.S3OptionsMenu):
    """ Custom Controller Menus """
    def org(self):
        """ ORG / Organization Registry """

       return M(c="org")(
                M("Organizations", f="organisation")(
                    M("New", m="create"),
                    M("List All"),
                    M("Search", m="search"),
                    M("Import", m="import")
                ),
                M("Venues", f="office")(
                    M("New", m="create"),
                    M("List All"),
                    #M("Map", m="map"),
                    #M("Search", m="search"),
                    M("Import", m="import")
                ),
            )

You can see this change in the Organization Registry Module at URL: /eden/org

Tip: The # character comments out the text after it on that line so that Python will ignore it.