100% found this document useful (1 vote)
901 views

Odoo Development

Here are the key points about module docs and manifest files: - README.rst - Provides an overview of the module, how to install/use it, credits, changelog, etc. It's the main documentation file. - __manifest__.py - Defines basic module metadata like name, version, author, dependencies, etc. It's the main manifest file. - Other common files include __init__.py, __openerp__.py (deprecated), doc/index.rst for additional documentation, doc/changelog.rst for changelog. - Files should follow standard Odoo/OCA conventions and templates to ensure quality and consistency across modules. - README and __manifest__ are essential files that must be prepared fully

Uploaded by

kikwete
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
901 views

Odoo Development

Here are the key points about module docs and manifest files: - README.rst - Provides an overview of the module, how to install/use it, credits, changelog, etc. It's the main documentation file. - __manifest__.py - Defines basic module metadata like name, version, author, dependencies, etc. It's the main manifest file. - Other common files include __init__.py, __openerp__.py (deprecated), doc/index.rst for additional documentation, doc/changelog.rst for changelog. - Files should follow standard Odoo/OCA conventions and templates to ensure quality and consistency across modules. - README and __manifest__ are essential files that must be prepared fully

Uploaded by

kikwete
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 127

Odoo development Documentation

Release master

IT-Projects LLC

January 13, 2017

Contents

First steps

Module Development
2.1 Docs and manifests .
2.2 Guidelines . . . . .
2.3 Odoo Python . . . .
2.4 XML . . . . . . . .
2.5 HTML . . . . . . .
2.6 CSS . . . . . . . . .
2.7 YAML . . . . . . .
2.8 Javascript . . . . . .
2.9 Frontend . . . . . .
2.10 Point of Sale (POS)
2.11 Access . . . . . . .
2.12 Hooks . . . . . . . .
2.13 Tests . . . . . . . .
2.14 Debugging . . . . .
2.15 Source Diving . . .
2.16 Lint . . . . . . . . .
2.17 Other . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

5
5
19
19
29
31
32
32
33
34
35
37
44
44
48
55
57
59

Module Migration
3.1 Switching module to new api . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Fixing references on migration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

63
63
64

User documentation
4.1 static/description/index.html .
4.2 Screenshots tools . . . . . . .
4.3 Module description . . . . . .
4.4 Contact us block . . . . . . .
4.5 JS Tour . . . . . . . . . . . .
4.6 Preview module on App Store
4.7 Image sizes . . . . . . . . . .

.
.
.
.
.
.
.

65
65
67
67
68
68
70
72

Git and Github


5.1 Initial git & github configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 Porting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.3 Conflict resolving . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

75
75
76
77

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.

5.4
5.5
5.6
5.7
5.8
5.9
5.10
5.11
5.12

.
.
.
.
.
.
.
.
.

78
79
79
79
80
82
83
83
83

Continuous Integration
6.1 Runbot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Odoo Travis Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3 Coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

85
85
87
87

Odoo
7.1 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.2 How to use Odoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

89
89
98

Odoo administration
8.1 Odoo installation .
8.2 Longpolling . . .
8.3 About longpolling
8.4 --workers . . .
8.5 --addons-path
8.6 --log-handler

Multi Pull Request . . . .


Cancel lame commit . . .
Pull request from console
Check remote bundings .
Files relocation . . . . . .
Commit comment prefix .
Git stash . . . . . . . . .
Update Git . . . . . . . .
Squash commits into one .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

Continuous Delivery

10 IDE
10.1
10.2
10.3
10.4

Emacs . . . . . .
PyCharm . . . . .
Tmux . . . . . . .
Visual Studio Code

103
103
105
106
106
107
107
109

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

111
111
113
115
117

11 Other
121
11.1 RST format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.2 Adjust chromium window size script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

ii

Odoo development Documentation, Release master

Ask new questions: https://github.com/it-projects-llc/odoo-development/issues/new


Check open questions: https://github.com/it-projects-llc/odoo-development/issues
Push your answers and improvements: https://github.com/it-projects-llc/odoo-development
Current content:

Contents

Odoo development Documentation, Release master

Contents

CHAPTER 1

First steps

Install odoo
take the course Bulding a module
read the article Source diving
Configure git
read Company rules (For IT-Projects LLC employees only)
Get tasks from your Guru!
Fork repo, clone repo to you machine, make commits, push updates, create Pull Request

Odoo development Documentation, Release master

Chapter 1. First steps

CHAPTER 2

Module Development

2.1 Docs and manifests


2.1.1 Files
All files from this section ought to be fully *0 prepared before any other files in new module. It helps you to review
requirements again before you start.
README.rst

Guidlines
OCAs README
Demo
addons-dev
HTML Description
Usage instructions
Changelog
Tested on

Guidlines
=============
Module Name
=============
Put some short introduction first.

Then add more detailed description, technical specifications, any other information that could be int
Credits
=======
Contributors
-----------* DEVELOPER_NAME <[email protected]>
0

The only exception could be made for data field in __openerp__.py file.

Odoo development Documentation, Release master

Sponsors
-------* `IT-Projects LLC <https://it-projects.info>`_
Maintainers
----------* `IT-Projects LLC <https://it-projects.info>`_
Further information
===================
Demo: http://runbot.it-projects.info/demo/REPO-NAME/BRANCH
HTML Description: https://apps.odoo.com/apps/modules/VERSION/TECHNICAL_NAME/
Usage instructions: `<doc/index.rst>`_
Changelog: `<doc/changelog.rst>`_
Tested on Odoo 10.0 ODOO_COMMIT_SHA_TO_BE_UPDATED

OCAs README
https://raw.githubusercontent.com/OCA/maintainer-tools/master/template/module/README.rst
Demo

Link to the runbot. Supported repo names are below. Change branche name if needed.
Demo:
Demo:
Demo:
Demo:
Demo:
Demo:
Demo:
Demo:
Demo:
Demo:

http://runbot.it-projects.info/demo/access-addons/10.0
http://runbot.it-projects.info/demo/addons-dev/misc-addons-10.0-some_feature
http://runbot.it-projects.info/demo/l10n-addons/10.0
http://runbot.it-projects.info/demo/mail-addons/10.0
http://runbot.it-projects.info/demo/misc-addons/10.0
http://runbot.it-projects.info/demo/odoo-saas-tools/10.0
http://runbot.it-projects.info/demo/odoo-telegram/10.0
http://runbot.it-projects.info/demo/pos-addons/10.0
http://runbot.it-projects.info/demo/rental-addons/10.0
http://runbot.it-projects.info/demo/website-addons/10.0

addons-dev In most cases, if you work in addons-dev, you shall not use demo link to addons-dev (e.g.
http://runbot.it-projects.info/demo/addons-dev/misc-addons-10.0-some_feature).
Use a link for target repo instead (e.g. http://runbot.it-projects.info/demo/misc-addons/10.0).
You can use links to addons-dev only if you know who will use it.
HTML Description

Link to app store, e.g.


HTML Description: https://apps.odoo.com/apps/modules/10.0/web_debranding/

You have to prepare this link even if the module is not published yet, i.e. link returns 404 error.

Chapter 2. Module Development

Odoo development Documentation, Release master

Usage instructions

doc/index.rst
Changelog

doc/changelog.rst
Tested on
Tested on Odoo 10.0 03bc8c5f9ac53a3349c1caac222f7619a632ccd8

commit sha can be found as following


cd /path/to/odoo
git rev-parse HEAD

doc/index.rst
=============
Module name
=============
Installation
============

* `Install <https://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html>`__ this


* OPTIONAL `Activate longpolling <https://odoo-development.readthedocs.io/en/latest/admin/longpolling
* Additional notes if any
Configuration
=============
Instruction how to configure the module.

* `Enable technical features <https://odoo-development.readthedocs.io/en/latest/odoo/usage/technical* Open menu ...


* Click ...
Usage
=====

Instruction for daily usage. It should describe how to check that module works. What shall user do an
* Open menu ...
* Click ...
Uninstallation
==============
Optional section for uninstallation notes. Delete it if you don't have notes for uninstallation.

This description will be available at app store


https://www.odoo.com/apps/modules/8.0/pos_multi_session/

2.1. Docs and manifests

under

Documentation

tab.

Example:

Odoo development Documentation, Release master

__openerp__.py / __manifest__.py

Filename
Template
name
summary
category
Hidden
version
version in OCA
author
author in OCA
website
license
external_dependencies

Filename

Use __openerp__.py for odoo 9.0 and earler versions.


Use __manifest__.py for odoo 10.0 and later versions, but dont rename filename on porting module from
one version to another
Template

Use example below as template. What are important here:


order of attributes
not used attributes are represented
quote characters (", """)
empty lines
no description attribute
price and currency attributes are commented-out if not used
comma after last item in list (e.g. in depends attribute)
add new line symbol at the end of file (i.e. right after last })
# -*- coding: utf-8 -*{
"name": """MODULE_NAME""",
"summary": """SHORT_DESCRIPTION_OF_THE_MODULE""",
"category": "SOME_CATEGORY",
"images": [],
"version": "1.0.0",
"application": False,
"author": "IT-Projects LLC, DEVELOPER_NAME",
"website": "https://it-projects.info",
"license": "LGPL-3",
# "price": 9.00,

Chapter 2. Module Development

Odoo development Documentation, Release master

# "currency": "EUR",
"depends": [
"DEPENDENCY1",
"DEPENDENCY2",
],
"external_dependencies": {"python": [], "bin": []},
"data": [
"FILE1.xml",
"FILE2.xml",
],
"qweb": [
"static/src/xml/QWEBFILE1.xml",
],
"demo": [
"demo/DEMOFILE1.xml",
],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": None,
"auto_install": False,
"installable": True,
}

See also:
OCAs template: https://github.com/OCA/maintainer-tools/blob/master/template/module/__openerp__.py
name

It must be non-technical name of the module


summary

Short description of the module. E.g. you can describe here which problem is solved by the module. It could sound as
a slogan.
category

Categories from the list below are preferred.


Accounting
2.1. Docs and manifests

Odoo development Documentation, Release master

Discuss
Document Management
eCommerce
Human Resources
Industries
Localization
Manufacturing
Marketing
Point of Sale
Productivity
Project
Purchases
Sales
Warehouse
Website
Extra Tools
Hidden For technical modules Hidden category can be used:
"category": "Hidden",

Such modules are excluded from search results on app store.


version

Note: whenever you change version, you have to add a record in changelog.rst
The x.y.z version numbers follow the semantics breaking.feature.fix:
x increments when the data model or the views had significant changes. Data migration might be needed, or
depending modules might be affected.
y increments when non-breaking new features are added. A module upgrade will probably be needed.
z increments when bugfixes were made. Usually a server restart is needed for the fixes to be made available.
On each version change a record in doc/changelog.rst should be added.
If a module ported to different odoo versions (e.g. 8 and 9) and some update is added only to one version (e.g. 9), then
version is changed as in example below:
init
[8.0] 1.0.0
[9.0] 1.0.0
feature added to 8.0 and ported to 9.0
[8.0] 1.1.0

10

Chapter 2. Module Development

Odoo development Documentation, Release master

[9.0] 1.1.0
feature added to 9.0 only and not going to be ported to 8.0:
[8.0] 1.1.0
[9.0] 1.2.0
fix made in 9.0 only and not going to be ported to 8.0:
[8.0] 1.1.0
[9.0] 1.2.1
fix made in 8.0 and ported to 9.0
[8.0] 1.2.2
[9.0] 1.2.2
i.e. two module branches cannot have same versions with a different meaning
version in OCA While OCA use odoo version in module version (e.g. 8.0.1.0.0), we specify odoo version in
README.rst file and use three numbers in version (e.g. 1.0.0).
author

Use company first and then developer(s):


"author": "IT-Projects LLC, Developer Name",

In the main, if module already exists and you make small updatesfixes, you should not add your name to authors.
author in OCA For OCAs repositories put company name first, then OCA. Developers are listed in README file:
"author": "IT-Projects LLC, Odoo Community Association (OCA)",

website

Url to developers twitter (e.g. "https://twitter.com/yelizariev") or companys website (e.g.


"https://it-projects.info")
license

IT-Projects LLC uses following licences:


"GPL-3" for odoo 8.0 and below
"LGPL-3" for odoo 9.0 and above
For OCAs repositories use "AGPL-3".

2.1. Docs and manifests

11

Odoo development Documentation, Release master

external_dependencies

Check if some python library exists:


"external_dependencies": {"python" : ["openid"]}

Check if some sytem application exists:


"external_dependencies": {"bin" : ["libreoffice"]}

doc/changelog.rst
Template

Use this for new modules


`1.0.0`
------- Init version

Guidlines
`2.0.0`
------- **ADD:** absolutely new way of ..
`1.2.0`
------- **ADD:** new interface for ..
`1.0.1`
------- **FIX:** issue about ...
- **FIX:** another issue about ...
`1.0.0`
------- Init version

icon.png
File icon.png must be located at /static/description/icon.png
IT-Projects LLC

Icons for IT-Projects LLC modules:


TODO
12

Chapter 2. Module Development

Odoo development Documentation, Release master

SaaS
Telegram
Access
Barcode
Mail
Pos
Stock
Website
Website_Sale
Misc

SaaS
Download

Telegram
Download

Access
Download
2.1. Docs and manifests

13

Odoo development Documentation, Release master

Barcode
Download

Mail
Download

Pos
Download

Stock
Download

14

Chapter 2. Module Development

Odoo development Documentation, Release master

Website
Download

Website_Sale
Download

Misc
Download

2.1.2 Notes
RST Requirements
Dont forget to keep correct rst format.

Extra lines
References to menu
Fields
Buttons
Selections
Titles and sections

Extra lines

Dont forget about additional lines for correct formatting

2.1. Docs and manifests

15

Odoo development Documentation, Release master

Raw RST
This and next sentences are joined together.
To split sentences to paragraphs you must add add empty line.
Splited sentence 1.
Splited sentence 2.
Lists below doesn't rendered correctly, because extra line is required:
* 1
* 2
* 3
The same for sublist:
* 1
* 1.1
* 1.2
* 1.3
* 2
Correctly formated lists:
* 1
* 2
* 3
* 3.1
* 3.2
* 3.3
* 4

Rendered RST This and next sentences are joined together. To split sentences to paragraphs you must add add
empty line.
Splited sentence 1.
Splited sentence 2.
Lists below doesnt rendered correctly, because extra line is required: * 1 * 2 * 3
The same for sublist:
1 * 1.1 * 1.2 * 1.3
2
Correctly formated lists:
1
2
3
3.1
3.2
3.3

16

Chapter 2. Module Development

Odoo development Documentation, Release master

4
References to menu

Use double back-quotes with spaced slash for menus:


OK:
* Open menu ``Settings >> Parameters >> System Parameters``
BAD
* Open
* Open
* Open
* Open

menu
menu
menu
menu

``Settings>>Parameters>>System Parameters``
"Settings >> Parameters >> System Parameters"
''Settings >> Parameters >> System Parameters''
``Settings > Parameters > System Parameters``

Fields

Use bold format for fields:


* Set **Name** and **Date** values

Buttons

Use square brackets in double back-quotes to name buttons. Keep letter cases the same as in UI.
OK:
* click ``[Save]``
Bad:
* click ``[save]``

Selections

Use arrow symbol -> to specify value in selection and many2one fields:
* Choose ``Partner -> Administrator``

Titles and sections


OK:
===========================
Correctly formatted Title
===========================
Correctly formatted section
===========================
BAD:
===========================================
No spaces at the beggining and end of title
===========================================

2.1. Docs and manifests

17

Odoo development Documentation, Release master

=============================
No space at the end of title
=============================
=======================================
Incorrect number of signs in title
========================================
================
Incorrect number of signs in title
================
Incorrect number of signs in section
=====================================
Incorrect number of signs in section
===================================

Difference of doc files


README.rst

Contains information interested for developers


index.rst

Usage instruction. Used by end users after purchasing the module. It shall give an answer to the question How to
check that module works (how to install, how to configure, how to use)?. Also, it may cover the question How to
safely uninstall the module.
index.html

Module representation. It shall give an answer to the questions Do I need this module? Should I buy it?.
Content intersection

While every file has its own purpose, the content may intersect. If you dont want duplicate content, use the following
priority:
index.html
index.rst
README.rst

18

Chapter 2. Module Development

Odoo development Documentation, Release master

2.2 Guidelines
Source:
https://www.odoo.com/documentation/8.0/reference/guidelines.html

2.2.1 Comments
First of all, comments in the source are required if its not obvious why are doing something.
Additionally, you can add comments about what are you doing, if it could be helpful.

