Odoo Development
Odoo Development
Odoo Development
Release master
IT-Projects LLC
1 First steps 3
2 Module Development 5
2.1 Docs and manifests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3 Odoo Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.5 HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6 CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.7 YAML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.8 Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.9 Frontend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.10 Point of Sale (POS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.11 Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.12 Hooks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.13 Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.14 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.15 Source Diving . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
2.16 Lint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.17 Other . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3 Module Migration 65
3.1 Switching module to new api . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.2 Fixing references on migration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4 User documentation 69
4.1 static/description/index.html . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.2 Screenshots tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.3 Module description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.4 Contact us block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.5 JS Tour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.6 Preview module on App Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.7 Image sizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
i
5.4 Multi Pull Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.5 Cancel lame commit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.6 Pull request from console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.7 Check remote bundings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.8 Files relocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.9 Commit comment prefix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.10 Git stash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.11 Update Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.12 Squash commits into one . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
6 Continuous Integration 91
6.1 Runbot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
6.2 Odoo Travis Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
6.3 Coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
7 Odoo 95
7.1 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
7.2 How to use Odoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
10 IDE 119
10.1 Emacs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
10.2 PyCharm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
10.3 Tmux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
10.4 Visual Studio Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
11 Other 129
11.1 RST format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
11.2 Adjust chromium window size script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
ii
Odoo development Documentation, Release master
Contents 1
Odoo development Documentation, Release master
2 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
3
Odoo development Documentation, Release master
Module Development
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
– OCA’s README
• Demo
– addons-dev
• HTML Description
• Usage instructions
• Changelog
• Tested on
Guidlines
=============
Module Name
=============
Then add more detailed description, technical specifications, any other information that could be int
Credits
=======
Contributors
------------
* DEVELOPER_NAME <PERSON@it-projects.info>
0 The only exception could be made for “data” field in __openerp__.py file.
5
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
Changelog: `<doc/changelog.rst>`_
OCA’s 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: http://runbot.it-projects.info/demo/access-addons/10.0
Demo: http://runbot.it-projects.info/demo/addons-dev/misc-addons-10.0-some_feature
Demo: http://runbot.it-projects.info/demo/l10n-addons/10.0
Demo: http://runbot.it-projects.info/demo/mail-addons/10.0
Demo: http://runbot.it-projects.info/demo/misc-addons/10.0
Demo: http://runbot.it-projects.info/demo/odoo-saas-tools/10.0
Demo: http://runbot.it-projects.info/demo/odoo-telegram/10.0
Demo: http://runbot.it-projects.info/demo/pos-addons/10.0
Demo: http://runbot.it-projects.info/demo/rental-addons/10.0
Demo: 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
You have to prepare this link even if the module is not published yet, i.e. link returns 404 error.
Usage instructions
• doc/index.rst
Changelog
• doc/changelog.rst
Tested on
doc/index.rst
=============
Module name
=============
Installation
============
Configuration
=============
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 under Documentation tab. Example:
https://www.odoo.com/apps/modules/8.0/pos_multi_session/
__openerp__.py / __manifest__.py
• Filename
• Template
• name
• summary
• category
– Hidden
• version
– version in OCA
• author
– author in OCA
• website
• license
• external_dependencies
Filename
Template
# "price": 9.00,
# "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:
• OCA’s template: https://github.com/OCA/maintainer-tools/blob/master/template/module/__openerp__.py
name
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
• Accounting
• Discuss
• Document Management
• eCommerce
• Human Resources
• Industries
• Localization
• Manufacturing
• Marketing
• Point of Sale
• Productivity
• Project
• Purchases
• Sales
• Warehouse
• Website
• Extra Tools
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
– [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
In the main, if module already exists and you make small updatesfixes, you should not add your name to authors.
author in OCA For OCA’s repositories put company name first, then OCA. Developers are listed in README file:
"author": "IT-Projects LLC, Odoo Community Association (OCA)",
website
license
external_dependencies
doc/changelog.rst
Template
- Init version
Guidlines
`2.0.0`
-------
`1.2.0`
-------
`1.0.1`
-------
`1.0.0`
-------
- Init version
icon.png
IT-Projects LLC
• SaaS
• Telegram
• Access
• Barcode
• Mail
• Pos
• Stock
• Website
• Website_Sale
• Misc
SaaS
Download
Telegram
Download
Access
Download
Barcode
Download
Mail
Download
Pos
Download
Stock
Download
Website
Download
Website_Sale
Download
Misc
Download
Notes
RST Requirements
• Extra lines
• References to menu
• Fields
• Buttons
• Selections
• Titles and sections
Extra lines
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.
* 1
* 1.1
* 1.2
* 1.3
* 2
* 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 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
References to menu
BAD
* Open menu ``Settings>>Parameters>>System Parameters``
* Open menu "Settings >> Parameters >> System Parameters"
* Open menu ''Settings >> Parameters >> System Parameters''
* Open menu ``Settings > Parameters > System Parameters``
Fields
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``
OK:
===========================
Correctly formatted Title
===========================
BAD:
===========================================
No spaces at the beggining and end of title
===========================================
=============================
No space at the end of title
=============================
=======================================
Incorrect number of signs in title
========================================
================
Incorrect number of signs in title
================
README.rst
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 don’t want duplicate content, use the following
priority:
• index.html
• index.rst
• README.rst
Guidelines
Source:
• https://www.odoo.com/documentation/8.0/reference/guidelines.html
Comments
First of all, comments in the source are required if it’s not obvious why are doing something.
Additionally, you can add comments about what are you doing, if it could be helpful.
Odoo Python
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, would raise Warning, telling you that you can only call it on one record.
@api.multi
And api.model is considered to be used when you need to do something with model itself and don’t need to mod-
ify/check some exact model’s record/records. For example there could be method that returns some meta info about
model’s structure or some helper methods, etc. Also in documentation it is said that this api is good to use when mi-
grating 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.
Pure Python
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'
_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 field’s value.
• For a boolean field like group_XXX, execute adds/removes ‘implied_group’ to/from the implied groups of
‘group’, depending on the field’s 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
default_XXX
TODO
group_XXX
module_XXX
Web controllers
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)
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 ~open-
erp.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.
Fields
class AModel(models.Model):
_name = 'a_name'
name = fields.Char(
string="Name", # Optional label of the field
compute="_compute_name_custom", # Transform the fields in computed fields
store=True, # If computed it will store the result
select=True, # Force index on field
readonly=True, # Field will be readonly in views
inverse="_write_name" # On update trigger
required=True, # Mandatory field
translate=True, # Translation enable
help='blabla', # Help tooltip text
company_dependent=True, # Transform columns to ir.property
search='_search_function' # Custom search function mainly used with compute
)
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
Char
Specific options:
• size: data will be trimmed to specified size
Text
Specific options:
• translate: field can be translated
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
DateTime
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
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
Specific options:
• selection: a list of tuple or a callable name that take recordset as input
Many2one
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
Specific options:
• comodel_name: name of the opposite model
• inverse_name: relational column of the opposite model
Many2many
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
When you call a record as a dict it will force to look on the columns.
Fields Defaults
#...
def a_fun(self):
return self.do_something()
Using a fun will force you to define function before fields definition.
Computed Fields
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
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
Related Field
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.
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
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 as-
signed to a list of triples of strings (name, sql_definition, message), where name is a valid SQL con-
straint name, sql_definition is a table_constraint_ expression, and message is the error message.
Postgres View is a kind of table, which is not physically materialized. Instead, the query is run every time the view is
referenced in a query.
To create Postgres View in odoo do as follows:
• create new model
• all fields must have the flag readonly=True.
• specify the parameter _auto=False to the odoo model, so no table corresponding to the fields is created
automatically.
• add a method init(self, cr) that creates a PostgreSQL View matching the fields declared in the model.
– id field has to be specified in SELECT part. See example below
• add views for the model in a usual way
Example:
from odoo import api, fields, models, tools
class ReportEventRegistrationQuestions(models.Model):
_name = "event.question.report"
_auto = False
@api.model_cr
def init(self):
""" Event Question main report """
tools.drop_view_if_exists(self._cr, 'event_question_report')
self._cr.execute(""" CREATE VIEW event_question_report AS (
SELECT
att_answer.id as id,
att_answer.event_registration_id as attendee_id,
answer.question_id as question_id,
answer.id as answer_id,
question.event_id as event_id
FROM
event_registration_answer as att_answer
LEFT JOIN
event_answer as answer ON answer.id = att_answer.event_answer_id
LEFT JOIN
event_question as question ON question.id = answer.question_id
GROUP BY
attendee_id,
event_id,
question_id,
answer_id,
att_answer.id
)""")
XML
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>
Xpath
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>
Important
Inside of
<xpath expr="//some/xpath" position="attributes">
...
</xpath>
Basic stuff
Code:
<t t-set="order" t-value="website.sale_get_order()"/>
Code:
<t t-set="foobar" t-value="website.env['ir.config_parameter'].get_param('my_module.foobar')"/>
Code:
<p><t t-esc="foobar"/></p>
Code:
2.4. XML 31
Odoo development Documentation, Release master
<label t-if="foobar">
<p>foobar is true</p>
</label>
Code:
t-att-value="my_var"
Inherit
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 -->
HTML
Active elements
Code:
<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.
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/>.
CSS
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>
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;
}
YAML
Pure YAML
TODO
YAML in odoo
TODO
Javascript
Inheritance
TODO
2.6. CSS 33
Odoo development Documentation, Release master
core.bus
Usage
// 8.0
var bus = openerp.web.bus;
// 9.0+
var core = require('web.core');
var bus = core.bus;
// trigger event
bus.trigger('barcode_scanned', barcode);
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,
kwargs: kwargs
}, options);
},
Frontend
Web page
Common
Open a new project:
./odoo.py scaffold newpage addons
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')
2.9. Frontend 35
Odoo development Documentation, Release master
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';
});
partner_model.fields.push('qty_available');
// Inheritance
return _super_posmodel.initialize.call(this, session, attributes);
},
});
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 doesn’t 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. It’s 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";
screens.OrderWidget.include({
...
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";
Access
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
• 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 user’s 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 me-
nuitem can be hidden.
<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>
2.11. Access 39
Odoo development Documentation, Release master
<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>
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.
Check the Technical Features box and reload odoo. Now we can see the Groups menu item!
2.11. Access 41
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
Our current user is Administrator. By default he is not a member of the base.group_multicompany group. That
is why the company_id isn’t visible for him on the form.
2.11. Access 43
Odoo development Documentation, Release master
Model records:
• restrict access to specified subset of records in model
Model:
• restrict access to all records of model
Superuser rights
Administrator, i.e. user with id 1 (SUPERUSER_ID), has exceptions about access rights.
ir.model.access
If some model doesn’t 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 don’t to apply to Administrator too. See the source: 8.0, 9.0, 10.0
See also:
• ir.model.access
• ir.rule
Hooks
Tests
Test class
From openerp/tests/common.py:
class BaseCase(unittest.TestCase):
"""
Subclass of TestCase for common OpenERP-specific code.
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
2.12. Hooks 45
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
at_install - run tests right after loading module’s 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
JS Testing
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):
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")
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.
Paypal testing
2.13. Tests 47
Odoo development Documentation, Release master
• Go to Dashboard->Sand box->Accounts. Create business (seller) and personal (buyer) accounts. It’s recom-
mended to don’t use non-ascii symbols in account information (address, name etc.)
• 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
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.
What to test
Obviously, you have to test features that module provide. But, it’s 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
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.
Debugging
Logs
• Error Message
• Terminal
• Console
– boot.js
• Sources
• Network
– How to see html request initiator
Error Message
It’s a first place where you can see error message. But in most time, it doesn’t contain enough information to resolve
problem. Check other possbile ways to get log messages below.
Terminal
Console
It’s a short term for browser’s console. Click F12 in browser to open console.
It can contain error and warning about client part.
boot.js
2.14. Debugging 49
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 browser’s developer tool. To see original odoo js files i.e. not minimized versions, swich odoo in debug mode first.
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 column’s element.
Typical errors
If into server console no errors but boot.js raise exception that find out reason error next steps:
2.14. Debugging 51
Odoo development Documentation, Release master
For example, sometimes during page load displayed the error type:
Missing dependencies: [...] Non loaded modules: [...]
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.
There is an AccessError which doesn’t 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.
2.14. Debugging 53
Odoo development Documentation, Release master
Such error means, that your user doesn’t satisfy access requirements specified in ir.rule. See Access section for general
understanding how odoo security works.
QWeb
will stop execution if debugging is active (exact condition depend on the browser and its development tools)
t-js the node’s 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-js‘s body:
<t t-set="foo" t-value="42"/>
<t t-js="ctx">
console.log("Foo is", ctx.foo);
</t>
Source
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
# 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%
# reset settings
sudo tc qdisc del dev eth0 root
2.14. Debugging 55
Odoo development Documentation, Release master
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 haven’t it yet.
sudo apt-get install xdotool
or so:
sleep 3 && echo '3333333333338'| grep -o . | xargs xdotool key && xargs xdotool key \n &
hw_escpos
• apply patch
cd /path/to/odoo/
# odoo 10
curl https://raw.githubusercontent.com/it-projects-llc/odoo-development/master/docs/dev/debug/hw
# odoo 9
curl https://raw.githubusercontent.com/it-projects-llc/odoo-development/master/docs/dev/debug/hw
On printing:
• some binary data is sent to /tmp/printer
• odoo prints logs with unparsed data
POS
At any database (including one on runbot as well as database where you have installed hw_escpos):
• set Receipt printer checkbox in pos.config and set ip equal to 127.0.0.1:8888
Warning: for some reason printer emulation doesn’t work in debug mode
• print ticket
Source Diving
Context
When porting module mail_move_message in the file static/src/js/mail_move_message.js there is a method ses-
sion.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
Context
=======
* LINK1
* LINK2
Problem
=======
* LINK1
* LINK2
Solution
========
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
Lint
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
# increase max errors to be fixed (otherwise script stops)
echo '{"maxerr": 1000}' > ~/.jshintrc
Script
EXCLUDE_FILES=".\(svg\|gif\|png\|jpg\)$"
# fix line break symbols
cd /path/to/MODULE_NAME
find * -type f | grep -v $EXCLUDE_FILES | xargs sed -i 's/\r//g'
# PEP8 py-:
autopep8 --in-place -r --aggressive --aggressive --ignore E501 ./
# fix CamelCase
oca-autopep8 -ri --select=CW0001 .
# 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 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'
2.16. Lint 59
Odoo development Documentation, Release master
Other
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
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.
Contents
• Dynamic records
– noupdate=False
– noupdate=True
* python file
* yaml file
* __openerp__.py
* __init__.py
python file
MODULE = '_web_debranding'
class view(models.Model):
_inherit = 'ir.ui.view'
2.17. Other 61
Odoo development Documentation, Release master
})
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
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
MODULE = '_web_debranding'
def uninstall_hook(cr, registry):
registry['ir.model.data']._module_data_uninstall(cr, SUPERUSER_ID, [MODULE])
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. Other 63
Odoo development Documentation, Release master
Module Migration
Automatic replacements
# IMPORTS
# replace osv, orm
find . -type f -name '*.py' | xargs sed -i 's/from openerp.osv import orm$/from odoo import models/g'
find . -type f -name '*.py' | xargs sed -i 's/from openerp.models.orm import Model$/from odoo.models
find . -type f -name '*.py' | xargs sed -i 's/osv.osv_memory/models.TransientModel/g'
find . -type f -name '*.py' | xargs sed -i 's/osv.osv/models.Model/g'
find . -type f -name '*.py' | xargs sed -i 's/osv.except_osv/UserError/g'
find . -type f -name '*.py' | xargs sed -i 's/osv\./models./g'
find . -type f -name '*.py' | xargs sed -i 's/\<orm\./models./g'
find . -type f -name '*.py' | xargs sed -i 's/\(import .*\), osv/\1, models/g'
find . -type f -name '*.py' | xargs sed -i 's/\(import .*\)osv, /\1models, /g'
find . -type f -name '*.py' | xargs sed -i 's/\(import .*\)osv/\1models/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'
# general replacement
find . -type f -name '*.py' | xargs sed -i 's/from openerp/from odoo/g'
# FIELDS
# update fields
# (multiline: http://stackoverflow.com/questions/1251999/how-can-i-replace-a-newline-n-using-sed/7697
# delete _columns
65
Odoo development Documentation, Release master
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/
find . -type f -name '*.py' | xargs sed -i 's/fields.function(\([^,]*\)\(.*\)type=.\([2a-z]*\)["\x27]
find . -type f -name '*.py' | xargs sed -i 's/fields.many2one(\(.*\)obj=\([^,]*\)/fields.many2one(\2,
find . -type f -name '*.py' | xargs sed -i 's/,[ ]*,/,/g'
find . -type f -name '*.py' | xargs sed -i 's/,[ ]*,/,/g'
find . -type f -name '*.py' | xargs sed -i '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'
Semi-Automatic replacements
We recommend to use commands below after commiting previous changes. It allows you to check differences.
The commands doesn’t update code fully and usually you need to continue updates manually.
# pool -> env
find . -type f -name '*.py' | xargs sed -i 's/self.pool/self.env/g'
# remove cr, uid
find . -type f -name '*.py' | xargs sed -i 's/(cr, [^,]*, /(/g'
find . -type f -name '*.py' | xargs sed -i 's/(self, cr, [^,]*, ids/(self/g'
find . -type f -name '*.py' | xargs sed -i 's/(self, cr, uid, /(self, /g'
find . -type f -name '*.py' | xargs sed -i 's/, context=[^,)]*//g'
find . -type f -name '*.py' | xargs sed -i 's/self.env.get(\([^)]*\))/self.env[\1]/g'
# res_config.py
find . -type f -name 'res_config.py' | xargs sed -i 's/\(def get_default_.*\)(self)/\1(self, fields)/
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:
• it@it-projects.info
• https://www.it-projects.info/page/module-migration
User documentation
static/description/index.html
• Image sizes
• Templates
– Title
– Text + Image
– Image + Text
– Text, Image
– Contact us
• oe_dark
Image sizes
• Image Sizes
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
69
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
Contact us
• Contact us block
oe_dark
Use oe_dark class on every even section. Don’t use oe_dark on the last section Contact us.
<section class="oe_container">
<!--Title-->
</section>
<section class="oe_container">
</section>
<section class="oe_container">
</section>
<section class="oe_container">
</section>
<section class="oe_container">
<!--Contact us block-->
</section>
Screenshots tools
Module description
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']
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"""
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
JS Tour
Used to demonstrate module capabilities step by step with popup windows. It may be launched automatically or
manually.
Creating Tour
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({ ...
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>
4.5. JS Tour 73
Odoo development Documentation, Release master
Browser’s 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
Preview image
While it’s easy to change text, it’s not obvious how to preview image.
Base64
– AFTER
Nginx
You cannot use localhost due to security restrictions. So, you need to add some domain to /etc/hosts::
127.0.0.1 static.local
Image sizes
See also
• Preview module on App Store
• Adjust chromium window size script
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
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.
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
img.oe_demo.oe_picture.oe_screenshot
img.oe_demo.oe_screenshot
:: max-width: 928px;
ssh keys
github profile
* you can use your account to tweet about development, work, traveling, vacation, fun
* twitter profile’s website URL has to be https://www.it-projects.info/
* no requirements for profile description, header photo
* profile photo should be a real face photo
* location has to be specifed YouCity, Country
– Company must be set to @it-projects-llc
– Location must be your YouCity, Country
– Photo must be your real face photo
• https://github.com/settings/emails
– primary email must be personal address . . . @it-projects.info
– “Keep my email address private” must be switched off
• https://github.com/orgs/it-projects-llc/people
– get invitation
– set Organization visibility to Public
79
Odoo development Documentation, Release master
git email
git editor
gitignore
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
Forward-port
It’s 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
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
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:
git cherry-pick
Apply commits from newer branch (e.g. 9.0) to older branch (e.g. 8.0)
git checkout 8.0
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.
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
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:
git push
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
Notes
• It’s 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
only. Make mergingchery-picking commits be only about merging and chery-picking, make porting commits
separately.
• If you don’t have conflicts, you do not need to make commit after cherry-pick because it creates commit by its
own.
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
# take one commit sha from the list and check that it's in origin/9.0.
# possible output:
# upstream/9.0
# origin/9.0-dev
alias git=hub
Nessesary to add some header for pull request. Save it. If everything is ok you will got link to your pull request.
Files relocation
• git format-patch
• git filter-branch
git format-patch
# Create patch
cd $REPO_PATH
git fetch upstream
git format-patch --stdout --root upstream/$BRANCH -- $MODULE > /tmp/relocation.patch
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
git filter-branch
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
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
Basic tags
• [DOC] for documentation. Don’t use any other tags when you improve, fix, refactor documentation
• [PORT] for porting (version tag is required)
• [BACKPORT] for back-porting (version tag is required)
• [IMP] for improvements
• [FIX] for bug fixes
• [REF] for refactoring
• [ADD] for adding new resources (new modules or files).
• [REM] for removing of resources
• [REL] for releases
• [CI] for updating .travis.yml, requirements.txt, */tests/*, etc. files
• [LINT] for fixing lint errors
• [i18n] for translations
Version tags
• [8.0]
• [9.0]
• [10.0]
• etc.
Forbidden
Git stash
• book: https://git-scm.com/book/no-nb/v1/Git-Tools-Stashing
• man: https://git-scm.com/docs/git-stash
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
Backup
Remote backup
git push origin 9.0-new-module:9.0-new-module-backup
git rebase -i
Interactive squashing
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
Push
Continuous Integration
Runbot
• runbot.odoo.com
– How to use runbot.odoo.com?
• runbot.it-projects.info
• How to deploy runbot?
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
• 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
91
Odoo development Documentation, Release master
runbot.it-projects.info
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
TODO
Coverage
Odoo
Models
ir.config_parameter
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 doesn’t 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
95
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
res.users
TODO
res.groups
TODO
ir.model.access
Fields
96 Chapter 7. Odoo
Odoo development Documentation, Release master
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.
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 doesn’t pass, then user doesn’t get access
• Find and check global rules for the model and for read operation
– if the record doesn’t satisfy (doesn’t fit to domain) for at least one of the global rules, then user doesn’t
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)
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 97
Odoo development Documentation, Release master
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
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>
bus.bus
Bus
Bus is a module for instant notifications via longpolling. Add it to dependencies list:
'depends': ['bus']
Warning: Don’t 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
98 Chapter 7. Odoo
Odoo development Documentation, Release master
• Scheme of work
• Channel identifier
• Listened channels
• Binding notification event
• Start polling
• Sending notification
• Handling notifications
Scheme of work
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]);
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 99
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;
In js file:
bus.on("notification", this, this.on_notification);
Start polling
In js file:
bus.start_polling();
Note: You don’t 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
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
ir.cron
The first thing you notice is the data noupdate="1", this is telling Odoo that all code within this tag shouldn’t 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>
This user id is referring to a specific user, in most cases this will be base.user_root.
<field name="interval_number">1</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 -->
The field model specifies on which model the automated action should be called.
The priority of the job, as an integer: 0 means higher priority, 10 means lower priority.
Defaults.
Name Definition
nextcall lambda *a: time.strftime(DEFAULT_SERVER_DATETIME_FORMAT
priority 5
user_id lambda obj,cr,uid,context: uid
interval_number 1
interval_type months
numbercall 1
active 1
doall 1
mail.message
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>
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.
From UI
8.0-
Database with dots Early version of odoo doesn’t allow to create databases with dots. You can remove this restric-
tion in two ways:
1. Updates sources:
cd path/to/odoo
sed -i 's/matches="[^"]*"//g' addons/web/static/src/xml/base.xml
From terminal
9.0+
To create new database simple add -d parameter when you run odoo, e.g.:
./openerp-server -d database1
8.0
9.0+
Since Odoo 9.0 to enable Technical Features you only need to activate developer mode.
8.0
9.0
10.0+
8.0
9.0
10.0+
8.0
9.0
10.0+
8.0
9.0
10.0+
TODO
To activate developer mode, you need to add debug parameter to your url, e.g.:
localhost:8069/web?debug=1
Odoo administration
Odoo installation
Contents
• Odoo installation
– Local installation
* nginx_odoo.conf
– Production installation
Local installation
################### 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
111
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
################### /etc/hosts
# /etc/hosts must contains domains you use, e.g:
sudo bash -c "echo '127.0.0.1 8_0-project1.local' >> /etc/hosts"
sudo bash -c "echo '127.0.0.1 8_0-project2.local' >> /etc/hosts"
sudo bash -c "echo '127.0.0.1 9_0-project1.local' >> /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
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;
}
}
Production installation
• https://github.com/yelizariev/install-odoo
Longpolling
• 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
About longpolling
--workers
• 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
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
--addons-path
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.
--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
Log levels
• CRITICAL
• ERROR
• WARNING
• INFO
• DEBUG
• NOTSET
Continuous Delivery
TODO
117
Odoo development Documentation, Release master
IDE
Emacs
Emacs
– magit
– js3-mode
Spacemacs
Requirements
Installation
Documentation
http://spacemacs.org/doc/DOCUMENTATION.html
119
Odoo development Documentation, Release master
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-not-
already-open
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 code’s complexity. https://pylint.readthedocs.io/en/latest/
Install pylint.
sudo pip install pylint
With the Flycheck emacs extension, pylint’s output will be shown right inside your emacs buffers. Spacemacs has
flycheck in his syntax-checking layer.
M-x package-install RET flycheck
or
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 don’t like this mes-
sage 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
PyCharm
PyCharm
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 = ‘*’
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:
Tmux
Tmux installation
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 apt-get update
sudo apt-get install -y python-software-properties software-properties-common
sudo add-apt-repository -y ppa:pi-rho/dev
sudo apt-get update
sudo 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
# Other examples:
# set -g @plugin 'github_username/plugin_name'
# set -g @plugin 'git@github.com/user/plugin'
# set -g @plugin 'git@bitbucket.com/user/plugin'
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'
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
Tmux configuration
# reload settings
bind-key R source-file ~/.tmux.conf
# Statusbar settings
# toggle statusbar
bind-key s set status
# amount of time for which status line messages and other indicators
# are displayed. time is in milliseconds.
set -g display-time 2000
set-option -g status-keys vi
set-option -g mode-keys vi
# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-save-interval '5'
set -g @continuum-restore 'on'
# Other examples:
# set -g @plugin 'github_username/plugin_name'
# set -g @plugin 'git@github.com/user/plugin'
# set -g @plugin 'git@bitbucket.com/user/plugin'
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'
Attention: pylintrc file can be placed in the user invirument to work for all projects. like for debian “~/.pylintrc”
Configuration:-
// 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.enabled": true,
"python.formatting.provider": "yapf",
"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.
Debugging
Launch Configurations
To debug your app in VS Code, you’ll 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 workspace’s .vscode folder.
sample python Debugging
{
"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
Other
RST format
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
129
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 ‘‘ ‘‘.
Selection
• **bold**
• *italic*
• ‘‘code‘‘
Lists
• * - not numerated
• #. - numerated
• 1,2,3, ... - numerated
Links
• internal link:
:doc:`Link Text<../relative/path/to/article>`
• external link:
`Link Text <https://google.com/>`_
More documentations
• http://docutils.sourceforge.net/docs/user/rst/quickref.html
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