First steps with OpenERP

This article is an overview of the software OpenERP.

Firstly, the company and the context in which the software evolves, are presented. Then the technical aspects of OpenERP are introduced: architecture, content module, management views and objects ...

OpenERP is ...

An open source ERP

It is indicated in the name! OpenERP is an Enterprise Resource Planner. The scope of the tool covers (among others) the following domains:

  • Customer Relationship Management - CRM
  • Material requirements planning - MRP
  • Project management
  • Sale
  • Purchase
  • Marketing
  • Human resources
  • And of course, accounting

OpenERP is licensed under the GNU AGPL (Affero GPL) which is an extension of the GNU GPL licence for network applications. In fact, you have the possibility to access the source code, modify it and use the software without paying a licence.

A company

OpenERP is promoted by the Belgian eponymous company, founded in 2005. At the beginning of 2010, the name OpenERP appeared in financial newspapers after a fundraise of 3 million euros, initiated by Xavier NIEL, the creator of Free - a famous french ISP.

Beyond the standard business model of open source company - bug fixing, migrations, security alerts and training - OpenERP has a SaaS offer for 39 € / month / user.

To ensure the support at a worldwide level, the company which has only 85 employees relies on a partner network.

An ecosystem community oriented

The environment OpenERP consists of 150 partner companies in the world but also of individual contributors (around 800) participating in the development of the application. The interaction between OpenERP and community contributors through Launchpad. Everyone can submit a bug and track correction, ask questions, suggest improvements, contribute translations, and finally access the source code.

The contributors are divided into different categories:

  • Core team
  • Commiter : must have realised at least two working modules
  • Drivers : active members
  • Community : free access for everyone

Thanks to this schema, more than 700 modules are available within the different branches - addons, extra addons and community addons.

Technically

In the rest of this article, we will refer to the version 5 of OpenERP; available on Windows, Linux and MAC.

Architecture

OpenERP is based on client /server in disconnected mode. Like any other ERP, it is mainly a data base - PostgreSQL - on which is added an application server called OpenERP server. This server supports the following protocols : Net-RPC et XML-RPC secured or not.

This web-service server can be directly requested by a GTK client (provided by OpenERP), or a web server (called web-client).

Server and client-web components are written in python.

The framework

OpenERP framework is called OpenObject. Its mechanisms are:

  • Object-Relationship Mapping (ORM) integrated in Object Service (OSV). The aim is to avoid the developer to use SQL.
  • Model-View-Controller (MVC) which manages inheritance of views.
  • Report generation (PDF, HTML, ODT).
  • Translations in .po files.

Moreover all the OpenERP functionalities are grouped in modules whose state is maintained by OpenERP server.

An OpenERP module

The following structure is the advised one:

  • Module name
    • _terp__.py : meta file to descibe the module
    • __init__.py : load the different python files
    • XXX.py : business objects defined in python classes
    • view : objets'views
      • XXX.xml
    • workflow : definition of workflows and processes
      • XXX.xml
    • wizard : screen sequences which handle OpenERP objects
      • XXX.py : behavior of the wizard
      • YYY.xml : view of the wizard
    • i18n : translation files
      • XXX.po
    • security : access and user rights
      • XXX.xml
      • YYY.csv

Python files define objects (database tables) with their behavior and functionalities whereas XML/CSV files load data (records in db tables).

The module descriptor

__terp__.py file is required to make your module usable. Here is an example with the base module ('base'!) :

{
    'name': 'Base',
    'version': '1.1',
    'category': 'Generic Modules/Base',
    'description': """The kernel of OpenERP, needed for all installation.""",
    'author': 'Tiny',
    'website': 'http://www.openerp.com',
    'depends': [],
    'init_xml': [
        'base_data.xml',
        'base_menu.xml',
        'security/base_security.xml',
        'res/res_security.xml',
        'maintenance/maintenance_security.xml'
    ],
    'update_xml': [
        'base_update.xml',
        'ir/wizard/wizard_menu_view.xml',
        'ir/ir.xml',
        'ir/workflow/workflow_view.xml',
        'module/module_wizard.xml',
        'module/module_view.xml',
        'module/module_data.xml',
        'module/module_report.xml',
        'res/res_request_view.xml',
        'res/res_lang_view.xml',
        'res/partner/partner_report.xml',
        'res/partner/partner_view.xml',
        'res/partner/partner_wizard.xml',
        'res/bank_view.xml',
        'res/country_view.xml',
        'res/res_currency_view.xml',
        'res/partner/crm_view.xml',
        'res/partner/partner_data.xml',
        'res/ir_property_view.xml',
        'security/base_security.xml',
        'maintenance/maintenance_view.xml',
        'security/ir.model.access.csv'
    ],
    'demo_xml': [
        'base_demo.xml',
        'res/partner/partner_demo.xml',
        'res/partner/crm_demo.xml',
        'base_test.xml'
    ],
    'installable': True,
    'active': True,
    'certificate': '0076807797149',
}

This file contains four lists:

  • depends : modules to be installed before the current one
  • init : files to be loaded during server startup
  • update : files (re)loaded at each module update
  • demo : files to be loaded when an installation is made with the 'Load demonstration data' option

Furthermore, 'category' field determines the type of the module. We distinguish base modules, local ones (charts of accounts), ... and especially profile modules which define installation templates for new data bases, at dependency level (required modules) or installation screens.

Object Service

Each business object must inherit osv.osv class to become persistent. The steps are:

  • in __init__.py file: an import of the python file must be made (for example my_object.py will contain definition of 'my_object')
  • in my_object.py :
    • import osv and fields classes
    • define 'my_object' class
    • use the introspection mechanism to update the data base tables, calling 'my_object' constructor

Here is an example with the 'res.country' class:

from osv import fields, osv