2.3 Odoo Python


2.3.1 Python decoratos
Original article
http://odoo-new-api-guide-line.readthedocs.org/en/latest/decorator.html

2.2. Guidelines

19

Odoo development Documentation, Release master

@api.one
api.one is meant to be used when method is called only on one record. It makes sure, that there are no multiple records
when calling method with api.one decorator. Let say you got record partner = res.partner(1,). It is only one record and
there is method for example (in res.partner):
@api.one
def get_name(self):
return self.name #self here means one record

calling it like this works:


partner.get_name()

But if there would be more records, like:


partners = res.partner(1, 2,)

calling it, would raise Warning, telling you that you can only call it on one record.
@api.multi
something. For example:
@api.multi
def get_partner_names(self):
names = []
for rec in self:
names.append(rec.name)
return ', '.join(names)

And api.model is considered to be used when you need to do something with model itself and dont need to modify/check some exact models record/records. For example there could be method that returns some meta info about
models structure or some helper methods, etc. Also in documentation it is said that this api is good to use when migrating from old api, because it politely converts code to new api. Also in my own experience, if you need method
to return something, model decorator is good for it. api.one returns empty list, so it might lead to unexpected behavior
when using api.one on method when it is supposed to return something.

2.3.2 Pure Python


Compare two arrays
a = set(pos_config_obj.floor_ids.ids) b = set(rec.floor_ids.ids) diff = a.difference(b)

2.3.3 res.config.settings
Based on https://github.com/odoo/odoo/blob/9.0/openerp/addons/base/res/res_config.py
res.config.settings is a base configuration wizard for application settings. It provides support for setting
default values, assigning groups to employee users, and installing modules. To make such a settings wizard, define a
model like:
class my_config_wizard(osv.osv_memory):
_name = 'my.settings'
_inherit = 'res.config.settings'

20

Chapter 2. Module Development

Odoo development Documentation, Release master

_columns = {
'default_foo': fields.type(..., default_model='my.model'),
'group_bar': fields.boolean(..., group='base.group_user', implied_group='my.group'),
'module_baz': fields.boolean(...),
'other_field': fields.type(...),
}

The method execute (Apply button) provides some support based on a naming convention:
For a field like default_XXX, execute sets the (global) default value of the field XXX in the model named
by default_model to the fields value.
For a boolean field like group_XXX, execute adds/removes implied_group to/from the implied groups of
group, depending on the fields value. By default group is the group Employee. Groups are given by their
xml id. The attribute group may contain several xml ids, separated by commas.
For a boolean field like module_XXX, execute triggers the immediate installation of the module named XXX
if the field has value True.
For the other fields, the method execute invokes all methods with a name that starts with set_; such methods
can be defined to implement the effect of those fields.
The method default_get retrieves values that reflect the current status of the fields like default_XXX,
group_XXX and module_XXX. It also invokes all methods with a name that starts with get_default_; such
methods can be defined to provide current values for other fields.
Example
This for website.config.settings but it is similar to res.config.settings:

