Most Powerful Open Source ERP

How To Setup Web Theme

How To showing how to create a unique design for every site or every client.
  • Last Update:2016-02-09
  • Version:001
  • Language:en

The way we recommend to use erp5_web is to:

  • take an existing 'standard' HTML design
  • refactor the HTML / CSS with TAL, METAL and widgets to create a new erp5_theme

Table of Contents


erp5_web is designed to make this process as quick as possible. It is ideal to work in collaboration with graphic designers and Web agencies which do not need to know about the details of ERP5 or erp5_web. It is also ideal to create a unique design for every site or every client.

This approach is different from the approach of certain CMS which rather focus on providing a very generic and hopefully universal layout which can be somehow tweaked through CSS. People interested in a minimal universal layout can have a look a erp5_minimal_theme, which is currently more intended for developers and test rather than for real use in commercial Web sites.

In the remaining of this How To, we are going to explain how we created erp5_multiflex5_theme business template.

Before proceeding further, make sure you read:

  • ERP5 XHTML Style (Free Subscription) which explains the rendering process of ERP5
  • ERP5 Web (Free Subscription) which explains the architecture of ERP5 Web

Upload Static Files

The Multiflex5 theme is a public domain theme created by Gerhard of

The first step consists of creating a new folder in portal_skins and uploading all static files provided by the designer into that folder.

The files which must be uploaded include:

  • images (as Image object - use WebDAV if you have many images to upload, but not for the other file types since it would create DTML Page objects instead of Page Template objects)
  • CSS (as Page Template object)
  • HTML (as Page Template object)
  • JS (as Page Template object)

Do not forget to add: at the head of CSS. This is required to make sure that the CSS Page Template is considered as a CSS file by Web Browsers. Add a similar instruction for JS files or for any Text based Page Template which is not HTML.

Use a non ambiguous namespace for images (ie. do not user img for example but multiflex5_image) so that it is possible to mix multiple Web themes within a single site yet use caching. But remember that the CSS files may have references to the image folder: don't forget to rename it in the CSS files too.

Create the default main template files

Our goal is to create an empty layout which can be fed with:

  • content (the content of the pages, forms)
  • widgets (ie. building blocks of the page)
  • gadgets (ie. drag & dropable boxes, actually implemented as a widget)

There are 2 possible ways:

  • monolithic Page Template with TAL and METAL macros to create dynamic content and include widgets or gadgets (best for speed)
  • use of ERP5 native widget system (best for modularity but caching is required for speed)

The 2 approaches can be combined.

Let us try the "widget" approach. We need to create an ERP5 Form:


The page template of this form should be set in the Setting tab to:


Let us rename and add a few groups (Order Tab)

  right sidemenu1 (Subsection List)

The logical structure and building blocks of the page will appear in the end in:




will only be used to include each block defined in erp5_web_multiflex5_layout into the HTML code

Now, we are going to create the file:


It is a Page Template that we will later fill with all the code needed to build the page.

Create a skin

In Zope CMF, a skin will define which folders from the Skins Tool are used to fetch everything related to a theme : the CSS files, the templates, the static images... Each skin is made of several layers, each layer being a folder in the portal_skins tool.

In order to be able to use the new theme, you will have to add a new skin in portal_skins, and use it.

  • Go to the portal_skins tool
  • Go to the tab Properties
  • Type in a new skin name, multiflex5, and layers.You will have to include several layers like erp5_xhtml_style, erp5_core... The first item in this list has to be the name of the folder you created in the portal_skins. After filling in these two fields, add the skin.

Create a Web Site instance

Go to Web Site module. Add a Web Site.

Define the default layouts (Container and Content) to


Define the Skin name to:


Rename the ID of the website to:


Create a Web Page with some content. Let us suppose this page is:


Then go to the URL:


At this stage, you will likely get an error. Our goal now is to make sure the content of the web page is display in the right location.


template_erp5_xhtml_style is the mother HTML Template of anything in ERP5. It defines a "layout" slot where you will inject the part of your Page Template. Everything else exists only for theme designers.

