Edit on GitHub

Odoo Guidelines

This page introduce the new Odoo Coding Guidelines. This guidelines aims to improve the quality of the code (better readability of source, ...) but also to improve Odoo Apps ! Indeed, a proper code will ease the maintenance and debugging, lower the complexity and promote the reliability.

Module structure

Directories

A module is organised in a few directory :

  • data/ : demo and data xml
  • models/ : models definition
  • controllers/ : contains controllers (http routes).
  • views/ : contains the views and templates
  • static/ : contains the web assets, separated into css/, js/, img/, lib/, ...

File naming

For views declarations, split backend views from (frontend) templates in 2 differents files.

For models, split the business logic by sets of models, in each sets select a main model, this model gives its name to the set. If there is only one set of module, its name is the same as the module name. For each set named <main_model> the following files may be created:

  • models/<main_model>.py
  • models/<inherited_main_model.py
  • views/<main_model>_templates.xml
  • views/<main_model>_views.xml

For instance, sale module introduce sale_order and sale_order_line where sale_order is dominant. So the <main_model> files will be named models/sale_order.py and views/sale_order_views.py.

For data, split them by purpose : demo or data. The filename will be the main_model name, suffixed by _demo.xml or _data.xml.

For controller, the only file should be named main.py.

For static files, the name pattern is <module_name>.ext (i.e. : static/js/im_chat.js, static/css/im_chat.css, static/xml/im_chat.xml, ...). Don’t link data (image, libraries) outside Odoo : don’t use an url to an image but copy it in our codebase instead.

The complete tree should looks like

addons/<my_module_name>/
|-- __init__.py
|-- __openerp__.py
|-- controllers/
|   |-- __init__.py
|   `-- main.py
|-- data/
|   |-- <main_model>_data.xml
|   `-- <inherited_main_model>_demo.xml
|-- models/
|   |-- __init__.py
|   |-- <main_model>.py
|   `-- <inherited_main_model>.py
|-- security/
|   |-- ir.model.access.csv
|   `-- <main_model>_security.xml
|-- static/
|   |-- img/
|   |   |-- my_little_kitten.png
|   |   `-- troll.jpg
|   |-- lib/
|   |   `-- external_lib/
|   `-- src/
|       |-- js/
|       |   `-- <my_module_name>.js
|       |-- css/
|       |   `-- <my_module_name>.css
|       |-- less/
|       |   `-- <my_module_name>.less
|       `-- xml/
|           `-- <my_module_name>.xml
`-- views/
    |-- <main_model>_templates.xml
    |-- <main_model>_views.xml
    |-- <inherited_main_model>_templates.xml
    `-- <inherited_main_model>_views.xml

Note

Filename should only use only [a-z0-9_]

Warning

Use correct file permissions : folder 755 and file 644.

XML files

Format

When declaring a record in XML,

  • Place id attribute before model
  • For field declaration, name attribute is first. Then place the value either in the field tag, either in the eval attribute, and finally other attributes (widget, options, ...) ordered by importance.
  • Try to group the record by model. In case of dependencies between action/menu/views, the convention may not be applicable.
  • Use naming convention defined at the next point
  • The tag <data> is only used to set not-updatable data with noupdate=1
<record id="view_id" model="ir.ui.view">
    <field name="name">view.name</field>
    <field name="model">object_name</field>
    <field name="priority" eval="16"/>
    <field name="arch" type="xml">
        <tree>
            <field name="my_field_1"/>
            <field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" statusbar_colors='{"invoice_except":"red","waiting_date":"blue"}' />
        </tree>
    </field>
</record>

Naming xml_id

Security, View and Action

Use the following pattern :

  • For a menu : <model_name>_menu
  • For a view : <model_name>_view_<view_type>, where view_type is kanban, form, tree, search, ...
  • For an action : the main action respects <model_name>_action. Others are suffixed with _<detail>, where detail is a underscore lowercase string explaining a little bit the action (Should not be long). This is used only if multiple action are declared for the model.
  • For a group : <model_name>_group_<group_name> where group_name is the name of the group, genrally ‘user’, ‘manager’, ...
  • For a rule : <model_name>_rule_<concerned_group> where concerned_group is the short name of the concerned group (‘user’ for the ‘model_name_group_user’, ‘public’ for public user, ‘company’ for multi-company rules, ...).
<!-- views and menus -->
<record id="model_name_menu" model="ir.ui.menu">
    ...
</record>

<record id="model_name_view_form" model="ir.ui.view">
    ...
</record>

<record id="model_name_view_kanban" model="ir.ui.view">
    ...
</record>

<!-- actions -->
<record id="model_name_action" model="ir.actions.act_window">
    ...
</record>

<record id="model_name_action_child_list" model="ir.actions.act_window">
    ...
</record>

<!-- security -->
<record id="model_name_group_user" model="res.groups">
    ...
</record>

<record id="model_name_rule_public" model="ir.rule">
    ...
</record>

<record id="model_name_rule_company" model="ir.rule">
    ...
</record>

Note

View name use dot notation my.model.view_type or my.model.view_type.inherit instead of “This is the form view of My Model”.

Inherited XML

The naming pattern of inherited view is <base_view>_inherit_<current_module_name>. A module can extend a view only one time, suffix the orginal name with _inherit_<current_module_name>, where current_module_name is the technical name of the module extending the view.

<record id="inherited_model_view_form_inherit_my_module" model="ir.ui.view">
    ...
</record>

Python

PEP8 options

