[ADD] doc : odoo official coding guidelines. This is the first version of the guidelines. Discussions and conventions are not frozen.
This commit is contained in:
parent
27594146b5
commit
b9977ef5d3
|
@ -17,7 +17,7 @@ Reference
|
|||
reference/http
|
||||
reference/qweb
|
||||
reference/javascript
|
||||
|
||||
reference/translations
|
||||
reference/reports
|
||||
reference/workflows
|
||||
reference/guidelines
|
||||
|
|
|
@ -0,0 +1,424 @@
|
|||
.. highlight:: python
|
||||
|
||||
=================
|
||||
Odoo Guidelines
|
||||
=================
|
||||
|
||||
This page introduce the new Odoo Coding Guidelines. This guidelines
|
||||
aims to improve the quality of the code (better readability of source,
|
||||
...) but also to improve Odoo Apps ! Indeed, a proper code will ease
|
||||
the maintenance and debugging, lower the complexity and promote the
|
||||
reliability.
|
||||
|
||||
Module structure
|
||||
================
|
||||
|
||||
Directories
|
||||
------------
|
||||
A module is organised in a few directory :
|
||||
|
||||
- *data/* : demo and data xml
|
||||
- *models/* : models definition
|
||||
- *controllers/* : contains controllers (http routes).
|
||||
- *views/* : contains the views and templates
|
||||
- *static/* : contains the web assets, separated into *css/, js/, img/, lib/, ...*
|
||||
|
||||
File naming
|
||||
------------
|
||||
For *views* declarations, split backend views from (frontend)
|
||||
templates in 2 differents files.
|
||||
|
||||
For *models*, split the business logic by sets of models, in each sets
|
||||
select a main model, this model gives its name to the set. If there is
|
||||
only one set of module, its name is the same as the module name. For
|
||||
each set named <main_model> the following files may be created:
|
||||
|
||||
- models/<main_model>.py
|
||||
- models/<inherited_main_model.py
|
||||
- views/<main_model>_templates.xml
|
||||
- views/<main_model>_views.xml
|
||||
|
||||
For instance, *sale* module introduce ``sale_order`` and
|
||||
``sale_order_line`` where ``sale_order`` is dominant. So the
|
||||
<main_model> files will be named *models/sale_order.py* and
|
||||
*views/sale_order_views.py*.
|
||||
|
||||
|
||||
For *data*, split them by purpose : demo or data. The filename will be
|
||||
the main_model name, suffixed by *_demo.xml* or *_data.xml*.
|
||||
|
||||
For *controller*, the only file should be named *main.py*.
|
||||
|
||||
For *static files*, the name pattern is *<module_name>.ext* (i.e. :
|
||||
static/js/im_chat.js, static/css/im_chat.css, static/xml/im_chat.xml,
|
||||
...). Don't link data (image, libraries) outside Odoo : don't use an
|
||||
url to an image but copy it in our codebase instead.
|
||||
|
||||
The complete tree should looks like
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
addons/<my_module_name>/
|
||||
|-- __init__.py
|
||||
|-- __openerp__.py
|
||||
|-- controllers/
|
||||
| |-- __init__.py
|
||||
| `-- main.py
|
||||
|-- data/
|
||||
| |-- <main_model>_data.xml
|
||||
| `-- <inherited_main_model>_demo.xml
|
||||
|-- models/
|
||||
| |-- __init__.py
|
||||
| |-- <main_model>.py
|
||||
| `-- <inherited_main_model>.py
|
||||
|-- security/
|
||||
| |-- ir.model.access.csv
|
||||
| `-- <main_model>_security.xml
|
||||
|-- static/
|
||||
| |-- img/
|
||||
| | |-- my_little_kitten.png
|
||||
| | `-- troll.jpg
|
||||
| |-- lib/
|
||||
| | `-- external_lib/
|
||||
| `-- src/
|
||||
| |-- js/
|
||||
| | `-- <my_module_name>.js
|
||||
| |-- css/
|
||||
| | `-- <my_module_name>.css
|
||||
| |-- less/
|
||||
| | `-- <my_module_name>.less
|
||||
| `-- xml/
|
||||
| `-- <my_module_name>.xml
|
||||
`-- views/
|
||||
|-- <main_model>_templates.xml
|
||||
|-- <main_model>_views.xml
|
||||
|-- <inherited_main_model>_templates.xml
|
||||
`-- <inherited_main_model>_views.xml
|
||||
|
||||
|
||||
.. note:: Filename should only use only ``[a-z0-9_]``
|
||||
|
||||
.. warning:: Use correct file permissions : folder 755 and file 644.
|
||||
|
||||
XML files
|
||||
=========
|
||||
|
||||
Format
|
||||
------
|
||||
When declaring a record in XML,
|
||||
|
||||
- Place ``id`` attribute before ``model``
|
||||
- For field declaration, ``name`` attribute is first. Then place the
|
||||
*value* either in the ``field`` tag, either in the ``eval``
|
||||
attribute, and finally other attributes (widget, options, ...)
|
||||
ordered by importance.
|
||||
|
||||
- Try to group the record by model. In case of dependencies between
|
||||
action/menu/views, the convention may not be applicable.
|
||||
- Use naming convention defined at the next point
|
||||
- The tag *<data>* is only used to set not-updatable data with ``noupdate=1``
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="view_id" model="ir.ui.view">
|
||||
<field name="name">view.name</field>
|
||||
<field name="model">object_name</field>
|
||||
<field name="priority" eval="16"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="my_field_1"/>
|
||||
<field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" statusbar_colors='{"invoice_except":"red","waiting_date":"blue"}' />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
Naming xml_id
|
||||
-------------
|
||||
|
||||
Security, View and Action
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use the following pattern :
|
||||
|
||||
* For a menu : *<model_name>_menu*
|
||||
* For a view : *<model_name>_view_<view_type>*, where *view_type* is kanban, form, tree, search, ...
|
||||
* For an action : the main action respects *<model_name>_action*.
|
||||
Others are suffixed with *_<detail>*, where *detail* is a underscore
|
||||
lowercase string explaining a little bit the action (Should not be
|
||||
long). This is used only if multiple action are declared for the
|
||||
model.
|
||||
* For a group : *<model_name>_group_<group_name>* where *group_name*
|
||||
is the name of the group, genrally 'user', 'manager', ...
|
||||
* For a rule : *<model_name>_rule_<concerned_group>* where
|
||||
*concerned_group* is the short name of the concerned group ('user'
|
||||
for the 'model_name_group_user', 'public' for public user, 'company'
|
||||
for multi-company rules, ...).
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- views and menus -->
|
||||
<record id="model_name_menu" model="ir.ui.menu">
|
||||
...
|
||||
</record>
|
||||
|
||||
<record id="model_name_view_form" model="ir.ui.view">
|
||||
...
|
||||
</record>
|
||||
|
||||
<record id="model_name_view_kanban" model="ir.ui.view">
|
||||
...
|
||||
</record>
|
||||
|
||||
<!-- actions -->
|
||||
<record id="model_name_action" model="ir.actions.act_window">
|
||||
...
|
||||
</record>
|
||||
|
||||
<record id="model_name_action_child_list" model="ir.actions.act_window">
|
||||
...
|
||||
</record>
|
||||
|
||||
<!-- security -->
|
||||
<record id="model_name_group_user" model="res.groups">
|
||||
...
|
||||
</record>
|
||||
|
||||
<record id="model_name_rule_public" model="ir.rule">
|
||||
...
|
||||
</record>
|
||||
|
||||
<record id="model_name_rule_company" model="ir.rule">
|
||||
...
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
.. note:: View name use dot notation ``my.model.view_type`` or ``my.model.view_type.inherit`` instead of *"This is the form view of My Model"*.
|
||||
|
||||
|
||||
Inherited XML
|
||||
~~~~~~~~~~~~~
|
||||
The naming pattern of inherited view is *<base_view>_inherit_<current_module_name>*. A module can extend a view only one time, suffix the orginal name with *_inherit_<current_module_name>*, where *current_module_name* is the technical name of the module extending the view.
|
||||
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="inherited_model_view_form_inherit_my_module" model="ir.ui.view">
|
||||
...
|
||||
</record>
|
||||
|
||||
|
||||
Python
|
||||
======
|
||||
|
||||
PEP8 options
|
||||
------------
|
||||
|
||||
Using a linter can help to see syntax and semantic warning or error. Odoo Source Code try to respect Python standard, but some of them can be ignored.
|
||||
|
||||
- E501: line too long
|
||||
- E301: expected 1 blank line, found 0
|
||||
- E302: expected 2 blank lines, found 1
|
||||
- E126: continuation line over-indented for hanging indent
|
||||
- E123: closing bracket does not match indentation of opening bracket's line
|
||||
- E127: continuation line over-indented for visual indent
|
||||
- E128: continuation line under-indented for visual indent
|
||||
- E265: block comment should start with '# '
|
||||
|
||||
Imports
|
||||
-------
|
||||
The imports are ordered as
|
||||
|
||||
#. Externals libs (One per line sorted and splitted in python stdlib)
|
||||
#. Imports of ``openerp``
|
||||
#. Imports from Odoo modules (rarely, and only if necessary)
|
||||
|
||||
Inside these 3 groups, the imported lines are alphabetically sorted.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 1 : imports of python lib
|
||||
import base64
|
||||
import re
|
||||
import time
|
||||
# 2 : imports of openerp
|
||||
import openerp
|
||||
from openerp import api, fields, models # alphabetically ordered
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.translate import _
|
||||
# 3 : imports from odoo modules
|
||||
from openerp.addons.website.models.website import slug
|
||||
from openerp.addons.web.controllers.main import login_redirect
|
||||
|
||||
|
||||
Idioms
|
||||
-------
|
||||
|
||||
- Prefer ``%`` over ``.format()``, prefer ``%(varname)`` instead of position (This is better for translation)
|
||||
- Try to avoid generators and decorators
|
||||
- Always favor *Readability* over *conciseness* or using the language features or idioms.
|
||||
- Use list comprehension, dict comprehension, and basic manipulation using ``map``, ``filter``, ``sum``, ... They make the code easier to read.
|
||||
- The same applies for recordset methods : use ``filtered``, ``mapped``, ``sorted``, ...
|
||||
- Each python file should have ``# -*- coding: utf-8 -*-`` as first line
|
||||
- Use the ``UserError`` defined in ``openerp.exceptions`` instead of overriding ``Warning``, or find a more appropriate exception in *exceptions.py*
|
||||
- Document your code (docstring on methods, simple comments for the tricky part of the code)
|
||||
- Use meaningful variable/class/method names
|
||||
|
||||
|
||||
|
||||
Symbols
|
||||
-------
|
||||
|
||||
- Odoo Python Class : use camelcase for code in api v8, underscore lowercase notation for old api.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
...
|
||||
|
||||
class account_invoice(osv.osv):
|
||||
...
|
||||
|
||||
- Variable name :
|
||||
- use camelcase for model variable
|
||||
- use underscore lowercase notation for common variable.
|
||||
- since new API works with record or recordset instead of id list, don't suffix variable name with *_id* or *_ids* if they not contain id or list of id.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ResPartner = self.env['res.partner']
|
||||
partners = ResPartner.browse(ids)
|
||||
partner_id = partners[0].id
|
||||
|
||||
- ``One2Many`` and ``Many2Many`` fields should always have *_ids* as suffix (example: sale_order_line_ids)
|
||||
- ``Many2One`` fields should have *_id* as suffix (example : partner_id, user_id, ...)
|
||||
- Method conventions
|
||||
- Compute Field : the compute method pattern is *_compute_<field_name>*
|
||||
- Search method : the search method pattern is *_search_<field_name>*
|
||||
- Default method : the default method pattern is *_default_<field_name>*
|
||||
- Onchange method : the onchange method pattern is *_onchange_<field_name>*
|
||||
- Constraint method : the constraint method pattern is *_check_<constraint_name>*
|
||||
- Action method : an object action method is prefix with *action_*. Its decorator is ``@api.multi``, but since it use only one record, add ``self.ensure_one()`` at the beginning of the method.
|
||||
|
||||
- In a Model attribute order should be
|
||||
#. Private attributes (``_name``, ``_description``, ``_inherit``, ...)
|
||||
#. Default method and ``_default_get``
|
||||
#. Fields declarations
|
||||
#. Compute and search methods in the same order than field declaration
|
||||
#. Constrains methods (``@api.constrains``) and onchange methods (``@api.onchange``)
|
||||
#. CRUD methods (ORM overrides)
|
||||
#. Action methods
|
||||
#. And finally, other business methods.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Event(models.Model):
|
||||
# Private attributes
|
||||
_name = 'event.event'
|
||||
_description = 'Event'
|
||||
|
||||
# Default methods
|
||||
def _default_name(self):
|
||||
...
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Char(string='Name', default=_default_name)
|
||||
seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
|
||||
store=True, readonly=True, compute='_compute_seats')
|
||||
seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
|
||||
store=True, readonly=True, compute='_compute_seats')
|
||||
price = fields.Integer(string='Price')
|
||||
|
||||
# compute and search fields, in the same order that fields declaration
|
||||
@api.multi
|
||||
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
|
||||
def _compute_seats(self):
|
||||
...
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains('seats_max', 'seats_available')
|
||||
def _check_seats_limit(self):
|
||||
...
|
||||
|
||||
@api.onchange('date_begin')
|
||||
def _onchange_date_begin(self):
|
||||
...
|
||||
|
||||
# CRUD methods
|
||||
def create(self):
|
||||
...
|
||||
|
||||
# Action methods
|
||||
@api.multi
|
||||
def action_validate(self):
|
||||
self.ensure_one()
|
||||
...
|
||||
|
||||
# Business methods
|
||||
def mail_user_confirm(self):
|
||||
...
|
||||
|
||||
|
||||
Javascript and CSS
|
||||
==================
|
||||
**For javascript :**
|
||||
|
||||
- ``use strict;`` is recommended for all javascript files
|
||||
- Use a linter (jshint, ...)
|
||||
- Never add minified Javascript Libraries
|
||||
- Use camelcase for class declaration
|
||||
|
||||
**For CSS :**
|
||||
|
||||
- Prefix all your class with *o_<module_name>* where *module_name* is the technical name of the module ('sale', 'im_chat', ...) or the main route reserved by the module (for website module mainly, i.e. : 'o_forum' for website_forum module). The only exception for this rule is the webclient : it simply use *o_* prefix.
|
||||
- Avoid using id
|
||||
- Use bootstrap native class
|
||||
- Use underscore lowercase notation to name class
|
||||
|
||||
Git
|
||||
===
|
||||
|
||||
Commit message
|
||||
--------------
|
||||
|
||||
Prefix your commit with
|
||||
|
||||
- **[IMP]** for improvements
|
||||
- **[FIX]** for bug fixes
|
||||
- **[REF]** for refactoring
|
||||
- **[ADD]** for adding new resources
|
||||
- **[REM]** for removing of resources
|
||||
- **[MERGE]** for merge commits (only for forward/back-port)
|
||||
- **[CLA]** for signing the Odoo Individual Contributor License
|
||||
|
||||
Then, in the message itself, specify the part of the code impacted by your changes (module name, lib, transversal object, ...) and a description of the changes.
|
||||
|
||||
- Always put meaning full commit message: commit message should be
|
||||
self explanatory (long enough) including the name of the module that
|
||||
has been changed and the reason behind that change. Do not use
|
||||
single words like "bugfix" or "improvements".
|
||||
|
||||
- Avoid commits which simultaneously impacts lots of modules. Try to
|
||||
splits into different commits where impacted modules are different
|
||||
(It will be helpful when we are going to revert that module
|
||||
separately).
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
[FIX] website, website_mail: remove unused alert div, fixes look of input-group-btn
|
||||
Bootstrap's CSS depends on the input-group-btn
|
||||
element being the first/last child of its parent.
|
||||
This was not the case because of the invisible
|
||||
and useless alert.
|
||||
|
||||
[IMP] fields: reduce memory footprint of list/set field attributes
|
||||
|
||||
[REF] web: add module system to the web client
|
||||
This commit introduces a new module system for the javascript code.
|
||||
Instead of using global ...
|
||||
|
||||
|
||||
.. note:: The long description try to explain the *why* not the
|
||||
*what*, the *what* can be seen in the diff
|
||||
|
Loading…
Reference in New Issue