class website_config_settings(models.TransientModel):
_inherit = 'website.config.settings'
nobill_noship = fields_new_api.Boolean("Pickup and pay at store")
#When you press Apply
def set_nobill_noship(self, cr, uid, ids, context=None):
config_parameters = self.pool.get("ir.config_parameter")
for record in self.browse(cr, uid, ids, context=context):
config_parameters.set_param(cr, uid, "website_sale_checkout_store.nobill_noship", record.
#When page loads
def get_default_nobill_noship(self, cr, uid, fields, context=None):
nobill_noship = self.pool.get("ir.config_parameter").get_param(cr, uid, "website_sale_checkou
return {'nobill_noship': nobill_noship}
#website_sale_checkout_store - is your module

2.3.4 Update settings on module install


To update settings from any res.config.settings do as follows:
default_XXX
TODO
group_XXX
Add implied group(s) to a group via implied_ids field:

2.3. Odoo Python

21

Odoo development Documentation, Release master

<record model="res.groups" id="base.group_user">


<field name="implied_ids" eval="[
(4, ref('my.group'))
]"/>
</record>

module_XXX
Add XXX to the depends parameter in the __openerp__.py file

2.3.5 Web controllers


Send values to web page
If you need to transmit on rendering page some vars, you need to put that vars in dictionary and place it as second
argument:
@http.route(['/shop/checkout'], type='http', auth="public", website=True)
def checkout(self, **post):
...
values['order'] = order
return request.website.render("website_sale.checkout", values)

2.3.6 x2many values filling


To fill or manipulate one2many or many2many field with according values (records) you need to use special command
as says below.
This format is a list of triplets executed sequentially, where each triplet is a command to execute on the set of records.
Not all commands apply in all situations. Possible commands are:
(0, _, values) adds a new record created from the provided value dict.
(1, id, values) updates an existing record of id id with the values in values. Can not be used in ~.create.
(2, id, _) removes the record of id id from the set, then deletes it (from the database). Can not be used in ~.create.
(3, id, _) removes the record of id id from the set, but does not delete it. Can not be used on ~openerp.fields.One2many. Can not be used in ~.create.
(4, id, _) adds an existing record of id id to the set. Can not be used on ~openerp.fields.One2many.
(5, _, _) removes all records from the set, equivalent to using the command 3 on every record explicitly. Can not
be used on ~openerp.fields.One2many. Can not be used in ~.create.
(6, _, ids) replaces all existing records in the set by the ids list, equivalent to using the command 5 followed by
a command 4 for each id in ids. Can not be used on ~openerp.fields.One2many.
Note: Values marked as _ in the list above are ignored and can be anything, generally 0 or False.
Taken from https://github.com/odoo/odoo/blob/9.0/openerp/models.py

22

Chapter 2. Module Development

Odoo development Documentation, Release master

2.3.7 Fields
Based on: http://odoo-new-api-guide-line.readthedocs.io/en/latest/fields.html
Now fields are class property:
from openerp import models, fields

class AModel(models.Model):
_name = 'a_name'
name = fields.Char(
string="Name",
compute="_compute_name_custom",
store=True,
select=True,
readonly=True,
inverse="_write_name"
required=True,
translate=True,
help='blabla',
company_dependent=True,
search='_search_function'
)

#
#
#
#
#
#
#
#
#
#
#

Optional label of the field


Transform the fields in computed fields
If computed it will store the result
Force index on field
Field will be readonly in views
On update trigger
Mandatory field
Translation enable
Help tooltip text
Transform columns to ir.property
Custom search function mainly used with compute

# The string key is not mandatory


# by default it wil use the property name Capitalized
name = fields.Char()

Valid definition

Field inheritance
One of the new features of the API is to be able to change only one attribute of the field:
name = fields.Char(string='New Value')

Field types
Boolean

Boolean type field:


abool = fields.Boolean()

Char

Store string with variable len.:


achar = fields.Char()

Specific options:
size: data will be trimmed to specified size

2.3. Odoo Python

23

Odoo development Documentation, Release master

translate: field can be translated


Text

Used to store long text.:


atext = fields.Text()

Specific options:
translate: field can be translated
HTML

Used to store HTML, provides an HTML widget.:


anhtml = fields.Html()

Specific options:
translate: field can be translated
Integer

Store integer value. No NULL value support. If value is not set it returns 0:
anint = fields.Integer()

Float

Store float value. No NULL value support. If value is not set it returns 0.0 If digits option is set it will use numeric
type:
afloat = fields.Float()
afloat = fields.Float(digits=(32, 32))
afloat = fields.Float(digits=lambda cr: (32, 32))

Specific options:
digits: force use of numeric type on database. Parameter can be a tuple (int len, float len) or a callable that return
a tuple and take a cursor as parameter
Date

Store date. The field provides some helpers:


context_today returns current day date string based on tz
today returns current system date string
from_string returns datetime.date() from string
to_string returns date string from datetime.date
:

24

Chapter 2. Module Development

Odoo development Documentation, Release master

>>> from openerp import fields


>>> adate = fields.Date()
>>> fields.Date.today()
'2014-06-15'
>>> fields.Date.context_today(self)
'2014-06-15'
>>> fields.Date.context_today(self, timestamp=datetime.datetime.now())
'2014-06-15'
>>> fields.Date.from_string(fields.Date.today())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Date.to_string(datetime.datetime.today())
'2014-06-15'

DateTime

Store datetime. The field provide some helper:


context_timestamp returns current day date string based on tz
now returns current system date string
from_string returns datetime.date() from string
to_string returns date string from datetime.date
:

>>> fields.Datetime.context_timestamp(self, timestamp=datetime.datetime.now())


datetime.datetime(2014, 6, 15, 21, 26, 1, 248354, tzinfo=<DstTzInfo 'Europe/Brussels' CEST+2:00:00 DS
>>> fields.Datetime.now()
'2014-06-15 19:26:13'
>>> fields.Datetime.from_string(fields.Datetime.now())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Datetime.to_string(datetime.datetime.now())
'2014-06-15 19:26:13'

Binary

Store file encoded in base64 in bytea column:


abin = fields.Binary()

Selection

Store text in database but propose a selection widget. It induces no selection constraint in database. Selection must be
set as a list of tuples or a callable that returns a list of tuples:
aselection = fields.Selection([('a', 'A')])
aselection = fields.Selection(selection=[('a', 'A')])
aselection = fields.Selection(selection='a_function_name')

Specific options:
selection: a list of tuple or a callable name that take recordset as input
size: the option size=1 is mandatory when using indexes that are integers, not strings
2.3. Odoo Python

25

Odoo development Documentation, Release master

When extending a model, if you want to add possible values to a selection field, you may use the selection_add
keyword argument:
class SomeModel(models.Model):
_inherits = 'some.model'
type = fields.Selection(selection_add=[('b', 'B'), ('c', 'C')])

Reference

Store an arbitrary reference to a model and a row:


aref = fields.Reference([('model_name', 'String')])
aref = fields.Reference(selection=[('model_name', 'String')])
aref = fields.Reference(selection='a_function_name')

Specific options:
selection: a list of tuple or a callable name that take recordset as input
Many2one

Store a relation against a co-model:


arel_id = fields.Many2one('res.users')
arel_id = fields.Many2one(comodel_name='res.users')
an_other_rel_id = fields.Many2one(comodel_name='res.partner', delegate=True)

Specific options:
comodel_name: name of the opposite model
delegate: set it to True to make fields of the target model accessible from the current model (corresponds to
_inherits)
One2many

Store a relation against many rows of co-model:


arel_ids = fields.One2many('res.users', 'rel_id')
arel_ids = fields.One2many(comodel_name='res.users', inverse_name='rel_id')

Specific options:
comodel_name: name of the opposite model
inverse_name: relational column of the opposite model
Many2many

Store a relation against many2many rows of co-model:


arel_ids = fields.Many2many('res.users')
arel_ids = fields.Many2many(comodel_name='res.users',
relation='table_name',
column1='col_name',
column2='other_col_name')

26

Chapter 2. Module Development

Odoo development Documentation, Release master

Specific options:
comodel_name: name of the opposite model
relation: relational table name
columns1: relational table left column name
columns2: relational table right column name
Name Conflicts

Note: fields and method name can conflict.


When you call a record as a dict it will force to look on the columns.
Fields Defaults
Default is now a keyword of a field:
You can attribute it a value or a function
name = fields.Char(default='A name')
# or
name = fields.Char(default=a_fun)
#...
def a_fun(self):
return self.do_something()

Using a fun will force you to define function before fields definition.
Computed Fields
There is no more direct creation of fields.function.
Instead you add a compute kwarg. The value is the name of the function as a string or a function. This allows to
have fields definition atop of class:
class AModel(models.Model):
_name = 'a_name'
computed_total = fields.Float(compute='compute_total')
def compute_total(self):
...
self.computed_total = x

The function can be void. It should modify record property in order to be written to the cache:
self.name = new_value

Be aware that this assignation will trigger a write into the database. If you need to do bulk change or must be careful
about performance, you should do classic call to write

2.3. Odoo Python

27

Odoo development Documentation, Release master

To provide a search function on a non stored computed field you have to add a search kwarg on the field. The value
is the name of the function as a string or a reference to a previously defined method. The function takes the second
and third member of a domain tuple and returns a domain itself
def search_total(self, operator, operand):
...
return domain # e.g. [('id', 'in', ids)]

Inverse
The inverse key allows to trigger call of the decorated function when the field is written/created
Multi Fields
To have one function that compute multiple values:
@api.multi
@api.depends('field.relation', 'an_otherfield.relation')
def _amount(self):
for x in self:
x.total = an_algo
x.untaxed = an_algo

Related Field
There is not anymore fields.related fields.
Instead you just set the name argument related to your model:
participant_nick = fields.Char(string='Nick name',
related='partner_id.name')

The type kwarg is not needed anymore.


Setting the store kwarg will automatically store the value in database. With new API the value of the related field
will be automatically updated, sweet.
participant_nick = fields.Char(string='Nick name',
store=True,
related='partner_id.name')

Note: When updating any related field not all translations of related field are translated if field is stored!!
Chained related fields modification will trigger invalidation of the cache for all elements of the chain.
Property Field
There is some use cases where value of the field must change depending of the current company.
To activate such behavior you can now use the company_dependent option.
A notable evolution in new API is that property fields are now searchable.

28

Chapter 2. Module Development

Odoo development Documentation, Release master

WIP copyable option


There is a dev running that will prevent to redefine copy by simply setting a copy option on fields:
copy=False

# !! WIP to prevent redefine copy

2.3.8 Model constraints


Odoo provides two ways to set up automatically verified invariants: Python constraints <openerp.api.constrains> and
SQL constraints <openerp.models.Model._sql_constraints>.
A Python constraint is defined as a method decorated with ~openerp.api.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:
from openerp.exceptions import ValidationError
@api.constrains('age')
def _check_something(self):
for record in self:
if record.age > 20:
raise ValidationError("Your record is too old: %s" % record.age)
# all records passed the test, don't return anything

SQL constraints are defined through the model attribute ~openerp.models.Model._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.

2.4 XML
2.4.1 Create record of model
Create new record:
<openerp>
<data>
<record id="demo_multi_session" model="pos.multi_session">
<field name="name">multi session demo</field>
</record>
</data>
openerp>

If model exist it will be modifyed. Record creating in module it declareted. To change model created in another
module add mule name before id:
<openerp>
<data>
<record id="point_of_sale.pos_config_main" model="pos.config">
<field name="multi_session_id" ref="demo_multi_session"/>
</record>
</data>
openerp>

2.4. XML

29

Odoo development Documentation, Release master

2.4.2 Xpath
Add some attributes to node
Code:
<xpath expr="//some/xpath" position="attributes">
<attribute name="some_field">
</xpath>

Qweb expression:
<attribute name="t-att-another_field">website.get_another_field_value()</attribute>

After rendering it becomes regular attribute:


<.... another_field="value" ...>

Important

Inside of
<xpath expr="//some/xpath" position="attributes">
...
</xpath>

you can put only <attribute name= and nothing more.

2.4.3 Basic stuff


Call method of some model and put result in variable
Code:
<t t-set="order" t-value="website.sale_get_order()"/>

Here website means you use website=True in controller. TODO my be wrong.


Get value of some setting ir.config_parameter and put it in variable
Code:
<t t-set="foobar" t-value="website.env['ir.config_parameter'].get_param('my_module.foobar')"/>

Show value of variable


Code:
<p><t t-esc="foobar"/></p>

30

Chapter 2. Module Development

Odoo development Documentation, Release master

Use variable in condition


Code:
<label t-if="foobar">
<p>foobar is true</p>
</label>

Get variable transmitted by render() in XML template


Code:
t-att-value="my_var"

my_var is element of values dictionary (second argument of render()).

2.4.4 Inherit
Collisions and priority
If two or more xml templates inherit same parent template they can have same priorities. It may produce conflicts and
unexpected behavior. What you need is just set priority explicitly in your template:
<template id="..." inherit_id="..." priority="8" ..>
<xpath expr="..." position="...">
...
</xpath>
</template>
<!-- or -->
<record id="..." model="ir.ui.view">
...
<field name="inherit_id" ref="..."/>
<field name="priority" eval="8" />
<field name="arch" type="xml">
<xpath expr="..." position="...">
</xpath>
</field>
</record>

Less priority means prior execution.


Default priority is 16.

2.5 HTML
2.5.1 Active elements
Link-button that calls controller
Code:

2.5. HTML

31

Odoo development Documentation, Release master

<form action="/shop/checkout" name="myform" method="post">


<a class="btn btn-primary a-submit">My button</a>
</form>

Here action=/shop/checkout sets controller address. Class a-submit usually means do what in action of form.
Submit with button
Code:
<form action="/my_page" name="myform" method="post">
<button type="submit" class="btn btn-default">My button</button>
</form>

Wherein in controller in **post will be available some values from source form, those like <input/>.

2.6 CSS
2.6.1 CSS tips and tricks
Add your css on template
Code:
<template id="my_module_frontend" name="my_module assets" inherit_id="website_sale.assets_frontend">
<xpath expr="//link[@rel='stylesheet']" position="after">
<link rel="stylesheet" href="/my_module/static/src/css/main.css"/>
</xpath>
</template>

website_sale.assets_frontend is what you inherits.


Hide fields
Hide all children (that have attribute bill=1) of oe_website_sale class owner (that have attribute bill_enabled=0):
.oe_website_sale[bill_enabled='0'] [bill='1']{
display:none;
}

2.7 YAML
2.7.1 Pure YAML
TODO

2.7.2 YAML in odoo


TODO

32

Chapter 2. Module Development

Odoo development Documentation, Release master

2.8 Javascript
2.8.1 Inheritance
TODO

2.8.2 core.bus
core.bus (web.bus in 8.0) is used handle js events between modules.
Usage
// 8.0
var bus = openerp.web.bus;
// 9.0+
var core = require('web.core');
var bus = core.bus;
// bind event handler
bus.on('barcode_scanned', this, function (barcode) {
//...
})
// trigger event
bus.trigger('barcode_scanned', barcode);

2.8.3 Remote Procedure Call (RPC)


Call method
/**
* Call a method (over RPC) on the bound OpenERP model.
*
* @param {String} method name of the method to call
* @param {Array} [args] positional arguments
* @param {Object} [kwargs] keyword arguments
* @param {Object} [options] additional options for the rpc() method
* @returns {jQuery.Deferred<>} call result
*/
call: function (method, args, kwargs, options) {
args = args || [];
kwargs = kwargs || {};
if (!_.isArray(args)) {
// call(method, kwargs)
kwargs = args;
args = [];
}
var call_kw = '/web/dataset/call_kw/' + this.name + '/' + method;
return session.rpc(call_kw, {
model: this.name,
method: method,
args: args,

2.8. Javascript

33

Odoo development Documentation, Release master

kwargs: kwargs
}, options);
},

How to call wizard method from js


var compose_model = new Model('mail.compose.message');
return compose_model.call('create', [msg, {default_parent_id: options.parent_id}])
.then(function(id){
return compose_model.call('send_mail_action', [id, {}]);
});

2.9 Frontend
2.9.1 Web page
Common
Open a new project:
./odoo.py scaffold newpage addons

Add website as a dependency to newpage:


'depends': '[website]'

then add the website=True flag on the controller, this sets up a few new variables on the request object and allows
using the website layout in our template.
Creating pages
1 way
Write the following code in controllers.py:
from openerp import http
classNewPage(http.Controller):
@http.route('/new-page/',auth='public', website=True)
def index(self,**kw):
return http.request.render('newpage.index')

The new web page will appear by adding - /new-page/ http.request.render(newpage.index)


downloading a tamplate for a new page
A pattern templates.xml
<openerp>
<data>
<templateid="index">
<t t-call="website.layout">
<t t-set="title">New page</t>
<div class="oe_structure">
<div class="container">
<h1>My first web page</h1>
<p>Hello, world!</p>
</div>
</div>

34

Chapter 2. Module Development

Odoo development Documentation, Release master

</t>
</template>
</data>
</openerp>

website.layout means that the elements of pattern website are used.


After restarting the server while updating the module (in order to update the manifest and template) access
http://localhost:8069/new-page/. You will see a new page with a title My first web page and with text Hello, world!
2 way
Write in pattern the following:
<template name="Services page" id="website.services" page="True">
<t t-call="website.layout">
<div id="wrap">
<div class="container">
<h1>Our Services</h1>
<ul class="services">
<li>Cloud Hosting</li>
<li>Support</li>
<li>Unlimited space</li>
</ul>
</div>
</div>
</t>
</template>

page="True" creates a page as follows below: http://localhost:8069/page/services/


If add in view.xml:
<record id="services_page_link" model="website.menu">
<field name="name">Services</field>
<field name="url">/page/services</field>
<field name="parent_id" ref="website.main_menu" />
<field name="sequence" type="int">99</field>
</record>

This code will add a link to the main menu.

2.10 Point of Sale (POS)


2.10.1 Add new field in the model of POS module
To add new field in POS modules necessary in models.js override PosModel in the parent models which we take from
point_of_sale.models. For example:
var models = require('point_of_sale.models');
var _super_posmodel = models.PosModel.prototype;
models.PosModel = models.PosModel.extend({
initialize: function (session, attributes) {
// New code
var partner_model = _.find(this.models, function(model){
return model.model === 'product.product';
});

2.10. Point of Sale (POS)

35

Odoo development Documentation, Release master

partner_model.fields.push('qty_available');
// Inheritance
return _super_posmodel.initialize.call(this, session, attributes);
},
});

2.10.2 JS access and inheritance


action_button
Here you will find explanation of how to get/inherit action_button POS objects.
For example we have definition in this file:
odoo.define('pos_reprint.pos_reprint', function (require) {
...
screens.define_action_button({
'name': 'guests',
'widget': TableGuestsButton,
'condition': function()

This defenition doesnt return class ReprintButton. So, we cannot inherit it in a usual way.
In order to reach that object we need get instance of it using gui. Then we can inherit it
To make clear what this is like look up example where guests number button renderings:
this.gui.screen_instances['products'].action_buttons['guests'].renderElement();

While you can make call and even replace function with new one, you are not able to make inheritance via extend
or include functions. Its because we cannot reach Class and only get access to instance of that class.
This kind of approach make sense only for those widgets:
DiscountButton
ReprintButton
TableGuestsButton
SubmitOrderButton
OrderlineNoteButton
PrintBillButton
SplitbillButton
set_fiscal_position_button

screen_classes
To create new screen widget (via the extend() method) or to modify existing screen widget (via the include() method)
you need the target class. Usually you can get this class using following code:
odoo.define('module_name.file_name', function (require) {
"use strict";
var screens = require('point_of_sale.screens');
screens.OrderWidget.include({
...

36

Chapter 2. Module Development

Odoo development Documentation, Release master

But it is available only for widgets that are returned by main function in the file point_of_sale/static/src/js/screens.js.
List of the screens:
ReceiptScreenWidget
ActionButtonWidget
define_action_button
ScreenWidget
PaymentScreenWidget
OrderWidget
NumpadWidget
ProductScreenWidget
ProductListWidget
In other cases you can get targeted screen widget class using following code:
odoo.define('module_name.file_name', function (require) {
"use strict";
var gui = require('point_of_sale.gui');

gui.Gui.prototype.screen_classes.filter(function(el) { return el.name == 'clientlist'})[0].widget.inc


...

List of screens available via screen_classes:


gui.define_screen({name: 'scale', widget: ScaleScreenWidget});
gui.define_screen({name: 'products', widget: ProductScreenWidget});
gui.define_screen({name: 'clientlist', widget: ClientListScreenWidget});
gui.define_screen({name: 'receipt', widget: ReceiptScreenWidget});
gui.define_screen({name: 'payment', widget: PaymentScreenWidget});
gui.define_screen({name: 'bill', widget: BillScreenWidget});
gui.define_screen({'name': 'splitbill', 'widget': SplitbillScreenWidget,
gui.define_screen({'name': 'floors', 'widget': FloorScreenWidget,

2.11 Access
2.11.1 Security tutorial
Resources:
http://odoo-docs.readthedocs.org/en/latest/04_security.html
https://www.odoo.com/documentation/9.0/howtos/backend.html#security
https://www.odoo.com/documentation/9.0/reference/security.html
Odoo is very flexible on the subject of security. We can control what users can do and what they cannot on different
levels. Also we can control independently each of the four basic operations: read, write, create, unlink. I.e. allow only
read, allow only create, grant permission to create or delete only.
On fields/menu level we can:
hide fields or menus for some users and show them for others

2.11. Access

37

Odoo development Documentation, Release master

make fields readonly for some users and make them editable for others
show different variants to pick on the Selection fields for different users
On the fields level of security res.users and res.groups models are used. These models relate to each other as
many2many. This means that a user can be a member of many groups and one group can be assigned to many users.
One example of how we can hide menu in regard to current users groups is the following.

On the picture above in Settings / Users we can see only Users menu. We know that there should be Groups
menu also. Let Us see in ./openerp/addons/base/res/res_users_view.xml on the point of how menuitem can be hidden.

38

Chapter 2. Module Development

Odoo development Documentation, Release master

<record id="action_res_groups" model="ir.actions.act_window">


<field name="name">Groups</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.groups</field>
<field name="view_type">form</field>
<field name="help">A group is a set of functional areas that will be assigned to the
user in order to give them access and rights to specific applications and tasks in
the system. You can create custom groups or edit the ones existing by default
in order to customize the view of the menu that users will be able to see. Whether
they can have a read, write, create and delete access right can be managed from here.
</field>
</record>
<menuitem action="action_res_groups" id="menu_action_res_groups" parent="base.menu_users"
groups="base.group_no_one"/>

The groups attribute in the menuitem element shows us that only the members of base.group_no_one
group can see the Groups menu item.
The base.group_no_one xmlid is defined in the
./openerp/addons/base/security/base_security.xml as follows.
<record model="res.groups" id="group_erp_manager">
<field name="name">Access Rights</field>
</record>
<record model="res.groups" id="group_system">
<field name="name">Settings</field>
<field name="implied_ids" eval="[(4, ref('group_erp_manager'))]"/>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record model="res.groups" id="group_user">
<field name="name">Employee</field>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record model="res.groups" id="group_multi_company">
<field name="name">Multi Companies</field>
</record>
<record model="res.groups" id="group_multi_currency">
<field name="name">Multi Currencies</field>
</record>
<record model="res.groups" id="group_no_one">
<field name="name">Technical Features</field>
</record>
<record id="group_sale_salesman" model="res.groups">
<field name="name">User</field>
</record>
<record id="group_sale_manager" model="res.groups">
<field name="name">Manager</field>
<field name="implied_ids" eval="[(4, ref('group_sale_salesman'))]"/>
</record>

Here we can see the group_no_one along with the other base groups. Note that group_no_one has Technical
Features name. Let us include our user in the Technical Features group. Since we have no access to the
Groups menu item, the only way we can do it is from the Users menu item. See the picture below.

2.11. Access

39

Odoo development Documentation, Release master

Check the Technical Features box and reload odoo. Now we can see the Groups menu item!

40

Chapter 2. Module Development

Odoo development Documentation, Release master

From Settings / Users / Groups we can see a list of existing groups. Here we also can assign users for
groups.
Hide fields
In
the
./openerp/addons/base/res/res_users_view.xml
we
can
see
the
view_users_simple_form view. Note here that the company_id field is visible only for members of
the base.group_multi_company group.
<!-- res.users -->
<record id="view_users_simple_form" model="ir.ui.view">
<field name="name">res.users.simplified.form</field>
<field name="model">res.users</field>
<field name="priority">1</field>

2.11. Access

41

Odoo development Documentation, Release master

<field name="arch" type="xml">


<form string="Users">
<sheet>
<field name="id" invisible="1"/>
<div class="oe_form_box_info oe_text_center" style="margin-bottom: 10px" attrs="{'inv
You are creating a new user. After saving, the user will receive an invite email
</div>
<field name="image" widget='image' class="oe_avatar oe_left" options='{"preview_image
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
<field name="email" invisible="1"/>
<label for="login" class="oe_edit_only" string="Email Address"/>
<h2>
<field name="login" on_change="on_change_login(login)"
placeholder="[email protected]"/>
</h2>
<label for="company_id" class="oe_edit_only" groups="base.group_multi_company"/>
<field name="company_id" context="{'user_preference': 0}" groups="base.group_mult
</div>
<group>
<label for="groups_id" string="Access Rights"
attrs="{'invisible': [('id', '>', 0)]}"/>
<div attrs="{'invisible': [('id', '>', 0)]}">
<field name="groups_id" readonly="1" widget="many2many_tags" style="display:
</div>
<field name="phone"/>
<field name="mobile"/>
<field name="fax"/>
</group>
</sheet>
</form>
</field>
</record>

Our current user is Administrator. By default he is not a member of the base.group_multicompany group. That
is why the company_id isnt visible for him on the form.

42

Chapter 2. Module Development

Odoo development Documentation, Release master

Model records:
restrict access to specified subset of records in model
Model:
restrict access to all records of model

2.11.2 Superuser rights


Administrator, i.e. user with id 1 (SUPERUSER_ID), has exceptions about access rights.
ir.model.access
If some model doesnt have records in ir.model.access (Access Rules), then only Administrator has access to that
model.
Note: Official documentation states record rules do not apply to the Administrator user although access rules do
seems to be wrong. Access Rules dont to apply to Administrator too. See the source: 8.0, 9.0, 10.0
See also:
ir.model.access

2.11. Access

43

Odoo development Documentation, Release master

ir.rule

2.12 Hooks
2.13 Tests
2.13.1 Basic python tests
This tests runs with -d [my_db] -u [module_to_be_tested] --test-enable --workers=0 parameters.
To make some tests do next steps:
Create folder named tests
Add __init__.py file
Create file that name begins from test_
Add test methods that names start from test_
Example (will result testing error):
from openerp.tests.common import TransactionCase
class TestMessage(TransactionCase):
at_install = False
post_install = True
def test_count(self):
self.assertEqual(1, 0)

Test class
From openerp/tests/common.py:
class BaseCase(unittest.TestCase):
"""
Subclass of TestCase for common OpenERP-specific code.
This class is abstract and expects self.registry, self.cr and self.uid to be
initialized by subclasses.
"""
class TransactionCase(BaseCase):
""" TestCase in which each test method is run in its own transaction,
and with its own cursor. The transaction is rolled back and the cursor
is closed after each test.
"""
class SingleTransactionCase(BaseCase):
""" TestCase in which all test methods are run in the same transaction,
the transaction is started with the first test method and rolled back at
the end of the last.
"""
class SavepointCase(SingleTransactionCase):
""" Similar to :class:`SingleTransactionCase` in that all test methods

44

Chapter 2. Module Development

Odoo development Documentation, Release master

are run in a single transaction *but* each test case is run inside a
rollbacked savepoint (sub-transaction).
Useful for test cases containing fast tests but with significant database
setup common to all cases (complex in-db test data): :meth:`~.setUpClass`
can be used to generate db test data once, then all test cases use the
same data without influencing one another but without having to recreate
the test data either.
"""
class HttpCase(TransactionCase):
""" Transactional HTTP TestCase with url_open and phantomjs helpers.
"""

at_install, post_install
By default, odoo runs test with paramaters:
at_install = False
post_install = True

at_install - run tests right after loading modules files. It runs only in demo mode.
post_install - run test after full installation process. It differs from at_install, because
it runs after calling registry.setup_models(cr)
it runs after calling model._register_hook(cr)
Assert Methods
https://docs.python.org/2.7/library/unittest.html#assert-methods

2.13.2 JS Testing
Regular phantom JS tests
For automatic web tests odoo uses phantom_js. You can test you module web mechanics behavior using phantom js.
What you need is:
Install phantom. sudo apt-get install phantomjs
Create folder named tests
Add __init__.py file
Create file that name begins from test_
Add test methods than names start from test_
Example:
import openerp.tests
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):

2.13. Tests

45

Odoo development Documentation, Release master

def test_01_mail_sent(self):
# wait till page loaded and then click and wait again
code = """
setTimeout(function () {
$(".mail_sent").click();
if (location.href.indexOf('channel_sent')!=-1) {
throw new Error('Already on channel_sent.');
}
setTimeout(function () {
if (location.href.indexOf('channel_sent')==-1) {
throw new Error('End page is not channel_sent.');
}
console.log('ok');
}, 3000);
}, 1000);
"""
link = '/web#action=%s' % self.ref('mail.mail_channel_action_client_chat')
self.phantom_js(link, code, "odoo.__DEBUG__.services['mail_sent.sent']", login="demo")

You need to call phantom_js and give to it arguments:


Starting url
JS code intended for execution
Ready criteria.
Some JS object that indicates preparedness of web page.
odoo.define(mail_archives.archives ...

In 9.0 it may be

User name
Use throw new Error(Error text); for errors handling.
JS phantom tests using Tours
It is possible to run js phantom tests using Tour as JS testing code. To run test automatically after installing module
you will need:
Install phantomjs if dont have yet
Inject JS Tour file on web page
Create test as described higher
Call tour
Call tour example:

class TestUi(openerp.tests.HttpCase):
def test_01_res_partner_mails_to_count(self):
self.phantom_js('/', "openerp.Tour.run('mails_count_tour', 'test')", "openerp.Tour.tours.mai

Also odoo must be started with -d , test-enable and without db-filter , workers. If assumes ti run test only on install
or update use -i or -u. Werkzeug must be 0.11.5 or higher.
Look up js tour page for details.

2.13.3 Paypal testing


To test paypal payments you need to:

46

Chapter 2. Module Development

Odoo development Documentation, Release master

Create developer account


Add seller and buyer in developer sandbox
Configure odoo
Directly testing
Create developer account
Go to https://developer.paypal.com/ and create new account.
Add seller and buyer
Go to Dashboard->Sand box->Accounts. Create business (seller) and personal (buyer) accounts.
Add some money to buyer (type amount in according field).
Go to http://sandbox.paypal.com and login as seller. May be you will be forced to apply unconfirmed ssl
certificate.
Go to Profile.
Copy protected seller code.
Configure odoo
Install payment_paypal module
Go to Settings->Payments->Payments->Paypal.
Pres Edit.
Enter here Paypal Email ID - it is seller account (like [email protected]).
Enter Paypal Merchant ID - paste protected seller code.
Set price list currency same as in paypal account.
Directly testing
Open web shop. Buy some goods and pay with paypal. When you will be redirected on paypal page use buyer login
and password.

2.13.4 What to test


Obviously, you have to test features that module provide. But, its important to have a stable module to test that
features in a different context. This article tries to describe what that context could be. It can be used both for manual
and automatic tests.
More about automatic tests:
Client-side unittests
Server-side unittests

2.13. Tests

47

Odoo development Documentation, Release master

User
While you develop a module, you can use an admin user for manual checking the result. It could simplify the process of
development, because you can skip security stuff for a while. But when you prepare module for relase you absolutely
need to check how system works from non-admin user.
Warning: Admin user has special access rights. Use another User to test module.

2.14 Debugging
2.14.1 Logs
There are several places where you can get logs.
Its better to activate developer (debug) mode in browser when you are looging for logs.
Error Message
Terminal
Console
boot.js
Sources
Network
How to see html request initiator

Error Message
Its a first place where you can see error message. But in most time, it doesnt contain enough information to resolve
problem. Check other possbile ways to get log messages below.
Terminal
Its a place where you run odoo.
Any errors related to python can be found here
Console
Its a short term for browsers console. Click F12 in browser to open console.
It can contain error and warning about client part.
boot.js

Example is here: Failed modules

48

Chapter 2. Module Development

Odoo development Documentation, Release master

Sources
Allows you to check which client side files are loaded and which are not. To do this:
1. Turn on debug mode in the url.
2. Open Developer tools (F12), go to the Sources tab and reload page.
3. Open left panel (if it is not open yet) and search interested app.
Example: Missing dependencies error in console
Network
Sometime error are not printed neither in Terminal, nor in Console. Then you can try to find some logs at Network tab
of browsers developer tool. To see original odoo js files i.e. not minimized versions, swich odoo in debug mode first.
How to see html request initiator

Suppose we want to know which part of our script initiate the request. If it is javascript we could see full program
stack by putting mouse pointer on the initiator columns element.

2.14.2 Typical errors


Error: Failed modules
If into server console no errors but boot.js raise exception that find out reason error next steps:

2.14. Debugging

49

Odoo development Documentation, Release master

1. Go to error line into boot.js.


2. Turn on breakpoint.

3. Rerun script (click F5)


4. When script stop on error line move to console.
5. Type command:
failed[0].error

6. To receive the output

50

Chapter 2. Module Development

Odoo development Documentation, Release master

Error: Missing dependencies


For example, sometimes during page load displayed the error type:
Missing dependencies:

2.14. Debugging

[...]

Non loaded modules:

[...]

51

Odoo development Documentation, Release master

You can find out reason in the Developer Tool in the tab Sources as described above.

Likely you can not find files included in the Missing dependencies list. Then you need to check they are included in
the view (.xml) files.
AccessError: Please contact your system administrator
There is an AccessError which doesnt specify groups that have access to an operation. It simply states:
The requested operation cannot be completed due to security restrictions. Please contact your system
administrator.

52

Chapter 2. Module Development

Odoo development Documentation, Release master

Such error means, that your user doesnt satisfy access requirements specified in ir.rule. See Access section for general
understanding how odoo security works.

2.14.3 QWeb
The javascript QWeb implementation provides a few debugging hooks:
t-log takes an expression parameter, evaluates the expression during rendering and logs its result with
console.log:
<t t-set="foo" t-value="42"/>
<t t-log="foo"/>

will print 42 to the console


t-debug triggers a debugger breakpoint during template rendering:
<t t-if="a_test">
<t t-debug="">
</t>

will stop execution if debugging is active (exact condition depend on the browser and its development tools)
t-js the nodes body is javascript code executed during template rendering. Takes a context parameter, which is
the name under which the rendering context will be available in the t-jss body:
<t t-set="foo" t-value="42"/>
<t t-js="ctx">
console.log("Foo is", ctx.foo);
</t>

Source

2.14. Debugging

53

Odoo development Documentation, Release master

2.14.4 Emulation of slow internet connections in browser

Emulation of package lossing


In case if you need to emulate bad connection, i.e. it works and probably fast, but lose some percents of TCP packages,
then do as following
# check your network interfaces
ifconfig
#
#
#
#

Example below is for eth0


Other possbile values are
* wlan0 - wireless connection
* lo - local connection. Use this, if your run a server on your machine

# lose 30 %
sudo tc qdisc add dev eth0 root netem loss 30%
# "burst of losing"
# Probabilyt of each next lossing depends on previous result.
# For example below:
# Pnext = 0.1 * Pprev + (1-0.1)* Random(0,1)
# Then the package is lost, if Pnext < 0.3
sudo tc qdisc add dev eth0 root netem loss 30% 10%
# show current settings
tc -s qdisc show dev eth0
# reset settings
sudo tc qdisc del dev eth0 root

54

Chapter 2. Module Development

Odoo development Documentation, Release master

2.14.5 Emulation barcode


Barcode scanner connected with computer work as keyboard. E.g. after scanning send sequence of symbols as if fast
typing on the keyboard.
Install xdotool app if you havent it yet.
sudo apt-get install xdotool

Emulation scanning barcode:


sleep 3 && echo '1234567890128'| grep -o . | xargs xdotool key && xargs xdotool key \n &

or so:
sleep 3 && echo '3333333333338'| grep -o . | xargs xdotool key && xargs xdotool key \n &

Where: 3 - sleep seconds; 3333333333338 - barcode.


After successfully scanning you will see 3333333333338 in the command line. If toggle to other window that
symbols appear in the input field in the this window. So we can send sequence in the app as if we scanning it.

2.14.6 ESC/POS printer emulation


hw_escpos
apply patch
install hw_escpos on odoo
run odoo with -d DB_WITH_HW_ESCPOS --db-filter=DB_WITH_HW_ESCPOS
in new terminal run
tail -f /tmp/printer

On printing:
some binary data is sent to /tmp/printer
odoo prints logs with unparsed data
POS
At any database, including one on runbot:
set Receipt printer checkbox in pos.config and set ip equal to 127.0.0.1
open POS interface
print ticket

2.15 Source Diving


Source Diving is a way to find answers to your questions.

2.15. Source Diving

55

Odoo development Documentation, Release master

2.15.1 Source Diving Cases


This section contains live examples of source diving.
Each case contains problem description and possible solutions. Use problems as exercises and solutions as manual.
Case: Transformed the method
Context

When porting module mail_move_message in the file static/src/js/mail_move_message.js there is a method session.web.form.FormOpenPopup(this).
Problem

In 9.0 not found such object. What object would be the analogue of the object? What you need to do to find this
object?
Solution

Possible solution
Guidelines
Use template below for new cases
===========
CASE NAME
===========
Context
=======
What we have. E.g. some module, or out-of-box odoo version 8.0
* LINK1
* LINK2
Problem
=======
What we need to do. E.g. port module to 9.0
* LINK1
* LINK2
Solution
========
:doc:`Possible solution <./answers/CASE_NAME>`

56

Chapter 2. Module Development

Odoo development Documentation, Release master

2.15.2 Overview: Transformed the method


Quite often when porting a module from 8.0 to 9.0 there is a situation, when 8.0 is a object, but there is no 9.0. And it
is not clear - it is outdated and it was removed or it was renamed. In very advanced cases, an object can be renamed
and changed almost beyond recognition.
To search you need to take several steps:
1. The default view that such an object exist, but it was renamed.
2. Look, what makes this object.
3. Search by name of methods that contains the given object, excluding common words (for example, init, start,
destroy...).
4. If the result is not found that search by unique keywords which can be found by bringing the object.
5. If anything gave no results, then maybe the object is deleted as obsolete.
Case
Possible solution

2.16 Lint
2.16.1 Script for fixing travis error on odoo
Installation
# install autopep8
sudo pip install --upgrade autopep8
# install oca-autopep8
git clone https://github.com/OCA/maintainer-tools.git
cd maintainer-tools
sudo python setup.py install
# install autoflake
sudo pip install --upgrade autoflake
# install fixmyjs
sudo npm install fixmyjs -g

Script
# fix line break symbols
cd /path/to/MODULE_NAME
find * -type f | grep -v ".\(svg\|png\|jpg\)$" | xargs sed -i 's/\r//g'
# add line break to the end of file
find * -type f | grep -v ".\(png\|jpg\)$" | xargs sed -i '$a\'
# trim trailing whitespaces
find * -type f | grep -v ".\(svg\|png\|jpg\)$" | xargs sed -i 's/[ \t]*$//g'
# PEP8

py-:

2.16. Lint

57

Odoo development Documentation, Release master

autopep8 --in-place -r --aggressive --aggressive --ignore E501 ./


# fix CamelCase
oca-autopep8 -ri --select=CW0001 .
# Replacement button
find . -type f -name
find . -type f -name
find . -type f -name

'Tab' on 4 button 'Space':


'*.xml' | xargs sed -i 's/\t/
'*.py' | xargs sed -i 's/\t/
'*.js' | xargs sed -i 's/\t/

/g'
/g'
/g'

# Replacement (relative-import)
find . -type f -name '__init__.py' | xargs sed -i 's/^import/from . import/g'
#find . -type f -name '__init__.py' | xargs sed -i 's/^import controllers/from . import controllers/g
#find . -type f -name '__init__.py' | xargs sed -i 's/^import models/from . import models/g'

# remove unused imports


autoflake --in-place -r --imports=openerp,openerp.http.request,openerp.SUPERUSER_ID,openerp.addons.ba
# remove prints
find . -type f -name '*.py' | xargs sed -i 's/^\( *\)\(print .*\)/\1# \2/g'
#Fix comments:
find . -type f -name '*.py' | xargs sed -i -e 's/ #\([^ ]\)/ # \1/g'
#lint for js:
fixmyjs --legacy --config ~/js_conf.json ./

# Addition of the first row (coding) in py-files


find -iname '*.py' | xargs grep -rLP 'coding: *utf-8' | xargs sed -i '1s/^/# -*- coding: utf-8 -*-\n/
# Correction is rights for run:
find -iname '*.py' | xargs chmod -x
# Duplicate implicit target name: "changelog".
find . -type f -name 'changelog.rst' | xargs sed -i 's/^Changelog/Updates/g'
find . -type f -name 'changelog.rst' | xargs sed -i 's/^=========/=======/g'
# Replace @api.one -> @api.multi
find . -type f -name '*.py' | xargs perl -i -p0e 's/'\
'@api\.one\n'\
'
def ([^(]*)\(([^(]*)\):/'\
'@api.multi\n'\
'
def $1($2):\n'\
'
for r in self:\n'\
'
r.$1_one($2)\n'\
'\n'\
'
\@api.multi\n'\
'
def $1_one($2):\n'\
'
self.ensure_one()/g'

Run following script only once:


# Correction is links in rst-files
#`_
->
`__
find . -type f -name '*.rst' | xargs sed -i 's/`_/`__/g'

58

Chapter 2. Module Development

Odoo development Documentation, Release master

2.17 Other
2.17.1 Dynamic records
While XML allows you create only static records, there is a way to create record dynamically via python code. You
need dynamic records, for example, to add support both for enterprise and community releases or to add some records
to each company in database etc.
There several ways to execute code on installation:
TODO
TODO
TODO
The problem with dynamic records is that odoo considers such records as ones, which were in xml files, but now
deleted. It means that odoo will delete such dynamic records right after updating. There are two ways to resolve it.
noupdate=False
Simply add update=True to your ir.model.data record:

debt_account = registry['account.account'].create(cr, SUPERUSER_ID, {


'name': 'Debt',
'code': 'XDEBT',
'user_type_id': registry.get('ir.model.data').get_object_reference(cr, SUPERUSER_ID, 'account', '
'company_id': company.id,
'note': 'code "XDEBT" should not be modified as it is used to compute debt',
})
registry['ir.model.data'].create(cr, SUPERUSER_ID, {
'name': 'debt_account_' + str(company.id),
'model': 'account.account',
'module': 'pos_debt_notebook',
'res_id': debt_account,
'noupdate': True, # If it's False, target record (res_id) will be removed while module update
})

noupdate=True
If for some reason you cannot use noupdate=False, you can use following trick.
Here is the example from web_debranding module. To create records in ir.model.data we use name
_web_debranding. Then odoo will consider such records as belonging to another module (_web_debranding)
and will not delete them. But it also means, that odoo will not delete them after uninstalling. For later case, we need
to use uninstall_hook.

2.17. Other

59

Odoo development Documentation, Release master

Contents
Dynamic records
noupdate=False
noupdate=True
* python file
* yaml file
* __openerp__.py
* __init__.py

python file
from openerp import SUPERUSER_ID, models, tools, api
MODULE = '_web_debranding'
class view(models.Model):
_inherit = 'ir.ui.view'
def _create_debranding_views(self, cr, uid):
self._create_view(cr, uid, 'menu_secondary', 'web.menu_secondary', '''
<xpath expr="//div[@class='oe_footer']" position="replace">
<div class="oe_footer"></div>
</xpath>''')

def _create_view(self, cr, uid, name, inherit_id, arch, noupdate=False, type='qweb'):


registry = self.pool
view_id = registry['ir.model.data'].xmlid_to_res_id(cr, SUPERUSER_ID, "%s.%s" % (MODULE, name
if view_id:
registry['ir.ui.view'].write(cr, SUPERUSER_ID, [view_id], {
'arch': arch,
})
return view_id
try:

view_id = registry['ir.ui.view'].create(cr, SUPERUSER_ID, {


'name': name,
'type': type,
'arch': arch,
'inherit_id': registry['ir.model.data'].xmlid_to_res_id(cr, SUPERUSER_ID, inherit_id,
})
except:
import traceback
traceback.print_exc()
return
registry['ir.model.data'].create(cr, SUPERUSER_ID, {
'name': name,
'model': 'ir.ui.view',
'module': MODULE,
'res_id': view_id,
'noupdate': noupdate,
})
return view_id

60

Chapter 2. Module Development

Odoo development Documentation, Release master

yaml file
!python {model: ir.ui.view}: |
self._create_debranding_views(cr, uid)

__openerp__.py
'uninstall_hook': 'uninstall_hook',
'data': [
'path/to/file.yml'
]

__init__.py
from openerp import SUPERUSER_ID
MODULE = '_web_debranding'
def uninstall_hook(cr, registry):
registry['ir.model.data']._module_data_uninstall(cr, SUPERUSER_ID, [MODULE])

2.17.2 Odoo database


Many to many
For every many to many field odoo creating new relations table for example pos_multi_rel with _rel postfix.

2.17.3 Odoo way of shaman


What to do if something not work but should to
1. Refresh page
2. Update module
3. Check openerp file depends, demo and other important fields
4. Check odoo config you use to run odoo. Especially adons paths
5. Uninstall and install again modules in depends
6. Clean browser cache
7. Carefully check logs. Look up if needed files loaded or not. May be some errors.
8. Create new base and install all modules.

2.17. Other

61

Odoo development Documentation, Release master

62

Chapter 2. Module Development

CHAPTER 3

Module Migration

3.1 Switching module to new api


3.1.1 Automatic replacements
# IMPORTS
# replace osv,
find . -type f
find . -type f
find . -type f
find . -type f
find . -type f
find . -type f
find . -type f
find . -type f
find . -type f
find . -type f

orm
-name
-name
-name
-name
-name
-name
-name
-name
-name
-name

'*.py'
'*.py'
'*.py'
'*.py'
'*.py'
'*.py'
'*.py'
'*.py'
'*.py'
'*.py'

|
|
|
|
|
|
|
|
|
|

xargs
xargs
xargs
xargs
xargs
xargs
xargs
xargs
xargs
xargs

sed
sed
sed
sed
sed
sed
sed
sed
sed
sed

-i
-i
-i
-i
-i
-i
-i
-i
-i
-i

's/from openerp.osv import orm$/from odoo import models/g'


's/from openerp.models.orm import Model$/from odoo.models
's/osv.osv_memory/models.TransientModel/g'
's/osv.osv/models.Model/g'
's/osv.except_osv/UserError/g'
's/osv\./models./g'
's/\<orm\./models./g'
's/\(import .*\), osv/\1, models/g'
's/\(import .*\)osv, /\1models, /g'
's/\(import .*\)osv/\1models/g'

find . -type f -name '*.py' | xargs sed -i 's/\(import .*\), orm/\1/g'


find . -type f -name '*.py' | xargs sed -i 's/\(import .*\)orm, /\1/g'
find . -type f -name '*.py' | xargs sed -i 's/^.*import orm$//g'
find . -type f -name '*.py' | xargs sed -i 's/openerp.osv/openerp/g'
# replace http
find . -type f
find . -type f
find . -type f

import
-name '*.py' | xargs sed -i 's/from openerp.addons.web import http/from odoo import ht
-name '*.py' | xargs sed -i 's/openerp.addons.web.http/odoo.http/g'
-name '*.py' | xargs sed -i 's/openerp.http/odoo.http/g'

# replace odoo
# fix importing. Otherwise you will get error:
#
AttributeError: 'module' object has no attribute 'session_dir'
find . -type f -name '*.py' | xargs sed -i 's/openerp.tools.config/odoo.tools.config/g'

# FIELDS
# update fields
# (multiline: http://stackoverflow.com/questions/1251999/how-can-i-replace-a-newline-n-using-sed/7697
# delete _columns
find . -type f -name '*.py' | xargs perl -i -p0e 's/
_columns = {(.*?)\n
}/$1\n/gs'
# computed fields
find . -type f -name '*.py' | xargs sed -i 's/fields.function(\(.*\) \(["\x27][^,]*\)/fields.function
find . -type f -name '*.py' | xargs sed -i 's/fields.function(\(.*\) multi=[^,)]*/fields.function(\1/

63

Odoo development Documentation, Release master

find
find
find
find
find

.
.
.
.
.

-type
-type
-type
-type
-type

f
f
f
f
f

-name
-name
-name
-name
-name

'*.py'
'*.py'
'*.py'
'*.py'
'*.py'

|
|
|
|
|

xargs
xargs
xargs
xargs
xargs

sed
sed
sed
sed
sed

-i
-i
-i
-i
-i

's/fields.function(\([^,]*\)\(.*\)type=.\([2a-z]*\)["\x27]
's/fields.many2one(\(.*\)obj=\([^,]*\)/fields.many2one(\2,
's/,[ ]*,/,/g'
's/,[ ]*,/,/g'
's/,[ ]*,/,/g'

# replace fields
find . -type f -name '*.py' | xargs perl -i -p0e 's/
_columns = {(.*?)
}/$1/gs'
find . -type f -name '*.py' | xargs sed -i 's/fields\.\(.\)/fields.\u\1/g'
find . -type f -name '*.py' | xargs sed -i 's/
[\x27"]\(.*\)[\x27"].*:.*\(fields.*\),$/\1 = \2/g'
# renamed attributes
find . -type f -name '*.py' | xargs sed -i 's/select=/index=/g'
find . -type f -name '*.py' | xargs sed -i 's/digits_compute=/digits=/g'

3.1.2 Semi-Automatic replacements


We recommend to use commands below after commiting previous changes. It allows you to check differences.
The commands doesnt update code fully and usually you need to continue updates manually.
# pool -> env
find . -type f -name
# remove cr, uid
find . -type f -name
find . -type f -name
find . -type f -name
find . -type f -name
find . -type f -name
# res_config.py
find . -type f -name

'*.py' | xargs sed -i 's/self.pool/self.env/g'


'*.py'
'*.py'
'*.py'
'*.py'
'*.py'

|
|
|
|
|

xargs
xargs
xargs
xargs
xargs

sed
sed
sed
sed
sed

-i
-i
-i
-i
-i

's/(cr, [^,]*, /(/g'


's/(self, cr, [^,]*, ids/(self/g'
's/(self, cr, uid, /(self, /g'
's/, context=[^,)]*//g'
's/self.env.get(\([^)]*\))/self.env[\1]/g'

'res_config.py' | xargs sed -i 's/\(def get_default_.*\)(self)/\1(self, fields)/

3.2 Fixing references on migration


3.2.1 9.0- 10.0+

# menu_hr_configuration
find . -type f -name '*.xml' | xargs sed -i 's/menu_hr_configuration/menu_human_resources_configurati
# base.group_hr
find . -type f -name '*.csv' -o -name '*.py' -o -name '*.xml' | xargs sed -i 's/base.group_hr/hr.gr
# website.salesteam_website_sales
find . -type f -name '*.csv' -o -name '*.py' -o -name '*.xml' | xargs sed -i 's/website.salesteam_w
# base.group_sale_salesman
find . -type f -name '*.csv' -o -name '*.py' -o -name '*.xml' | xargs sed -i 's/base.group_sale_sal
# product.prod_config_main
find . -type f -name '*.xml' | xargs sed -i 's/product.prod_config_main/sale.prod_config_main/g'

Note: We are happy to share our experience and hope that it will help someone to port odoo modules. We will be
glad, if you share this page or recommend our team for module migration jobs:
[email protected]
https://www.it-projects.info/page/module-migration

64

Chapter 3. Module Migration

CHAPTER 4

User documentation

4.1 static/description/index.html
Image sizes
Templates
Title
Text + Image
Image + Text
Text, Image
Contact us
oe_dark

4.1.1 Image sizes


Image Sizes

4.1.2 Templates
Title
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">NAME</h2>
<h3 class="oe_slogan">SUMMARY OR SLOGAN</h3>
</div>
</div>
</section>

Text + Image
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span6">
<p class="oe_mt32">

65

Odoo development Documentation, Release master

TEXT
</p>
</div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_demo oe_picture oe_screenshot" src="IMAGE.png"/>
</div>
</div>
</div>
</section>

Image + Text
TODO
Text, Image
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12 text-center">
<p class="oe_mt32">
TEXT
</p>
</div>
<div class="oe_row_img oe_centered">
<img class="oe_demo oe_picture oe_screenshot" src="IMAGE.png"/>
</div>
</div>
</section>

Contact us
Contact us block

4.1.3 oe_dark
Use oe_dark class on every even section. Dont use oe_dark on the last section Contact us.
<section class="oe_container">
<!--Title-->
</section>
<section class="oe_container">
</section>
<section class="oe_container oe_dark">
</section>
<section class="oe_container">
</section>
<section class="oe_container oe_dark">
</section>

66

Chapter 4. User documentation

Odoo development Documentation, Release master

<section class="oe_container">
</section>
<section class="oe_container">
<!--Contact us block-->
</section>

4.2 Screenshots tools


Nimbus Screen Screenshot: http://nimbus.everhelper.me/screenshot.php
Shutter: http://shutter-project.org/
sudo add-apt-repository ppa:shutter/ppa
sudo apt-get update && sudo apt-get install shutter

4.3 Module description


4.3.1 Main screenshot
The main screenshot displayed only in Odoo Apps should be located in the path_to_module/images/ directory
and its size should not exceed 1500x1000 px. Next, in the __openerp__.py file you need make the relevant record:
'images': ['images/main-screenshot.png']

4.3.2 Icon and index.html


The module icon needs to be located at path_to_module/static/description/ and it must be called
icon.png. Also in this directory you need to create index.html, where will be contained necessary HTML
tags, text description and screenshots (the recommended size is 752x352 px).
See also:
See the official template https://github.com/odoo/odoo/blob/master/addons/crm/static/description/index.html
It is important that index.html and screenshots it contains should be included at the same folder.
The result of the index.html and icon appearance can be checked by opening the module in Local Modules of
your Odoo instance.

4.3.3 Summary
This is an overview of content that provides a reader with the overaching theme, but does not expand on specific
details.
Summary should be included at __openerp__.py as summary:

"""Summary text""". For example:

'summary': """Use multiple POS for handling orders"""

4.2. Screenshots tools

67

Odoo development Documentation, Release master

4.4 Contact us block


For every selling modules IT-Projects LLC adds block generated by following command:

export ODOO_BRANCH=10.0
echo && echo && \
curl --silent https://raw.githubusercontent.com/it-projects-llc/odoo-development/master/docs/descript
| sed "s/ODOO_BRANCH/$ODOO_BRANCH/g" \
| sed "s/STAMP1_ROTATION/$(($RANDOM % 20 - 10))/g" \
| sed "s/STAMP2_ROTATION/$(($RANDOM % 20 - 10))/g" && \
echo && echo

4.5 JS Tour
Used to demonstrate module capabilities step by step with popup windows. It may be launched automatically or
manually.

4.5.1 Creating Tour


Tour is a simple JS file with some determined structure. Example:
{

id: 'mails_count_tour',
name: _t("Mails count Tour"),
mode: 'test',
path: '/web#id=3&model=res.partner',
steps: [
{
title:
_t("Mails count tutorial"),
content:
_t("Let's see how mails count work."),
popover:
{ next: _t("Start Tutorial"), end: _t("Skip") },
},
{
title:
_t("New fields"),
content:
_t("Here is new fields with mails counters. Press one of it."),
element:
'.mails_to',
},
{
waitNot:
'.mails_to:visible',
title:
_t("Send message from here"),
placement: 'left',
content:
_t("Now you can see corresponding mails. You can send mail to this partner rig
element:
'.oe_mail_wall .oe_msg.oe_msg_composer_compact>div>.oe_compose_post',
},
]
}

What you do here is describing steps that got to be proceeded by user or phantom (phantomjs).
In odoo 8 tour defines this way:
(function () {
'use strict';
var _t = openerp._t;
openerp.Tour.register({ ...

68

Chapter 4. User documentation

Odoo development Documentation, Release master

In odoo 9 tour defines that way:


odoo.define('account.tour_bank_statement_reconciliation', function(require) {
'use strict';
var core = require('web.core');
var Tour = require('web.Tour');
var _t = core._t;
Tour.register({ ...

Important details:
id - need to call this tour
path - from this path tour will be started in test mode
Next step occurs when all conditions are satisfied and popup window will appear near (chose position in placement)
element specified in element. Element must contain css selector of corresponding node. Conditions may be:
waitFor - this step will not start if waitFor node absent.
waitNot - this step will not start if waitNot node exists.
wait - just wait some amount of milliseconds before next step.
element - similar to waitFor, but element must be visible
closed window - if popup window have close button it must be closed before next step.
Opened popup window (from previous step) will close automatically and new window (next step) will be shown.
Inject JS Tour file on page:

<template id="res_partner_mails_count_assets_backend" name="res_partner_mails_count_assets_backend" i


<xpath expr="." position="inside">
<script src="/res_partner_mails_count/static/src/js/res_partner_mails_count_tour.js" type="te
</xpath>
</template>

Some docs is here (begin from 10 slide): http://www.slideshare.net/openobject/how-to-develop-automated-tests Also


checkout here: https://github.com/odoo/odoo/blob/9.0/addons/web/static/src/js/tour.js
You can launch tour by entering in browser address like this mydatabase/web#/tutorial.mails_count_tour=true where
after tutorial. is id of your tour.

4.5.2 Automatic tour launch


To run tour after module installation do next steps.
Create ToDo
Create Action
ToDo is some queued web actions that may call Action like this:
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_website_tutorial"/>
<field name="state">open</field>
</record>

Action is like this:

4.5. JS Tour

69

Odoo development Documentation, Release master

<record id="res_partner_mails_count_tutorial" model="ir.actions.act_url">


<field name="name">res_partner_mails_count Tutorial</field>
<field name="url">/web#id=3&amp;model=res.partner&amp;/#tutorial_extra.mails_count_tour=true</fie
<field name="target">self</field>
</record>

Here tutorial_extra.**mails_count_tour** is tour id.


Use eval to compute some python code if needed:

<field name="url" eval="'/web?debug=1&amp;res_partner_mails_count=tutorial#id='+str(ref('base.partner

4.6 Preview module on App Store


Browsers dev tools allows to preview Module in App Store before actual uploading.
open https://www.odoo.com/apps
click Inspect Element on some application
change text and images
Done! Now can decide do you need make changes or keep current images and text
Preview image
Base64
Nginx

4.6.1 Preview image


While its easy to change text, its not obvious how to preview image.
Base64
google: convert image to base64
convert image to base64 with a tool you choosed. It must be some long string started with data:image/:
data:image/png;base64,iVBORw0KGgoAAAAcF8RMI3xAAA......AAElFTkSuQmCC

paste this line to src attribute of image tag


BEFORE

70

Chapter 4. User documentation

Odoo development Documentation, Release master

AFTER

4.6. Preview module on App Store

71

Odoo development Documentation, Release master

Nginx
Configure your nginx and use local link in src attribute.:
<img src="static.local/path/to/image.png"/>

You cannot use localhost due to security restrictions. So, you need to add some domain to /etc/hosts::
127.0.0.1

static.local

TODO instruction for nginx

4.7 Image sizes


See also
Preview module on App Store
Adjust chromium window size script

72

Chapter 4. User documentation

Odoo development Documentation, Release master

__openerp__.py -> images


description/index.html

4.7.1 __openerp__.py -> images


This images is displayed on application page (example) and in application list (example )
Displayed size:
app page:
750 x 400

app list:
262,5 x 130

Recommended size (aspect) to fit both usage:


750 x 371

You can scale picture, saving proportion.


Note: Appearance in app list is more important, as there is less chance that user open app page, if small sized image
in app list is not attractive enough.

4.7.2 description/index.html
All values assumed, that you put the code inside .oe_container and .oe_row, e.g.:
<section class="oe_container">
<div class="oe_row oe_spaced">
...
<div class="oe_demo oe_picture oe_screenshot">
<img class="img img-responsive" src="1.png"/>
</div>
...
</div>
</section>

oe_span6 img.oe_demo.oe_picture.oe_screenshot
:: max-width: 362px; max-height: 382px;
img.oe_demo.oe_picture.oe_screenshot
:: max-width: 761px; max-height: 382px;
img.oe_demo.oe_screenshot
:: max-width: 928px;
4.7. Image sizes

73

Odoo development Documentation, Release master

74

Chapter 4. User documentation

CHAPTER 5

Git and Github

5.1 Initial git & github configuration


5.1.1 ssh keys
Configure github ssh keys: https://help.github.com/articles/generating-ssh-keys/

5.1.2 github emails


IT-Projects LLC employees only:
https://github.com/settings/profile
public email should be personal address . . . @it-projects.info
https://github.com/settings/emails
primary email should be personal address . . . @it-projects.info
Keep my email address private should be turned off

5.1.3 git email


Configure email in git. Email must be the same as in github settings:
git config --global user.email "[email protected]"

5.1.4 git editor


git config --global core.editor "nano"

5.1.5 gitignore
Configure global gitignore
Possible content for ~/.gitignore_global:
*~
*.pyc

75

Odoo development Documentation, Release master

5.2 Porting
If you add some feature to one branch and need to add it to anoher branch, then you have to make port.
See also:
Conflicts resolving

5.2.1 Forward-port
Its the simplest case. You merge commits from older branch (e.g. 8.0) to newer branch (e.g. 9.0)
git checkout 9.0
git merge origin/8.0
# [Resolve conflicts if needed]
git push

After git merge you probably need to make some minor changes. In that case just add new commits to newer
branch
git add ...
git commit -m "...."
git push

5.2.2 Back-port
If you need to port new feature from newer branch (e.g. 9.0) to older one (e.g. 8.0), then you have to make back-port.
The problem here is that newer branch has commits which should be applied for newer branch only. That is you cannot
just make git merge 9.0, because it brings 9.0-only commits to 8.0 branch. Possible solutions here are:

5.2.3 git cherry-pick


Apply commits from newer branch (e.g. 9.0) to older branch (e.g. 8.0)
git checkout 8.0
git cherry-pick <commit-1>
# [Resolve conflicts if needed]
git cherry-pick <commit-2>
# [Resolve conflicts if needed]
# ...
git push

Also possible to pick the commit from any remote repository. Add this repository to your remotes. Do fetch from it.
And then cherry-pick.

76

Chapter 5. Git and Github

Odoo development Documentation, Release master

cherry-pick range of commits


The command git cherry-pick A..B applies commits betwwen A and B, but without A (A must be older than
B). To apply inclusive range of commits use format as follows:
git cherry-pick A^..B

For example, to backport this PR https://github.com/it-projects-llc/odoo-saas-tools/pull/286/commits , use command:


git cherry-pick 6ee4fa07d4c0adc837d7061e09da14638d8abf8d^..9133939a25f9e163f52e6662045fc2dc6010ac14

5.3 Conflict resolving


After making git merge or git cherry-pick there could be conflicts, because some commits try to make
changes on the same line. So, you need to choose which change shall be use. It could be one variant, both variants or
new variant.
What to do if you got conflicts:
Check status
git status

Resolve conflicts:
either edit files manually:
* open file with conflicts
* search for <<< or >>> and delete obsolete variant or make a mix of both variants.
or use following commands, if you are sure which version should be kept
git checkout --ours -- <file>
# or
git checkout --theirs -- <file>

Mark files as resolved via git add command


Done.
git push

5.3.1 Deleted files


Sometimes, changes can be conflicted because files are not exist anymore in ours version, but updated in theirs. In
that case execute the code below in order to ignore such changes:
git status | grep 'deleted by us' | awk '{print $5}' | xargs git rm

5.3.2 Notes
Its important, that on resolving conflict stage you should not make any updates inside conflicting lines. You
can only choose which lines should be kept and which deleted. E.g. if you resolve conflicts due to porting some
updatefeature from one odoo version (e.g. 8.0) to another (e.g. 9.0), then such changes some time must be
tuned to make updatefeature work on target odoo version. But you have to make such tuning on a new commit

5.3. Conflict resolving

77

Odoo development Documentation, Release master

only. Make mergingchery-picking commits be only about merging and chery-picking, make porting commits
separately.
If you dont have conflicts, you do not need to make commit after cherry-pick because it creates commit by its
own.

5.4 Multi Pull Request


5.4.1 Find last merged point
To find last commit upstream/8.0 and upstream/9.0 were merged, use following commands
git fetch
git log upstream/8.0..upstream/9.0 --grep="Merge remote-tracking branch 'origin/8.0'" --merges -n 3
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

you will get something like that:


commit 5cb3652be72a05330c3988d270f3aef548511b29
Merge: f1cd564 6cc2562
Author: Ivan Yelizariev <[email protected]>
Date:
Sat Feb 27 16:00:42 2016 +0500
Merge remote-tracking branch 'origin/8.0' into 9.0-dev
commit 14632a790aa01ee2a1ee9fe52152cf2fbfa86423
Merge: 7a48b3a d66ba4f
Author: Ivan Yelizariev <[email protected]>
Date:
Thu Feb 25 11:31:43 2016 +0500
Merge remote-tracking branch 'origin/8.0' into 9.0-dev
commit 6981c245afdccc39b2b49585f8205a784161f9c6
Merge: 22081ed 6eb9f8d
Author: Ivan Yelizariev <[email protected]>
Date:
Fri Feb 19 19:14:15 2016 +0500
Merge remote-tracking branch 'origin/8.0' into 9.0-dev

# take one commit sha from the list and check that it's in origin/9.0.
git branch -r --contains 5cb3652be72a05330c3988d270f3aef548511b29
# possible output:
# upstream/9.0
# origin/9.0-dev
#
#
#
#

if there is not upstream/9.0 in output,


then commit has not been merged yet and you cannot use it
for branch 9.0 use this commit sha 5cb3652be72a05330c3988d270f3aef548511b29
for branch 8.0 need find which of two commits in ``Merge:`` line contains "upstream/8.0"

git branch -r --contains f1cd564


git branch -r --contains 6cc2562
# Use commit sha to create new branches:

78

Chapter 5. Git and Github

Odoo development Documentation, Release master

git checkout -b '9.0-new_branch_name' 5cb3652be72a05330c3988d270f3aef548511b29


git checkout -b '8.0-new_branch_name' 6cc2562

5.5 Cancel lame commit


Imagine you make lame commit. Now to repair things do next:
1. git reset HEAD~1 soft
2. git status
You will see: Your branch is behind origin/8.0 by 1 commit, and can be fast-forwarded. (use git pull to update
your local branch)
3. git add // Add here changed (fixed) files
4. git diff cached //make sure everything is ok.
5. git status
You will see: Your branch is behind origin/8.0 by 1 commit, and can be fast-forwarded. (use git pull to update
your local branch)
6. git commit -mI fixed my mistakes
7. git status
You will see: Your branch and origin/8.0 have diverged, and have 1 and 1 different commit each, respectively. (use
git pull to merge the remote branch into yours)
Now finaly force is with you:
8. git push origin 8.0 -f

5.6 Pull request from console


Yes it possible! Try this manual: https://github.com/github/hub Than in console:
alias git=hub

And pull request:


git pull-request upstream 9.0

Nessesary to add some header for pull request. Save it. If everything is ok you will got link to your pull request.

5.7 Check remote bundings


Check current branch:
git branch -vv

Local branch must be bind to origin. If its no do next:


git push -u origin 9.0-pos_ms

5.5. Cancel lame commit

79

Odoo development Documentation, Release master

5.8 Files relocation


git format-patch
git filter-branch

5.8.1 git format-patch


This section is based on OCAs instruction.
Used variabes:
$REPO_PATH, $REPO_NAME - source repository
$MODULE - the name of the module you want to move
$BRANCH - the branch of the $REPO with $MODULE
$DEST_REPO_PATH, $DEST_REPO_NAME - target repository

# Set variables
export REPO_PATH=/path/to/misc-addons REPO_NAME=misc-addons MODULE=some_module BRANCH=10.0 DEST_REPO_
# Create patch
cd $REPO_PATH
git fetch upstream
git format-patch --stdout --root upstream/$BRANCH -- $MODULE > /tmp/relocation.patch
# Remove module from source repository
git checkout -b $BRANCH-$MODULE-relocation-remove upstream/$BRANCH
git rm -r $MODULE
git commit -m "[REM] $MODULE is relocated to $DEST_REPO_NAME"
git push origin
# then create PR on github
# Add commits to target repository
cd $DEST_REPO_PATH
git fetch upstream
git checkout -b $BRANCH-$MODULE-relocation-add upstream/$BRANCH
git am -3 < /tmp/relocation.patch
git push origin
# then create PR on github

5.8.2 git filter-branch


This section is based on http://gbayer.com/development/moving-files-from-one-git-repository-to-another-preservinghistory/
Goal:
Move directory 1 from Git repository A to Git repository B.
Constraints:
Git repository A contains other directories that we dont want to move.
Wed like to perserve the Git commit history for the directory we are moving.

80

Chapter 5. Git and Github

Odoo development Documentation, Release master

Lets start
$REPO: the repository hosting the module (e.g. misc-addons)
$DEST_REPO: the repository you want to move the module to (e.g. access-addons)
$MODULE: the name of the module you want to move (e.g. group_menu_no_access)
$BRANCH: the branch of the $REPO with $MODULE (source branch, e.g. 8.0)
Warning: If you have installed git from official ubuntu 14.04 deb repository then you should first update it. You
can update git using this instruction Update git
$
$
$
$
$
$
$
$
$
$
$
$
$

cd ~
git clone https://github.com/it-projects-llc/$REPO -b $BRANCH
cd $REPO
git remote rm origin
git filter-branch --subdirectory-filter $MODULE -- --all
mkdir $MODULE
mv * $MODULE # never mind the "mv: cannot move..." warning message
git add .
git commit -m "[MOV] $MODULE: ready"
cd ~
cd $DEST_REPO
git remote add $MODULE-hosting-remote ~/$REPO
git pull $MODULE-hosting-remote $BRANCH

After the last command you will have the module with all its commits in your destination repo. Now you can push it
on github etc. You can remove ~/$REPO folder - no use of it now.
Warning: Cloning - this is required step. It is temporary directory. It will removed all modules except the one
that you want to move.
The following script may come in handy if you need to move several modules. But be sure that you understand all its
commands before using.
#!/bin/bash
source_repo=$PWD
echo $source_repo
if [ -n "$1" ]
then
module=$1
echo $module
else
echo "Must be module name"
exit $E_WRONGARGS
fi

if [ -n "$2" ]
then
dest_repo=$2
echo $dest_repo
else
echo "Must be dest_repo"
exit $E_WRONGARGS
fi

5.8. Files relocation

81

Odoo development Documentation, Release master

if [ -n "$3" ]
then
branch=$3
echo $branch
else
echo "Must be branch specified"
exit $E_WRONGARGS
fi
cp -r $source_repo ../$module
cd ../$module
git remote rm origin
git filter-branch --subdirectory-filter $module -- --all
mkdir $module
mv * $module
git add .
git commit -m "[MOV] module -- $module"
cd $dest_repo
git remote add repo_moved_module $source_repo/../$module
git pull repo_moved_module $branch --no-edit
git remote rm repo_moved_module
rm -rf $source_repo/../$module

In order to use it you should make the movemodule.sh file in your home directory and put all lines above there and
make this file executable.
$ cd ~
$ chmod +x movemodule.sh

To do the moving of group_menu_no_access from addons-yelizariev to access-addons with the movemodule.sh take
the following steps.
$ cd ~
$ git clone https://github.com/yelizariev/addons-yelizariev.git
$ cd addons-yelizariev

This part is the same as moving without the script. But then I type only one command instead of many in case of fully
manual approach.
addons-yelizarie$ ~/movemodule.sh group_menu_no_access ~/access-addons 8.0

5.9 Commit comment prefix


Based on: https://www.odoo.com/documentation/8.0/reference/guidelines.html
[DOC] for documentation. Dont use any other tags when you improve, fix, refactor documentation
[PORT] for porting
[IMP] for improvements
[FIX] for bug fixes
[REF] for refactoring
[ADD] for adding new resources (new modules or files).
[REM] for removing of resources

82

Chapter 5. Git and Github

Odoo development Documentation, Release master

[REL] for releases


[CI] for updating .travis.yml, requirements.txt, */tests/*, etc. files
[LINT] for fixing lint errors
[i18n] for translations

5.9.1 Forbidden
Dont use tags below
[WIP], [DEV] instead of noting that work in progress make message as if your work is already done.

5.10 Git stash


book: https://git-scm.com/book/no-nb/v1/Git-Tools-Stashing
man: https://git-scm.com/docs/git-stash

5.11 Update Git


Ubuntu 14.04 official deb repository has 1.9 version of Git. It is too old and have to be updated.
http://askubuntu.com/questions/579589/upgrade-git-version-on-ubuntu-14-04
sudo apt-get remove git
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
git --version

5.12 Squash commits into one


5.12.1 Backup
Before making a squash consider to backup your commits.
Local backup:
git tag 9.0-new-module-backup

Remote backup
git push origin 9.0-new-module:9.0-new-module-backup

To restore original state you can use following command:


# be sure that you on the branch you are going to change
git status
# restore from tag
git rebase 9.0-new-module-backup -X theirs

5.10. Git stash

83

Odoo development Documentation, Release master

# restore from remote branchtag


git rebase origin/9.0-new-module-backup -X theirs

5.12.2 git commit --amend


Instead of creating new commit, adds updates to the latest commit.

5.12.3 git rebase -i


Interactive squashing
git rebase -i <your-first-commit>^
# e.g.
git rebase -i 7801c8b^

Then edit opened file and keep pick for the first commit and and replace pick with squash for the rest ones. E.g.
Origin:
TODO

Edited:
TODO

Warning: If you remove a line here THAT COMMIT WILL BE LOST.

5.12.4 Push
git push -f origin 9.0-new-module

84

Chapter 5. Git and Github

CHAPTER 6

Continuous Integration

6.1 Runbot
runbot.odoo.com
How to use runbot.odoo.com?
runbot.it-projects.info
How to deploy runbot?

6.1.1 runbot.odoo.com
http://runbot.odoo.com/ official runbot. While its main purpose is checking pull requests to official repository, it is
usefull on daily development routine.
It allows to play with any odoo version. Each build has all modules installed with demo data.
It allows to quickly try enterprise odoo versions
How to use runbot.odoo.com?
open http://runbot.odoo.com/runbot/
switch to repository you need. Odoo community (odoo/odoo) is default.
find a row with odoo version you need (10.0, 9.0, 8.0, 7.0)
click on fast forward icon to open latest build. Alternatively, click on any blue button on a row, that corresponds
to odoo version you need.
on login page enter credentials:
Admin
* login: admin
* password: admin
Demo
* login: demo
* password: demo

85

Odoo development Documentation, Release master

6.1.2 runbot.it-projects.info
http://runbot.it-projects.info/ customized runbot for IT-Projects repositories.
Stages of making a build:
Checkout sources from github
-base database: install updated modules for pull request builds and base modules for branch builds. For some
repositories explicit modules (i.e. ones, that are specified in runbot settings) are installed too
-all database: install all modules of the repo
run the build with two prepared databases
Main features:
Blue button - enter to -all database
Green button - enter to -base database
Key logs (shown on build page) key logs, warnings and errors
Detailed logs (txt files)
Full base logs full logs of installation process in -base database
Full all logs full logs of installation process in -all database
Full run logs full logs for both databases after running, i.e. when Blue and Green button are available.
Logs includes cron work, url requests etc

86

Chapter 6. Continuous Integration

Odoo development Documentation, Release master

6.1.3 How to deploy runbot?


There is docker that allows you deploy you own runbot for your repositores. Check it out for further information
https://github.com/it-projects-llc/odoo-runbot-docker

6.2 Odoo Travis Tests


TODO

6.3 Coverage

6.2. Odoo Travis Tests

87

Odoo development Documentation, Release master

88

Chapter 6. Continuous Integration

CHAPTER 7

Odoo

7.1 Models
Section helps in understanding built-in models

7.1.1 ir.config_parameter
Add record by module
XML: <record>

Code:
<data noupdate="1">
<record id="myid" model="ir.config_parameter">
<field name="key">mymodule.mykey</field>
<field name="value">True</value>
</record>

Prons:
record is deleted on uninstalling
Cons:
it raises error, if record with that key is already created manulally
XML: <function>

Code:

<function model="ir.config_parameter" name="set_param" eval="('auth_signup.allow_uninvited', True)" /

Prons:
it doesnt raise error, if record with that key is already created manulally
Cons:
record is not deleted on uninstalling
value is overwrited after each module updating

89

Odoo development Documentation, Release master

YML

Code:
!python {model: ir.config_parameter}: |
SUPERUSER_ID = 1
if not self.get_param(cr, SUPERUSER_ID, "ir_attachment.location"):
self.set_param(cr, SUPERUSER_ID, "ir_attachment.location", "
postgresql:lobject")

Prons:
value is not overwrited if it already exists
Cons:
record is not deleted on uninstalling

7.1.2 res.users
TODO

7.1.3 res.groups
TODO

7.1.4 ir.model.access
Defines access to a whole model.
Each access control has a model to which it grants permissions, the permissions it grants and optionally a group.
Access controls are additive, for a given model a user has access all permissions granted to any of its groups: if the
user belongs to one group which allows writing and another which allows deleting, they can both write and delete.
If no group is specified, the access control applies to all users, otherwise it only applies to the members of the given
group.
Available permissions are creation (perm_create), searching and reading (perm_read), updating existing records
(perm_write) and deleting existing records (perm_unlink)
See also:
Superuser rights
ir.rule
Fields

name = fields.Char(required=True, index=True)


active = fields.Boolean(default=True, help='If you uncheck the active field, it will disable the ACL
model_id = fields.Many2one('ir.model', string='Object', required=True, domain=[('transient', '=', Fal
group_id = fields.Many2one('res.groups', string='Group', ondelete='cascade', index=True)
perm_read = fields.Boolean(string='Read Access')
perm_write = fields.Boolean(string='Write Access')

90

Chapter 7. Odoo

Odoo development Documentation, Release master

perm_create = fields.Boolean(string='Create Access')


perm_unlink = fields.Boolean(string='Delete Access')

7.1.5 ir.rule
Record rules are conditions that records must satisfy for an operation (create, read, write or delete) to be allowed.
Example of a condition: User can update Task that assigned to him.
Group field defines for which group rule is applied. If Group is not specified, then rule is global and applied for all
users.
Domain field defines conditions for records.
Boolean fields (read, write, create, delete) of ir.rule mean Apply this rule for this kind of operation. They do not mean
restrict access for this kind of operation.
Checking access algorithm
To check either user has access for example to read a record, system do as following:
Check access according to ir.model.access records. If it doesnt pass, then user doesnt get access
Find and check global rules for the model and for read operation
if the record doesnt satisfy (doesnt fit to domain) for at least one of the global rules, then user doesnt
get access
Find and check non-global rules for the model and for read operation.
if there are no such groups, then user get access
if the record satisfy (fit to domain) for at least one of the non-global rules, then user get access
See also:
Superuser rights
Fields

name = fields.Char(index=True)
active = fields.Boolean(default=True, help="If you uncheck the active field, it will disable the reco
model_id = fields.Many2one('ir.model', string='Object', index=True, required=True, ondelete="cascade"
groups = fields.Many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id')
domain_force = fields.Text(string='Domain')
domain = fields.Binary(compute='_force_domain', string='Domain')
perm_read = fields.Boolean(string='Apply for Read', default=True)
perm_write = fields.Boolean(string='Apply for Write', default=True)
perm_create = fields.Boolean(string='Apply for Create', default=True)
perm_unlink = fields.Boolean(string='Apply for Delete', default=True)

7.1.6 product.template
The stores have products that differ from some other only a one or few properties. Such goods it makes no sense to
separate as individual products. They are join in a group of similar goods, which are called template.
shop: product pages use product.template (when order is created, then product.product is used).

7.1. Models

91

Odoo development Documentation, Release master

7.1.7 product.product
The product, unlike the template, it is a separate product that can be calculated, set the price, to assign a discount.
product.product is used:
sale.order
stock
pos

7.1.8 ir.actions.todo
The model is used for executing actions (records in the ir.actions.act_window model). The model allows to set
conditions and sequence of appearance of wizards. Also you can specify a regular interface window but only as last
action. Code:
<record id="sce.initial_setup" model="ir.actions.todo">
<field name="action_id" ref="action_initial_setup"/>
<field name="state">open</field>
<field name="sequence">1</field>
<field name="type">automatic</field>
</record>

The startup type can be one of the following:


manual: Launched manually.
automatic: Runs whenever the system is reconfigured. The launch takes place either after install/upgrade any
module or after calling the execute method in the res.config model.
once: After having been launched manually, it sets automatically to Done.

7.1.9 bus.bus
Bus
Bus is a module for instant notifications via longpolling. Add it to dependencies list:
'depends': ['bus']

Note: Mail module in odoo 9.0 is already depended on module bus.


Warning: Dont mistake longpolling bus with core.bus which is client-side only and part of web module.

What is longpolling
About longpolling
How to enable Longpolling in odoo

92

Chapter 7. Odoo

Odoo development Documentation, Release master

How to implement longpolling

Scheme of work
Channel identifier
Listened channels
Binding notification event
Start polling
Sending notification
Handling notifications

Scheme of work

Specify channels that current client is listening


Bind notification event to your handler
Start polling
Send notification to some channel via python code
Channel identifier

Channel identifier - is a way to distinguish one channel from another. In the main, channel contains dbname, some
string and some id.
Added via js identifiers can be string only.
var channel = JSON.stringify([dbname, 'model.name', uid]);

Added via python identifiers can be a string or any data structure.


# tuple
channel = (request.db, 'model.name', request.uid)
# or a string
channel = '["%s","%s","%s"]' % (request.db, 'model.name', request.uid)

Warning: JSON.stringify in js and json.dumps in python could give a different result.

Listened channels

You can add channels in two ways: either on the server side via _poll function in bus controller or in js file using the
method bus.add_channel().
With controllers:
# In odoo 8.0:
import openerp.addons.bus.bus.Controller as BusController
# In odoo 9.0:
import openerp.addons.bus.controllers.main.BusController
class Controller(BusController):
def _poll(self, dbname, channels, last, options):

7.1. Models

93

Odoo development Documentation, Release master

if request.session.uid:
registry, cr, uid, context = request.registry, request.cr, request.session.uid, request.c
new_channel = (request.db, 'module.name', request.uid)
channels.append(new_channel)
return super(Controller, self)._poll(dbname, channels, last, options)

In the js file:
// 8.0
var bus = openerp.bus.bus;
// 9.0+
var bus = require('bus.bus').bus;
var channel = JSON.stringify([dbname, 'model.name', uid]);
bus.add_channel(new_channel);

Binding notification event

In js file:
bus.on("notification", this, this.on_notification);

Start polling

In js file:
bus.start_polling();

Note: You dont need to call bus.start_polling(); if it was already started by other module.
When polling starts, request /longpolling/poll is sent, so you can find and check it via Network tool in your
browser
Sending notification

You can send notification only through a python. If you need to do it through the client send a signal to server in a
usual way first (e.g. via controllers).
self.env['bus.bus'].sendmany([(channel1, message1), (channel2, message2), ...])
# or
self.env['bus.bus'].sendone(channel, message)

Handling notifications
on_notification: function (notifications) {
// Old versions passes single notification item here. Convert it to the latest format.
if (typeof notification[0][0] === 'string') {
notification = [notification]
}
for (var i = 0; i < notification.length; i++) {
var channel = notification[i][0];

94

Chapter 7. Odoo

Odoo development Documentation, Release master

var message = notification[i][1];


// proceed a message as you need
// ...
}
},

Examples
pos_multi_session:
add channel (python)
bind event
send notification
chess:
add channel (js)
bind event
send notification
mail_move_message:
add channel (python)
bind event
send notification

7.1.10 ir.cron
Creating automated actions in Odoo
Schedulers are automated actions that run automatically over a time period and can do a lot of things. They give the
ability to execute actions database without needing manual interaction. Odoo makes running a background job easy:
simply insert a record to ir.cron table and Odoo will execute it as defined.
1. Creating the model and method of this model.
class model_name(models.Model):
_name = "model.name"
# fields
def method_name(self, cr, uid, context=None): # method of this model
# your code

2. Creating the automated action


If you want to build new modules in the guidelines from Odoo you should add the code for an automated
action under yourDefaultModule/data/ in a separate XML file.
An important thing to note with automated actions is that they should always be defined within a noupdate field since
this shouldnt be updated when you update your module.
<openerp>
<data noupdate="1">
<record id="unique_name" model="ir.cron">
<field name="name">Name </field>

7.1. Models

95

Odoo development Documentation, Release master

<field name="active" eval="True" />


<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doal">1</field>
<!--<field name="nextcall" >2016-12-31 23:59:59</field>-->
<field name="model" eval="'model.name '" />
<field name="function" eval="'method_name '" />
<field name="args" eval="" />
<!--<field name="priority" eval="5" />-->
</record>
</data>
</openerp>

The first thing you notice is the data noupdate="1", this is telling Odoo that all code within this tag shouldnt be
updated when you update your module.
<record id="unique_name" model="ir.cron">

The id is an unique identifier for Odoo to know what record is linked to which id. The model called (ir.cron) is the
model specifically made by Odoo for all automated actions. This model contains all automated actions and should
always be specified.
<field name="name">Name </field>

The next line is the name.


<field name="active" eval="True" />

Boolean value indicating whether the cron job is active or not.


<field name="user_id" ref="base.user_root"/>

This user id is referring to a specific user, in most cases this will be base.user_root.
<field name="interval_number">1</field>

Number of times the scheduler is to be called based on the interval_type


<field name="interval_type">days</field>

Interval Unit.
It should be one value for the list: minutes, hours, days, weeks, months.
<field name="numbercall">-1</field>

An integer value specifying how many times the job is executed. A negative value means no limit.
<field name="doal">1</field>

A boolean value indicating whether missed occurrences should be executed when the server restarts.
<field name="nextcall" >2016-12-31 23:59:59</field> <!-- notice the date/time format -->

Next planned execution date for this job.


<field name="model" eval="'model.name '" />

The field model specifies on which model the automated action should be called.

96

Chapter 7. Odoo

Odoo development Documentation, Release master

<field name="function" eval="'method_name '" />

Name of the method to be called when this job is processed.


<field name="args" eval="" />

The arguments to be passed to the method.


<field name="priority" eval="5" />

The priority of the job, as an integer: 0 means higher priority, 10 means lower priority.
Defaults.
Name
nextcall
priority
user_id
interval_number
interval_type
numbercall
active
doall

Definition
lambda *a: time.strftime(DEFAULT_SERVER_DATETIME_FORMAT
5
lambda obj,cr,uid,context: uid
1
months
1
1
1

7.1.11 mail.message
Message Subtypes in Odoo
Most of the time in Odoo multiple users work upon one particular record or document like sale order,Invoice ,Tasks
etc. In such scenarios,it becomes extremely important to track changes done by every individual against that document.
It helps management to find any possible reason in case of any issue occurs. Odoo provides this feature to great extent
with the help of OpenChatter Integration.
Consider a scenario where multiple users are working in a single project.Various parameters for that project are already
configured like deadline,Initially Planned Hours etc. Now one of the user changes the value of Planned Hours. So
now it is important to know which user has changed it and what was the previous value. We can track it by creating
message subtypes in Odoo as following.
It needs to be defined in XML which will have following syntax.
<record id="mt_task_planned_hours" model="mail.message.subtype">
<field name="name">Task planned hours changed</field>
<field name="res_model">project.task</field>
<field name="default" eval="True"/>
<field name="description">Task planned hours changed</field>
</record>

Users can also have a mail.message.subtype that depends on an other to act through a relation field. For the planned
hours, we can have following sysntax for it.
<record id="mt_task_planned_hours_change" model="mail.message.subtype">
<field name="name">Task planned hours changed</field>
<field name="sequence">10</field>
<field name="res_model">project.task</field>
<field name="parent_id" eval="ref('mt_task_planned_hours')"/>
<field name="relation_field">project_id</field>
</record>

7.1. Models

97

Odoo development Documentation, Release master

Odoo provide feature to track various events related with one particular document with the help of _track attribute. If
we inherit mail.thread object then with the help of _track attribute, user can sent notification also to keep aware others
about changes happening against this particular document. The syntax can be as follow.
_track = {
'planned_hours': {
'module_name.mt_task_planned_hours': lambda self, cr, uid, obj, ctx=None: obj.planned_hours,
},

In order to track changes related with any field,Odoo provides an attribute named as track_visibility.It has to be defined
at field level which has below syntax.

planned_hours = fields.Float(string = 'Initially Planned Hours', track_visibility='onchange', help='E

Hence, it is easy to track the changes done so far against any particular document by different users.

7.2 How to use Odoo


7.2.1 How to create database
From UI
To create new database open /web/database/manager
8.0-

Database with dots Early version of odoo doesnt allow to create databases with dots. You can remove this restriction in two ways:
1. Updates sources:
cd path/to/odoo
sed -i 's/matches="[^"]*"//g' addons/web/static/src/xml/base.xml

2. update html code via Inspect Element tool


You must remove the matches field value.

98

Chapter 7. Odoo

Odoo development Documentation, Release master

From terminal
9.0+

To create new database simple add -d parameter when you run odoo, e.g.:
./openerp-server -d database1

will create new database with name database1

7.2.2 How to enable Technical Features


8.0
open Settings / Users / Users
select your user
click [Edit]
switch Technical Features on
click [Save]
refresh web page (click F5)
9.0+
Since Odoo 9.0 to enable Technical Features you only need to activate developer mode.

7.2.3 How to install/update module


There are several ways to installupdate module
From App store (install)
8.0
9.0+
From App store (update)
8.0
9.0+
From zip archive
From addons path on server

From App store (install)


8.0

navigate to Settings / Modules / Apps


remove Featured [x] filter
search module you need

7.2. How to use Odoo

99

Odoo development Documentation, Release master

click [Install]
9.0+

activate developer mode.


navigate to Apps menu
click on second Apps menu in left side bar
remove Featured [x] filter
search module you need
click [Install]
From App store (update)
8.0

navigate to Settings / Modules / Updates


9.0+

navigate to Apps menu


click Updates menu in left side bar
From zip archive
TODO
From addons path on server
TODO

7.2.4 How to activate developer mode


To activate developer mode, you need to add debug parameter to your url, e.g.:
localhost:8069/web?debug=1

Also, you can use UI to do it


click button at top right-hand corner <User Name> -> About Odoo
click Activate the developer mode

100

Chapter 7. Odoo

Odoo development Documentation, Release master

7.2. How to use Odoo

101

Odoo development Documentation, Release master

102

Chapter 7. Odoo

CHAPTER 8

Odoo administration

Official
docs:
*
https://www.odoo.com/documentation/8.0/setup/install.html
https://www.odoo.com/documentation/8.0/setup/deploy.html

8.1 Odoo installation


Contents
Odoo installation
Local installation
* nginx_odoo.conf
Production installation

8.1.1 Local installation


sudo apt-get update
sudo apt-get install git python-pip htop moreutils tree nginx gimp wmctrl postgresql-server-dev-all
sudo apt-get upgrade
################### Github
# configure ssh keys: https://help.github.com/articles/generating-ssh-keys/
################### Odoo
# download odoo from git:
cd /some/dir/
git clone https://github.com/odoo/odoo.git
# install dependencies:
wget http://nightly.odoo.com/9.0/nightly/deb/odoo_9.0.latest_all.deb
sudo dpkg -i odoo_9.0.latest_all.deb # shows errors -- just ignore them and execute next command:
sudo apt-get -f install
sudo apt-get remove odoo
# install wkhtmltox
cd /usr/local/src
lsb_release -a
uname -i
# check version of your OS and download appropriate package

103

Odoo development Documentation, Release master

# http://wkhtmltopdf.org/downloads.html
# e.g.
apt-get install xfonts-base xfonts-75dpi
apt-get -f install
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-trusty-amd64.deb
dpkg -i wkhtmltox-*.deb
# requirements.txt
cd /path/to/odoo
sudo pip install -r requirements.txt
sudo pip install watchdog
# fix error with jpeg (if you get it)
# uninstall PIL
sudo pip uninstall PIL
# install libjpeg-dev with apt
sudo apt-get install libjpeg-dev
# reinstall pillow
pip install -I pillow
# (from here https://github.com/odoo/odoo/issues/612 )
# fix issue with lessc
# install Less CSS via nodejs according to this instruction:
# https://www.odoo.com/documentation/8.0/setup/install.html
# create postgres user:
sudo su - postgres -c "createuser -s $USER"
# Create new config file if you don't have it yet:
cd /path/to/odoo
./openerp-server --save
# then edit it, e.g. via emacs
emacs -nw ~/.openerp_serverrc
# set dbfilter = ^%h$
# set workers = 2 # to make longpolling\bus\im work
# create different versions of conf file:
cp ~/.openerp_serverrc ~/.openerp_serverrc-9
cp ~/.openerp_serverrc ~/.openerp_serverrc-8

################### /etc/hosts
# /etc/hosts must contains domains you use, e.g:
sudo bash -c "echo '127.0.0.1 8_0-project1.local'
sudo bash -c "echo '127.0.0.1 8_0-project2.local'
sudo bash -c "echo '127.0.0.1 9_0-project1.local'

>> /etc/hosts"
>> /etc/hosts"
>> /etc/hosts"

################### nginx
# put nginx_odoo.conf to /etc/nginx/sites-enabled/
# delete default configuration:
cd /etc/nginx/sites-enabled/
rm default
# restart nginx
sudo /etc/init.d/nginx restart
################### run Odoo
cd /path/to/odoo

104

Chapter 8. Odoo administration

Odoo development Documentation, Release master

git checkout somebranch-or-revision


git tag 8_0-honduras.local
# everytime run odoo this way:
git checkout 8_0-client1.local && ./odoo.py --config=/path/to/.openerp_serverrc-8
# or
git checkout 8_0-project1.local && ./odoo.py --config=/path/to/.openerp_serverrc-8 --auto-reload
# or
git checkout 9_0-project1.local && ./odoo.py --config=/path/to/.openerp_serverrc-9 --dev
# etc.
# then open database you need, e.g. (type http:// explicitly, because browser could understand it as
# http://8_0-client1.local/
# (database name should be 8_0-client1.local )

nginx_odoo.conf
server {
listen 80 default_server;
server_name .local;
proxy_buffers 16 64k;
proxy_buffer_size 128k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
#proxy_redirect http:// https://;
proxy_read_timeout 600s;
client_max_body_size 100m;
location /longpolling {
proxy_pass http://127.0.0.1:8072;
}
location / {
proxy_pass http://127.0.0.1:8069;
}
}

8.1.2 Production installation


https://github.com/yelizariev/install-odoo

8.2 Longpolling
Longpolling is a way to deliver instant notification to web client (e.g. in chats).
To activate longpolling:
install gevent and psycogreen
python -c "import gevent" || sudo pip install gevent
python -c "import psycogreen" || sudo pip install psycogreen

set non-zero value for workers parameter


8.2. Longpolling

105

Odoo development Documentation, Release master

configure nginx
location /longpolling {
proxy_pass http://127.0.0.1:8072;
}
location / {
proxy_pass http://127.0.0.1:8069;
}

if you install odoo 9.0 via deb package, then you have to restore openerp-gevent file (see #10207):
cd /usr/bin/
wget https://raw.githubusercontent.com/odoo/odoo/9.0/openerp-gevent
chmod +x openerp-gevent

Read more about longpolling

8.3 About longpolling


What is HTTP Long Polling?
Web applications were originally developed around a client/server model, where the Web client is always the initiator
of transactions, requesting data from the server. Thus, there was no mechanism for the server to independently send,
or push, data to the client without the client first making a request.
In a Nutshell: HTTP Long Polling
To overcome this deficiency, Web app developers can implement a technique called HTTP long polling, where the
client polls the server requesting new information. The server holds the request open until new data is available.
Once available, the server responds and sends the new information. When the client receives the new information, it
immediately sends another request, and the operation is repeated. This effectively emulates a server push feature.
Thus, each data packet means new connection which will remain open until the server sends the information.
In practice the connection usually reinstalls once per 20-30 seconds to get rid of possible problems (mistakes) , e.g.
problems connected with HTTP-proxy.
In contradiction to usual polling, such notice appears faster.
Delay = connection installing + data transfer
Advantages of longpolling
The loading to the server is reduced unlike usual polling
Reduced traffic
Supporting in all modern browsers
Thus, longpolling helps the client to receive data as soon as they appear in the server in contrast to periodic, which
send requests according to interval specified.

8.4 --workers
Non-zero values for --workers activates Multiprocessing.
Multiprocessing increases stability, makes somewhat better use of computing resources and can be better monitored
and resource-restricted.

106

Chapter 8. Odoo administration

Odoo development Documentation, Release master

Number of workers should be based on the number of cores in the machine (possibly with some room for cron
workers depending on how much cron work is predicted)
Worker limits can be configured based on the hardware configuration to avoid resources exhaustion
Warning: multiprocessing mode currently isnt available on Windows

8.4.1 Longpolling
Hidden feature of Multiprocessing is automatic run gevent process for longpolling support.
Longpolling is an extra proccess, i.e. if you have --workers=2 then you will get 2 worker processes and 1 gevent
process

8.5 --addons-path
8.5.1 Duplicate addons
If you have two folder with the same module and you have reason to add both folders to addons_path, then first
found version of the module will be used. That is folder in the begging of addons_path list has more priority.

8.6 --log-handler
--log-handler=PREFIX:LEVEL

Setups a handler at LEVEL for a given PREFIX. This option can be repeated.
For example, if you want to have DEBUG level for module telegram only, you can run it with parameter:
--log-handler=openerp.addons.telegram:DEBUG

To disable werkzeug logs add following parameter:


--log-handler=werkzeug:CRITICAL

8.6.1 Log levels


CRITICAL
ERROR
WARNING
INFO
DEBUG
NOTSET

8.5. --addons-path

107

Odoo development Documentation, Release master

108

Chapter 8. Odoo administration

CHAPTER 9

Continuous Delivery

TODO

109

Odoo development Documentation, Release master

110

Chapter 9. Continuous Delivery

CHAPTER 10

IDE

10.1 Emacs
10.1.1 Emacs
Install emacs 24.4+ http://askubuntu.com/questions/437255/how-to-install-emacs-24-4-on-ubuntu
Open Emacs
Press Alt-x package-list-packages
install packages: click i and then x
some packages require dependencies, that have to be installed via terminal * flymake * loccur * flymake-css *
flymake-jslint * flymake-python-pyflakes
sudo pip install flake8

magit
js3-mode

10.1.2 Spacemacs
Requirements
emacs version 24 or newer.
Installation
Install spacemacs from github https://github.com/syl20bnr/spacemacs
Documentation
http://spacemacs.org/doc/DOCUMENTATION.html

111

Odoo development Documentation, Release master

Layers for Odoo development


Use the following layers:
auto-completion
better-defaults
emacs-lisp
git
syntax-checking
version-control
pyhton
eyebrowse
sql
python
semantic
Syntax-checking in python uses pylint package (http://liuluheng.github.io/wiki/public_html/Python/flycheck-pylintemacs-with-python.html). Install it by
sudo pip install pylint

10.1.3 Replace text in recursively found files


1. M-x find-name-dired: you will be prompted for a root directory and a filename pattern.
2. Press t to toggle mark for all files found.
3. Press Q for Query-Replace in Files...: you will be prompted for query/substitution regexps.
4. Proceed as with query-replace-regexp: SPACE to replace and move to next match, n to skip a match,
etc.
Based on: http://stackoverflow.com/questions/270930/using-emacs-to-recursively-find-and-replace-in-text-files-notalready-open

10.1.4 Pylint
Pylint is a tool that checks for errors in Python code, tries to enforce a coding standard and looks for code smells. It
can also look for certain type errors, it can recommend suggestions about how particular blocks can be refactored and
can offer you details about the codes complexity. https://pylint.readthedocs.io/en/latest/
Install pylint.
sudo pip install pylint

With the Flycheck emacs extension, pylints output will be shown right inside your emacs buffers. Spacemacs has
flycheck in his syntax-checking layer.
M-x package-install RET flycheck

Configure pylint by using a pylintrc file.

112

Chapter 10. IDE

Odoo development Documentation, Release master

pylint --generate-rcfile >.pylintrc

Pylint Odoo plugin


Install pylint odoo plugin https://github.com/OCA/pylint-odoo
pip install --upgrade git+https://github.com/oca/pylint-odoo.git
or
pip install --upgrade --pre pylint-odoo

Add the plugin in pylintrc.


load-plugins=pylint_odoo

Useful configurations
By default there is 100 characters per line allowed. Allow 120 characters
max-line-length=120

To disable certain warning add its code to disable list in pylintrc. For example, If you dont like this message Missing method docstring with code C0111 or this Use of super on an old style class
(E1002)

disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W16

You can find other codes here: http://pylint-messages.wikidot.com/


Flychek highlights odoo import lines as from openerp import models, fields, api
with error message F0401: Unable to import....
There are two options to fix it http://stackoverflow.com/questions/1899436/pylint-unable-to-import-error-how-to-set-pythonpath.
Edit pylintrc to include your odoo directory like this:
init-hook='import sys; sys.path.append("/path/to/odoo")'

10.2 PyCharm
10.2.1 PyCharm
Remote access with pgAdmin to Odoo postgre database on Ubuntu
This is for PgAdmin integration, but same method working with PyCharm.
STEP #1 get pgAdmin Install pgAdmin from pgadmin.org
STEP #2 allow postgre server remote connections from everywhere Open etc/postgresql/9.x/main/pg_hba.conf and
add following line: host all all all md5
STEP #3 let the postgre server listen to everyone Open etc/postgresql/9.x/main/postgresql.conf and change following
line: listen_addresses = *

10.2. PyCharm

113

Odoo development Documentation, Release master

STEP #4 give the user postgres a password Start the psql terminal: sudo -u postgres psql Give a password: ALTER
USER postgres PASSWORD yourpassword; Leave the psql terminal: q
STEP #5 Restart postgre server by executing this terminal command: sudo /etc/init.d/postgresql restart
STEP #6 Start pgAdmin and add a connection to a server like this:

You are ready!


Original:
http://odoo.guide/remote-access-with-pgadmin-to-odoo-postgre-database-on-ubuntu/

114

Chapter 10. IDE

Odoo development Documentation, Release master

10.3 Tmux
10.3.1 Tmux installation
Install Tmux
sudo apt-get install tmux

Check version
tmux -V

If you have 1.8 or older then you should update. Here are update commands for ubuntu 14.04
sudo
sudo
sudo
sudo
sudo

apt-get update
apt-get install -y python-software-properties software-properties-common
add-apt-repository -y ppa:pi-rho/dev
apt-get update
apt-get install -y tmux=2.0-1~ppa1~t

Now if you do tmux -V it should show tmux 2.0 which is a good version for tmux plugins.
Based on: http://stackoverflow.com/questions/25940944/ugrade-tmux-from-1-8-to-1-9-on-ubuntu-14-04
Install Tmux Plugin Manager
Requirements: tmux version 1.9 (or higher), git, bash
Clone TPM:
$ git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

Put this at the bottom of .tmux.conf:


# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
#
#
#
#

Other examples:
set -g @plugin 'github_username/plugin_name'
set -g @plugin '[email protected]/user/plugin'
set -g @plugin '[email protected]/user/plugin'

# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

Reload TMUX environment so TPM is sourced:


# type this in terminal
$ tmux source ~/.tmux.conf

Based on: https://github.com/tmux-plugins/tpm


Install Tmux Resurrect
Add plugin to the list of TPM plugins in .tmux.conf:

10.3. Tmux

115

Odoo development Documentation, Release master

set -g @plugin 'tmux-plugins/tmux-resurrect'

Hit prefix + I to fetch the plugin and source it. You should now be able to use the plugin.
Based on: https://github.com/tmux-plugins/tmux-resurrect
Install tmux-continuum
Last saved environment is automatically restored when tmux is started. Put the following lines in tmux.conf:
set -g @continuum-save-interval '5'
set -g @continuum-restore 'on'

Your environment will be automatically saved every 5 minutes. When you start tmux it will automatically restore
Based on: https://github.com/tmux-plugins/tmux-continuum

10.3.2 Tmux configuration


Create a file with the name .tmux.conf in your home directory.
An example of .tmux.conf:
# Global settings
# Set prefix key to Ctrl-a
# unbind-key C-b
# set-option -g prefix C-a
# send the prefix to client inside window
# bind-key C-a send-prefix
# scrollback buffer n lines
set -g history-limit 10000
# tell tmux to use 256 colour terminal
set -g default-terminal "screen-256color"
# enable wm window titles
set -g set-titles on
# reload settings
bind-key R source-file ~/.tmux.conf
# Statusbar settings
# toggle statusbar
bind-key s set status
# use vi-style key bindings in the status line
set -g status-keys vi
# amount of time for which status line messages and other indicators
# are displayed. time is in milliseconds.
set -g display-time 2000
# default statusbar colors

116

Chapter 10. IDE

Odoo development Documentation, Release master

set -g status-fg white


set -g status-bg default
set -g status-attr default
# default window title colors
setw -g window-status-fg white
setw -g window-status-bg default
setw -g window-status-attr dim
# active window title colors
setw -g window-status-current-fg cyan
setw -g window-status-current-bg default
#setw -g window-status-current-attr bright
setw -g window-status-current-attr underscore
# command/message line colors
set -g message-fg white
set -g message-bg black
set -g message-attr bright
set-option -g status-keys vi
set-option -g mode-keys vi
# List
set -g
set -g
set -g
set -g
set -g
set -g
#
#
#
#

of plugins
@plugin 'tmux-plugins/tpm'
@plugin 'tmux-plugins/tmux-sensible'
@plugin 'tmux-plugins/tmux-resurrect'
@plugin 'tmux-plugins/tmux-continuum'
@continuum-save-interval '5'
@continuum-restore 'on'

Other examples:
set -g @plugin 'github_username/plugin_name'
set -g @plugin '[email protected]/user/plugin'
set -g @plugin '[email protected]/user/plugin'

# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

10.4 Visual Studio Code


10.4.1 Install Visual Studio Code
install visualstudiocode from https://code.visualstudio.com
add the following Extensions :
python: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python
odoo-snippets: https://marketplace.visualstudio.com/items?itemName=jeffery9.odoo-snippets
Fallow the same instructions in (emacs-pylint) to install pylint and Pylint Odoo plugin. Then make same configurations in pylintrc file as descriped there.
Attention: pylintrc file can be placed in the user invirument to work for all projects. like for debian ~/.pylintrc

10.4. Visual Studio Code

117

Odoo development Documentation, Release master

10.4.2 Configuration:sample configuration (for user or workspace setting)


// Place your settings in this file to overwrite default and user settings.
{
//"python.pythonPath": "optional: path to python use if you have environment path ",
// use this so the autocompleate/goto definition will work with python extension
"python.autoComplete.extraPaths": [
"${workspaceRoot}/odoo/addons",
"${workspaceRoot}/odoo",
"${workspaceRoot}/odoo/openerp/addons" ],
//"python.linting.pylintPath": "optional: path to python use if you have environment path",
"python.linting.enabled": true,
//load the pylint_odoo
"python.linting.pylintArgs": ["--load-plugins", "pylint_odoo"],
"python.formatting.provider": "yapf",
//"python.formatting.yapfPath": "optional: path to python use if you have environment path",
// "python.linting.pep8Path": "optional: path to python use if you have environment path",
"python.linting.pep8Enabled": true,
// add this auto-save option so the pylint will sow errors while editing otherwise
//it will only show the errors on file save
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 500

// The following will hide the compiled file in the editor/ add other file to hide them from edit
"files.exclude": {
"**/*.pyc": true
}
}

Note: some lines are commented because it is optional. you can activate them if needed like in the case of using
Virtualenv.

10.4.3 Debugging
Launch Configurations
To debug your app in VS Code, youll first need to set up your launch configuration file - launch.json.
Click on the Configure gear icon on the Debug view top bar, choose your debug environment and VS
Code will generate a launch.json file under your workspaces .vscode folder.
sample python Debugging

118

Chapter 10. IDE

Odoo development Documentation, Release master

{
"name": "Python",
"type": "python",
"request": "launch",
"stopOnEntry": false,
"pythonPath": "${config.python.pythonPath}",
//"program": "${file}", use this to debug opened file.
"program": "${workspaceRoot}/Path/To/odoo.py",
"args": [
"-c ${workspaceRoot}/sampleconfigurationfile.cfg"
],
"cwd": "${workspaceRoot}",
"console": "externalTerminal",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},

Important: use args to specify any options like databace, config or user name and password.
sorce

10.4. Visual Studio Code

119

Odoo development Documentation, Release master

120

Chapter 10. IDE

CHAPTER 11

Other

11.1 RST format


11.1.1 Document Title / Subtitle
The title of the whole document is distinct from section titles and may be formatted somewhat differently (e.g. the
HTML writer by default shows it as a centered heading).
To indicate the document title in reStructuredText, use a unique adornment style at the beginning of the document. To
indicate the document subtitle, use another unique adornment style immediately after the document title. For example:
================
Document Title
================
---------Subtitle
---------Section Title
=============
...

Note that Document Title and Section Title above both use equals signs, but are distict and unrelated styles. The
text of overline-and-underlined titles (but not underlined-only) may be inset for aesthetics.
Sections
# with overline, for parts
* with overline, for chapters
=, for sections
-, for subsections
^, for subsubsections
, for paragraphs

121

Odoo development Documentation, Release master

Code block
Enter double colon (::) and then empty line and then at least one space and finaly you can enter your code.
Also you can use inplace code reference by using .

11.1.2 Selection
**bold**
*italic*
code

11.1.3 Lists
* - not numerated
#. - numerated
1,2,3, ... - numerated

11.1.4 Links
internal link:
:doc:`Link Text<../relative/path/to/article>`

external link:
`Link Text <https://google.com/>`_

11.1.5 More documentations


http://docutils.sourceforge.net/docs/user/rst/quickref.html

11.2 Adjust chromium window size script


You can make screenshot with size exactly you need.
Open chromium. Do not expand window (or in wount work). Run this command:
wmctrl -a chromium -e 1,0,0,760,451

Last two arguments is width and height. Consider to add chromium window borders to your screenshot size. In my
case it 10px to width and 80px to height. Likely you got same. So for 750 x 371 it be 760 x 451.
Note: We try our best to improve this manual, but if you still have difficults, we can offer private tutoring / advising
/ consultation on odoo development (resolve problem that developer(s) stack on, reveview code and pull requests ect).
Feel free to hire us on upwork:
Ivan Yelizariev
Ildar Nasyrov
122

Chapter 11. Other

Odoo development Documentation, Release master

For module development contact us by email or fill out request form:


[email protected]
https://www.it-projects.info/page/website.contactus

11.2. Adjust chromium window size script

123

You might also like