Using a linter can help to see syntax and semantic warning or error. Odoo Source Code try to respect Python standard, but some of them can be ignored.

  • E501: line too long
  • E301: expected 1 blank line, found 0
  • E302: expected 2 blank lines, found 1
  • E126: continuation line over-indented for hanging indent
  • E123: closing bracket does not match indentation of opening bracket’s line
  • E127: continuation line over-indented for visual indent
  • E128: continuation line under-indented for visual indent
  • E265: block comment should start with ‘# ‘

Imports

The imports are ordered as

  1. Externals libs (One per line sorted and splitted in python stdlib)
  2. Imports of openerp
  3. Imports from Odoo modules (rarely, and only if necessary)

Inside these 3 groups, the imported lines are alphabetically sorted.

# 1 : imports of python lib
import base64
import re
import time
# 2 :  imports of openerp
import openerp
from openerp import api, fields, models # alphabetically ordered
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
# 3 :  imports from odoo modules
from openerp.addons.website.models.website import slug
from openerp.addons.web.controllers.main import login_redirect

Idioms

  • Prefer % over .format(), prefer %(varname) instead of position (This is better for translation)
  • Try to avoid generators and decorators
  • Always favor Readability over conciseness or using the language features or idioms.
  • Use list comprehension, dict comprehension, and basic manipulation using map, filter, sum, ... They make the code easier to read.
  • The same applies for recordset methods : use filtered, mapped, sorted, ...
  • Each python file should have # -*- coding: utf-8 -*- as first line
  • Use the UserError defined in openerp.exceptions instead of overriding Warning, or find a more appropriate exception in exceptions.py
  • Document your code (docstring on methods, simple comments for the tricky part of the code)
  • Use meaningful variable/class/method names

Symbols

  • Odoo Python Class : use camelcase for code in api v8, underscore lowercase notation for old api.
class AccountInvoice(models.Model):
    ...

class account_invoice(osv.osv):
    ...
  • Variable name :
    • use camelcase for model variable
    • use underscore lowercase notation for common variable.
    • since new API works with record or recordset instead of id list, don’t suffix variable name with _id or _ids if they not contain id or list of id.
ResPartner = self.env['res.partner']
partners = ResPartner.browse(ids)
partner_id = partners[0].id
  • One2Many and Many2Many fields should always have _ids as suffix (example: sale_order_line_ids)

  • Many2One fields should have _id as suffix (example : partner_id, user_id, ...)

  • Method conventions
    • Compute Field : the compute method pattern is _compute_<field_name>
    • Search method : the search method pattern is _search_<field_name>
    • Default method : the default method pattern is _default_<field_name>
    • Onchange method : the onchange method pattern is _onchange_<field_name>
    • Constraint method : the constraint method pattern is _check_<constraint_name>
    • Action method : an object action method is prefix with action_. Its decorator is @api.multi, but since it use only one record, add self.ensure_one() at the beginning of the method.
  • In a Model attribute order should be
    1. Private attributes (_name, _description, _inherit, ...)
    2. Default method and _default_get
    3. Fields declarations
    4. Compute and search methods in the same order than field declaration
    5. Constrains methods (@api.constrains) and onchange methods (@api.onchange)
    6. CRUD methods (ORM overrides)
    7. Action methods
    8. And finally, other business methods.
class Event(models.Model):
    # Private attributes
    _name = 'event.event'
    _description = 'Event'

    # Default methods
    def _default_name(self):
        ...

    # Fields declaration
    name = fields.Char(string='Name', default=_default_name)
    seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
        store=True, readonly=True, compute='_compute_seats')
    seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
        store=True, readonly=True, compute='_compute_seats')
    price = fields.Integer(string='Price')

    # compute and search fields, in the same order that fields declaration
    @api.multi
    @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
    def _compute_seats(self):
        ...

    # Constraints and onchanges
    @api.constrains('seats_max', 'seats_available')
    def _check_seats_limit(self):
        ...

    @api.onchange('date_begin')
    def _onchange_date_begin(self):
        ...

    # CRUD methods
    def create(self):
        ...

    # Action methods
    @api.multi
    def action_validate(self):
        self.ensure_one()
        ...

    # Business methods
    def mail_user_confirm(self):
        ...

Javascript and CSS

For javascript :

  • use strict; is recommended for all javascript files
  • Use a linter (jshint, ...)
  • Never add minified Javascript Libraries
  • Use camelcase for class declaration

For CSS :

  • Prefix all your class with o_<module_name> where module_name is the technical name of the module (‘sale’, ‘im_chat’, ...) or the main route reserved by the module (for website module mainly, i.e. : ‘o_forum’ for website_forum module). The only exception for this rule is the webclient : it simply use o_ prefix.
  • Avoid using id
  • Use bootstrap native class
  • Use underscore lowercase notation to name class

Git

Commit message

Prefix your commit with

  • [IMP] for improvements
  • [FIX] for bug fixes
  • [REF] for refactoring
  • [ADD] for adding new resources
  • [REM] for removing of resources
  • [MERGE] for merge commits (only for forward/back-port)
  • [CLA] for signing the Odoo Individual Contributor License

Then, in the message itself, specify the part of the code impacted by your changes (module name, lib, transversal object, ...) and a description of the changes.

  • Always put meaning full commit message: commit message should be self explanatory (long enough) including the name of the module that has been changed and the reason behind that change. Do not use single words like “bugfix” or “improvements”.
  • Avoid commits which simultaneously impacts lots of modules. Try to splits into different commits where impacted modules are different (It will be helpful when we are going to revert that module separately).
[FIX] website, website_mail: remove unused alert div, fixes look of input-group-btn
Bootstrap's CSS depends on the input-group-btn
element being the first/last child of its parent.
This was not the case because of the invisible
and useless alert.

[IMP] fields: reduce memory footprint of list/set field attributes

[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code.
Instead of using global ...

Note

The long description try to explain the why not the what, the what can be seen in the diff