class Country(osv.osv):
    _name = 'res.country'
    _description = 'Country'
    _columns = {
        'name': fields.char('Country Name', size=64,
            help='The full name of the country.', required=True, translate=True),
        'code': fields.char('Country Code', size=2,
            help='The ISO country code in two chars.\n'
            'You can use this field for quick search.', required=True),
    }
    _sql_constraints = [
        ('name_uniq', 'unique (name)',
            'The name of the country must be unique !'),
        ('code_uniq', 'unique (code)',
            'The code of the country must be unique !')
    ]

    def name_search(self, cr, user, name='', args=None, operator='ilike',
            context=None, limit=80):
        if not args:
            args=[]
        if not context:
            context={}
        ids = False
        if len(name) == 2:
            ids = self.search(cr, user, [('code', 'ilike', name)] + args,
                    limit=limit, context=context)
        if not ids:
            ids = self.search(cr, user, [('name', operator, name)] + args,
                    limit=limit, context=context)
        return self.name_get(cr, user, ids, context)
    _order='name'

    def create(self, cursor, user, vals, context=None):
        if 'code' in vals:
            vals['code'] = vals['code'].upper()
        return super(Country, self).create(cursor, user, vals,
                context=context)

    def write(self, cursor, user, ids, vals, context=None):
        if 'code' in vals:
            vals['code'] = vals['code'].upper()
        return super(Country, self).write(cursor, user, ids, vals,
                context=context)

Country()

osv.osv class defines specific attributes and methods. In addition, the fields of an object can be simple (boolean, integer, datetime, ...), a reference to other business objects (many2one, many2many, One2Many) or associated with a specific function Python. For each field, the attributes string (text), required, readonly, help (tooltip help) or select (field defining a search criterion) can be specified.

Among osv.osv attributes, there are:

  • _name (required) : the object (db table) name
  • _columns (required) : the list of all the object fields
  • _defaults : initialization of fields
  • _inherit : the inherited object. If the attribute _name is identical to that of _inherit, compatibility is ensured between views. Otherwise, a new table is created. There is also an attribute _inherits that allows multiple inheritance.
  • _sql_constraints : SQL constraints with the following format ('constraint name', 'SQL definition', 'message')

Every osv object has the methods : create, search, read, write, copy, unlink, browse (allows to use the dot notation to access object fields), default_get (get default values), perm_read (get the user id who created this object), fields_get (return a field list with their description), name_get, name_search, import_data, export_data.

It is possible to override these methods - like in the 'res.country' example, for name_search, create and write functions.

Besides, in most of the the prototypes of osv.osv object, there is a data base cursor to directly handle SQL requests.

An object representation (screen) is an XML record in a specific table. Different types of views exist but we mainly use form and tree:

  • form : display a single object. Used in edition mode.
  • tree : display an item list.

For example, res.country object has these views:

<record id="view_country_tree" model="ir.ui.view">
            <field name="name">res.country.tree</field>
            <field name="model">res.country</field>
            <field name="type">tree</field>
            <field name="arch" type="xml">
                <tree string="Country">
                    <field name="name"/>
                    <field name="code"/>
                </tree>
            </field>
        </record>

        <record id="view_country_form" model="ir.ui.view">
            <field name="name">res.country.form</field>
            <field name="model">res.country</field>
            <field name="type">form</field>
            <field name="arch" type="xml">
                <form string="Country">
                    <field name="name" select="1"/>
                    <field name="code" select="1"/>
                </form>
            </field>
        </record>

Which display:

Elements composing a view can be: an object field, a button, a separator, a label, a group of elements, notebooks ...

A set of attributes apply to these elements (readonly, required, invisible, string, widget (url, email, progressbar, ...), ...). Readonly, required and invisible attributes may have a value depending on object fields, to create dynamic views. As is, some invoice parameters are only editable when it is in draft state.

To finish with views presentation, it is possible to make inheritance between views. The objective is to replace or add elements using eventually XPath queries.

Configuration

Through a module

Data loading in OpenERP is commonly made by XML files. Each file must fits with the following structure:

<?xml version="1.0" encoding="utf-8"?>
<openerp>
    <data noupdate="1">

        <record id="entry_id" model="table_name">
            <field name="field1_name">value</field>
            <field name="field2_name" ref="referenced_id" />
            <field name="field3_name" model="table_name" search="[('field', 'operator', 'value')]" />
        </record>

    </data>
</openerp>

An XML record must provide the id and model (name of the db table) attributes. As id can be absolute or relative, records defined in XML files can be modified by another module:

  • inside a module named 'module_A', I create a new record whose id is new_entry_A
<record id="new_entry_A" model="table_name">
              ...
  • in another module (module_B), I modify the record registered in data base whose id is new_entry_A
<record id="module_A.entry_A" model="table_name">
              ...

In the case of relational fields, attributes ref and search can link XML records together. 'Ref' is to specify an absolute or relative identifier previously loaded. 'Search' is a criterion search in the form of a list of tuples: [('field_name', 'operator', 'value'), ...]

'Noupdate' attribute defines the system behavior during a module update : will entries in data base be overriden by XML content ? This attribute is to be handle with care to avoid lost of user modifications.

OpenERP also manage CSV files. In this case, the file name must be the one of the SQL table where insertion must be made. In addition, the names of columns in the CSV file must be identical to the columns of the table.

CSV allows to load important quantities of data, like client references, products or financial accounts.

Through GUI

The GUI also allows the modification of all business entries, customization of views and definitions of objects:

Moreover, changes made by users are recoverable through the module 'base_module record' that can generate XML files corresponding to changes made since the start of a recording session.

Conclusion

In this article , we have only addressed a few part of OpenERP aspects. Numerous concepts like workflows, processes, reports and user rights have not been presented.

To be continued.