You will have to insert the following code in your page template body in order to insert it in the slot. This code defines a couple of useful variables and, more important, tells template_erp5_xhtml_style which javascripts and CSS to embed in addition to other JS and CSS which are embedded by default.


  <tal:block metal:define-macro="master">
     <tal:block tal:define="website here/getWebSiteValue;
                            enctype string:multipart/form-data;
                            portal_path python:website.absolute_url();
                            current_main_section here/WebSite_getMainSection;
                            editable request/editable_mode | python: 0;
                            list_mode list_mode | nothing;
                            dialog_mode dialog_mode | nothing;
                            has_no_layout python: layout_form is None;
                            dummy python: request.set('dialog_mode', dialog_mode);
                            dummy python: request.set('list_mode', list_mode);
                            global css_list_template python:[
                                  '%s/mf54_reset.css' % portal_path,
                                  '%s/mf54_grid.css' % portal_path,
                                  '%s/mf54_content.css' % portal_path,
                            global js_list python:[
                                  '%s/erp5_tabber.js' % portal_path,
        <tal:block metal:use-macro="here/template_erp5_xhtml_style/macros/master">
          <tal:block metal:fill-slot="layout">




The code to insert in the layout slot is the static code of one of the Multiflex5 templates, found in its HTML files.

After injecting this code, you'll be able to display the static layout with no dynamic content.

Note that all the headers of the HTML are managed by template_erp5_xhtml_style. template_erp5_xhtml_style should be rich enough to cover all possible cases. If this is not the case, try to extend it in a generic way and contribute to erp5_xhtml_style core. If what you want to do is very specific, you can consider making your own version of template_erp5_xhtml_style although this is not recommended for maintenance.

Add the "content" slot

Find out in the static code the location which can be considered as "content". Then erase and replace with a METAL expression like this:

  <!-- C. MAIN SECTION -->      
  <div class="main">
    <h1 class="pagetitle">Content Toolbox (With sidebar)</h1>

    <!-- C.1 CONTENT -->
    <div class="main_content">
      <div class="corner-content-1col-top"></div>                        
      <div class="content-1col-nobox">
        <tal:block metal:define-slot="main"/>
      <div class="corner-content-1col-bottom"></div>                       

(You can notice that the class name has been renamed to main_content. It is required to rename a few CSS class names (content, document...) in order to prevent any conflict between the web theme originated HTML code and the ERP5-generated HTML code)

From now on, the METAL expression will be replaced with the content generated by ERP5 Forms. Let us look at an example showing how the metal expression is replaced and what the content looks like:

<!-- C.1 CONTENT -->
<div class="main_content">
  <div class="corner-content-1col-top"></div>                        
  <div class="content-1col-nobox">
    <div class="document">            
      <div class="content">
        <fieldset id="fieldset_webcontent_header_bottom" class="webcontent header bottom">
          <div title="" class="field headline">
            <label>Title </label>
            <div class="input"><span class="headline">10</span></div>
        <fieldset id="fieldset_bottom" class="bottom">
          <div title="" class="field page hiddenLabel">
            <label>Page Content</label>
            <div class="input">
              <h1>1-03. Text</h1>
              <p>Standard text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text</p>
      <p class="clear"/>
  <div class="corner-content-1col-bottom"></div>                       

As we can see the content generated by ERP5 add two embedded divs:

  <div class="document">            
  <div class="content">

Inside the content div, all forms are rendered using a structure which combines the following tags:


The rendering of Web Page uses 2 fields:

  • one to display the title
  • one to display the content of the page

However, it is well know that certain Web layouts consider the title of a Page as part of the layout while others consider as part of content. For this reason, in order to help CSS authors to display or hide that information, the following classes were defined in ERP5 :

  • headline
  • header

All this code is generated automatically by erp5_xhtml_style and form_render. It is not a good idea to change it.

If you have a problem of CSS class name conflict (for instance on the "content" class), rename the classes of the Web theme rather than those of erp5_xhtml_style.

Define widget structure one by one

First of all, look at the page and name the different groups.


  • header: a header which can contain multiple subgroups (ex. header_top, header_bottom)
  • top: an area which is at the top of the content area, under the header
  • headline: an are which is at the top of the content itself, within the content area
  • left: an area with boxes at the left of the content area
  • right: an area with boxes at the right of the content area
  • footer: an area under the content area

Next, extend the variable definition in:



aggregate python: has_no_layout or layout_form.Form_getGroupList(
                  ['header', 'top', 'headline', 'subcontent', 'footer']);
aggregate python: ((aggregate is not has_no_layout) and dict(aggregate)) or {};
header_area python:aggregate.get('header', []);
top_area python:aggregate.get('top', []);
headline_area python:aggregate.get('headline', []);
subcontent_area python:aggregate.get('subcontent', []);
footer_area python:aggregate.get('footer', []);

The goal here is to define the different aggregate groups to use to fill the page.

Then add the following line to render the area named 'subcontent':

<!-- Layout form rendering -->
<tal:block tal:condition="python: layout_form is not None">
  <tal:block metal:use-macro="here/aggregate_render/macros/aggregate_populate_request"/>
  <tal:block tal:repeat="aggregate python: [('subcontent', subcontent_area)]">
      <tal:block metal:use-macro="here/aggregate_render/macros/aggregate_render"/>

Then move HTML code from the static page to the layout form:


The idea is to create an "Editor Field" then set:

editable = False
display = True

and fill the "Default" value with static HTML code which is going to be replaced later with dynamic code.

Create dynamic content for widgets

Create a new field and associated Widget renderer in the form of a Page Template:


Here is an example of code for a Widget:

<tal:block replace="nothing">
  This widget displays the list of Sections contained in the current context.
    * make sure translation is possible
    * make sure URLs are correct
    * use generic method (erp5_web) to retrieve the list of subsections
    * make it hierarchic
<ul tal:define="current_web_section python:request.get('current_web_section', here)">
  <li tal:repeat="subsection python:current_web_section.WebSection_getSiteMapTree(depth=1)"><a href="#" tal:content="subsection/translated_title"
  tal:attributes="href subsection/url">Menu item</a></li>

Please watch here how we use the request to retrieve the rendering environment. This is where we should find the current document, current context, current section, etc. It helps accelerating rendering.

Add dynamic parameters to the CSS Page Template

Add the following kind of code at the head the CSS:

<tal:block define="dummy python: request.RESPONSE.setHeader('Content-Type', 'text/css;; charset=utf-8')"/>
  <tal:block define="
    site_url python:here.absolute_url();
    body_color python:here.getLayoutProperty('layout_body_color', 'rgb(240,240,240)');
    primary_color python:here.getLayoutProperty('layout_primary_color', 'rgb(200,200,200)');
    secondary_color python:here.getLayoutProperty('layout_secondary_color', 'rgb(225,225,225)');
    header_color python:here.getLayoutProperty('layout_header_color', 'rgb(218,218,218)');
    menu_color python:here.getLayoutProperty('layout_menu_color', 'rgb(235,235,235)');
    container_width python:here.getLayoutProperty('layout_container_width', 978);
    border_width python:here.getLayoutProperty('layout_border_width', 10);
    content_width python:container_width - 2 * border_width;
    header_background_image python:here.getLayoutProperty('layout_header_background_image', 
    header_background_image python:'%s/%s' % (site_url, header_background_image);
  <!-- Original CSS code goes here -->


and make every CSS dynamic such as in:

.header {width:px; min-height:130px /*Non-IE6*/; height:auto !important /*Non-IE6*/; height:130px /*IE6*/; border-left:solid px ; border-right:solid px ; background: url() repeat-x /*Total header background image entered here. Height: 125px*/; font-size:1.0em;}

The idea is to make the following items configurable:

  • width
  • colors
  • images
  • choice of fonts

For this, you will have to create too a form named


and define all required field in this form. In our example:

  • layout_body_color
  • layout_primary_color
  • layout_secondary_color
  • layout_header_color
  • layout_menu_color
  • layout_container_width
  • layout_border_width
  • layout_header_background_image

Related Articles