Building a Module
Start/Stop the Odoo server
Odoo uses a client/server architecture in which clients are web browsers accessing the Odoo server via RPC.
Business logic and extension is generally performed on the server side, although supporting client features (e.g. new data representation such as interactive maps) can be added to the client.
In order to start the server, simply invoke the command odoo-bin in the shell, adding the full path to the file if necessary:
The server is stopped by hitting
Ctrl-C twice from the terminal, or by killing the corresponding OS process.
Build an Odoo module
Both server and client extensions are packaged as modules which are optionally loaded in a database.
Odoo modules can either add brand new business logic to an Odoo system, or alter and extend existing business logic: a module can be created to add your country’s accounting rules to Odoo’s generic accounting support, while the next module adds support for real-time visualisation of a bus fleet.
Everything in Odoo thus starts and ends with modules.
Composition of a module
An Odoo module can contain a number of elements:
- Business objects
Declared as Python classes, these resources are automatically persisted by Odoo based on their configuration
- Object views
Definition of business objects UI display
- Data files
XML or CSV files declaring the model metadata :
- Web controllers
Handle requests from web browsers
- Static web data
Each module is a directory within a module directory. Module directories are specified by using the
An Odoo module is declared by its manifest.
A module is also a Python package with a
__init__.py file, containing import instructions for various Python files in the module.
For instance, if the module has a single
__init__.py might contain:
The command creates a subdirectory for your module, and automatically creates a bunch of standard files for a module. Most of them simply contain commented code or XML. The usage of most of those files will be explained along this tutorial.
A key component of Odoo is the ORM layer. This layer avoids having to write most SQL by hand and provides extensibility and security services2.
Business objects are declared as Python classes extending
Model which integrates them into the automated persistence system.
Models can be configured by setting a number of attributes at their definition. The most important attribute is
_name which is required and defines the name for the model in the Odoo system. Here is a minimally complete definition of a model:
Fields are used to define what the model can store and where. Fields are defined as attributes on the model class:
Much like the model itself, its fields can be configured, by passing configuration attributes as parameters:
Some attributes are available on all fields, here are the most common ones:
unicode, default: field’s name)
The label of the field in UI (visible by users).
True, the field can not be empty, it must either have a default value or always be given a value when creating a record.
Long-form, provides a help tooltip to users in the UI.
Requests that Odoo create a database index on the column.
There are two broad categories of fields: “simple” fields which are atomic values stored directly in the model’s table and “relational” fields linking records (of the same model or of different models).
Odoo creates a few fields in all models1. These fields are managed by the system and shouldn’t be written to. They can be read if useful or necessary:
By default, Odoo also requires a
name field on all models for various display and search behaviors. The field used for these purposes can be overridden by setting
Odoo is a highly data driven system. Although behavior is customized using Python code part of a module’s value is in the data it sets up when loaded.
Module data is declared via data files, XML files with
<record> elements. Each
<record> element creates or updates a database record.
modelis the name of the Odoo model for the record.
idis an external identifier, it allows referring to the record (without having to know its in-database identifier).
<field>elements have a
namewhich is the name of the field in the model (e.g.
description). Their body is the field’s value.
Data files have to be declared in the manifest file to be loaded, they can be declared in the
'data' list (always loaded) or in the
'demo' list (only loaded in demonstration mode).
Views define the way the records of a model are displayed. Each type of view represents a mode of visualization (a list of records, a graph of their aggregation, …). Views can either be requested generically via their type (e.g. a list of partners) or specifically via their id. For generic requests, the view with the correct type and the lowest priority will be used (so the lowest-priority view of each type is the default view for that type).
View inheritance allows altering views declared elsewhere (adding or removing content).
Generic view declaration
A view is declared as a record of the model
ir.ui.view. The view type is implied by the root element of the
Tree views, also called list views, display records in a tabular form.
Their root element is
<tree>. The simplest form of the tree view simply lists all the fields to display in the table (each field as a column):
Forms are used to create and edit single records.
Their root element is
<form>. They are composed of high-level structure elements (groups, notebooks) and interactive elements (buttons and fields):
Form views can also use plain HTML for more flexible layouts:
Search views customize the search field associated with the list view (and other aggregated views). Their root element is
<search> and they’re composed of fields defining which fields can be searched on:
If no search view exists for the model, Odoo generates one which only allows searching on the
Relations between models
A record from a model may be related to a record from another model. For instance, a sale order record is related to a client record that contains the client data; it is also related to its sale order line records.
Relational fields link records, either of the same model (hierarchies) or between different models.
Relational field types are:
Many2one(other_model, ondelete='set null')
A simple link to an other object:
Bidirectional multiple relationship, any record on one side can be related to any number of records on the other side. Behaves as a container of records, accessing it also results in a possibly empty set of records:
Odoo provides two inheritance mechanisms to extend an existing model in a modular way.
The first inheritance mechanism allows a module to modify the behavior of a model defined in another module:
add fields to a model,
override the definition of fields on a model,
add constraints to a model,
add methods to a model,
override existing methods on a model.
The second inheritance mechanism (delegation) allows to link every record of a model to a record in a parent model, and provides transparent access to the fields of the parent record.
Instead of modifying existing views in place (by overwriting them), Odoo provides view inheritance where children “extension” views are applied on top of root views, and can add or remove content from their parent.
An extension view references its parent using the
inherit_id field, and instead of a single view its
arch field is composed of any number of
xpath elements selecting and altering the content of their parent view:
An XPath expression selecting a single element in the parent view. Raises an error if it matches no element or more than one
Operation to apply to the matched element:
xpath’s body at the end of the matched element
replaces the matched element with the
xpath’s body, replacing any
$0node occurrence in the new body with the original element
xpath’s body as a sibling before the matched element
xpaths’s body as a sibling after the matched element
alters the attributes of the matched element using special
attributeelements in the
In Odoo, Search domains are values that encode conditions on records. A domain is a list of criteria used to select a subset of a model’s records. Each criteria is a triple with a field name, an operator and a value.
For instance, when used on the Product model the following domain selects all services with a unit price over 1000:
By default criteria are combined with an implicit AND. The logical operators
| (OR) and
! (NOT) can be used to explicitly combine criteria. They are used in prefix position (the operator is inserted before its arguments rather than between). For instance to select products “which are services OR have a unit price which is NOT between 1000 and 2000”:
domain parameter can be added to relational fields to limit valid records for the relation when trying to select records in the client interface.
Computed fields and default values
So far fields have been stored directly in and retrieved directly from the database. Fields can also be computed. In that case, the field’s value is not retrieved from the database but computed on-the-fly by calling a method of the model.
To create a computed field, create a field and set its attribute
compute to the name of a method. The computation method should simply set the value of the field to compute on every record in
The value of a computed field usually depends on the values of other fields on the computed record. The ORM expects the developer to specify those dependencies on the compute method with the decorator
depends(). The given dependencies are used by the ORM to trigger the recomputation of the field whenever some of its dependencies have been modified:
Any field can be given a default value. In the field definition, add the option
X is either a Python literal value (boolean, integer, float, string), or a function taking a recordset and returning a value:
The “onchange” mechanism provides a way for the client interface to update a form whenever the user has filled in a value in a field, without saving anything to the database.
For instance, suppose a model has three fields
price, and you want to update the price on the form when any of the other fields is modified. To achieve this, define a method where
self represents the record in the form view, and decorate it with
onchange() to specify on which field it has to be triggered. Any change you make on
self will be reflected on the form.
For computed fields, valued
onchange behavior is built-in as can be seen by playing with the Session form: change the number of seats or participants, and the
taken_seats progressbar is automatically updated.
Odoo provides two ways to set up automatically verified invariants:
Python constraints and
A Python constraint is defined as a method decorated with
constrains(), and invoked on a recordset. The decorator specifies which fields are involved in the constraint, so that the constraint is automatically evaluated when one of them is modified. The method is expected to raise an exception if its invariant is not satisfied:
SQL constraints are defined through the model attribute
_sql_constraints. The latter is assigned to a list of triples of strings
(name, sql_definition, message), where
name is a valid SQL constraint name,
sql_definition is a table_constraint expression, and
message is the error message.
Tree views can take supplementary attributes to further customize their behavior:
allow changing the style of a row’s text based on the corresponding record’s attributes.
Values are Python expressions. For each record, the expression is evaluated with the record’s attributes as context values and if
true, the corresponding style is applied to the row. Here are some of the other values available in the context:
uid: the id of the current user,
today: the current local date as a string of the form
now: same as
todaywith the addition of the current time. This value is formatted as
font-style: italic), or any bootstrap contextual color (
"bottom". Makes the tree view editable in-place (rather than having to go through the form view), the value is the position where new rows appear.
Displays records as calendar events. Their root element is
<calendar> and their most common attributes are:
The name of the field used for color segmentation. Colors are automatically distributed to events, but events in the same color segment (records which have the same value for their
@colorfield) will be given the same color.
record’s field holding the start date/time for the event
record’s field holding the end date/time for the event
record’s field to define the label for each calendar event
<field> elements can have a
@filter_domain that overrides the domain generated for searching on the given field. In the given domain,
self represents the value entered by the user. In the example below, it is used to search on both fields
Search views can also contain
<filter> elements, which act as toggles for predefined searches. Filters must have one of the following attributes:
add the given domain to the current search
add some context to the current search; use the key
group_byto group results on the given field name
To use a non-default search view in an action, it should be linked using the
search_view_id field of the action record.
The action can also set default values for search fields through its
context field: context keys of the form
search_default_field_name will initialize field_name with the provided value. Search filters must have an optional
@name to have a default and behave as booleans (they can only be enabled by default).
Horizontal bar charts typically used to show project planning and advancement, their root element is
Graph views allow aggregated overview and analysis of models, their root element is
Graph views have 4 display modes, the default mode is selected using the
- Bar (default)
a bar chart, the first dimension is used to define groups on the horizontal axis, other dimensions define aggregated bars within each group.
By default bars are side-by-side, they can be stacked by using
2-dimensional line chart
Graph views contain
<field> with a mandatory
@type attribute taking the values:
the field should be aggregated by default
the field should be aggregated rather than grouped on
Used to organize tasks, production processes, etc… their root element is
A kanban view shows a set of cards possibly grouped in columns. Each card represents a record, and each column the values of an aggregation field.
For instance, project tasks may be organized by stage (each column is a stage), or by responsible (each column is a user), and so on.
Kanban views define the structure of each card as a mix of form elements (including basic HTML) and QWeb Templates.
Access control mechanisms must be configured to achieve a coherent security policy.
Group-based access control mechanisms
Groups are created as normal records on the model
res.groups, and granted menu access via menu definitions. However even without a menu, objects may still be accessible indirectly, so actual object-level permissions (read, write, create, unlink) must be defined for groups. They are usually inserted via CSV files inside modules. It is also possible to restrict access to specific fields on a view or object using the field’s groups attribute.
Access rights are defined as records of the model
ir.model.access. Each access right is associated to a model, a group (or no group for global access), and a set of permissions: read, write, create, unlink. Such access rights are usually created by a CSV file named after its model:
A record rule restricts the access rights to a subset of records of the given model. A rule is a record of the model
ir.rule, and is associated to a model, a number of groups (many2many field), permissions to which the restriction applies, and a domain. The domain specifies to which records the access rights are limited.
Here is an example of a rule that prevents the deletion of leads that are not in state
cancel. Notice that the value of the field
groups must follow the same convention as the method
write() of the ORM.
Wizards describe interactive sessions with the user (or dialog boxes) through dynamic forms. A wizard is simply a model that extends the class
TransientModel instead of
Model. The class
Model and reuse all its existing mechanisms, with the following particularities:
Wizard records are not meant to be persistent; they are automatically deleted from the database after a certain time. This is why they are called transient.
Wizard records may refer to regular records or wizard records through relational fields(many2one or many2many), but regular records cannot refer to wizard records through a many2one field.
We want to create a wizard that allow users to create attendees for a particular session, or for a list of sessions at once.
Wizards are simply window actions with a
target field set to the value
new, which opens the view (usually a form) in a separate dialog. The action may be triggered via a menu item, but is more generally triggered by a button.
An other way to launch wizards is through the
binding_model_id field of the action. Setting this field will make the action appear on the views of the model the action is “bound” to.
Each module can provide its own translations within the i18n directory, by having files named LANG.po where LANG is the locale code for the language, or the language and country combination when they differ (e.g. pt.po or pt_BR.po). Translations will be loaded automatically by Odoo for all enabled languages. Developers always use English when creating a module, then export the module terms using Odoo’s gettext POT export feature (without specifying a language), to create the module template POT file, and then derive the translated PO files. Many IDE’s have plugins or modes for editing and merging PO/POT files.
A report is a combination two elements:
ir.actions.reportwhich configures various basic parameters for the report (default type, whether the report should be saved to the database after generation,…)
A standard QWeb view for the actual report:
the standard rendering context provides a number of elements, the most important being:
the records for which the report is printed
the user printing the report
Because reports are standard web pages, they are available through a URL and output parameters can be manipulated through this URL, for instance the HTML version of the Invoice report is available through http://localhost:8069/report/html/account.report_invoice/1 (if
account is installed) and the PDF version through http://localhost:8069/report/pdf/account.report_invoice/1.
The web-service module offer a common interface for all web-services :
Business objects can also be accessed via the distributed object mechanism. They can all be modified via the client interface with contextual views.
Odoo is accessible through XML-RPC/JSON-RPC interfaces, for which libraries exist in many languages.
The following example is a Python 3 program that interacts with an Odoo server with the library
The following example is a Python 3 program that interacts with an Odoo server with the standard Python libraries
json. This example assumes the Productivity app (
note) is installed:
Examples can be easily adapted from XML-RPC to JSON-RPC.