diff --git a/doc/reference.rst b/doc/reference.rst index 9c995ef9638..00461245e48 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -17,7 +17,7 @@ Reference reference/http reference/qweb reference/javascript - reference/translations reference/reports reference/workflows + reference/guidelines diff --git a/doc/reference/guidelines.rst b/doc/reference/guidelines.rst new file mode 100644 index 00000000000..542b23ae7ce --- /dev/null +++ b/doc/reference/guidelines.rst @@ -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 the following files may be created: + +- models/.py +- models/_templates.xml +- views/_views.xml + +For instance, *sale* module introduce ``sale_order`` and +``sale_order_line`` where ``sale_order`` is dominant. So the + 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 *.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// + |-- __init__.py + |-- __openerp__.py + |-- controllers/ + | |-- __init__.py + | `-- main.py + |-- data/ + | |-- _data.xml + | `-- _demo.xml + |-- models/ + | |-- __init__.py + | |-- .py + | `-- .py + |-- security/ + | |-- ir.model.access.csv + | `-- _security.xml + |-- static/ + | |-- img/ + | | |-- my_little_kitten.png + | | `-- troll.jpg + | |-- lib/ + | | `-- external_lib/ + | `-- src/ + | |-- js/ + | | `-- .js + | |-- css/ + | | `-- .css + | |-- less/ + | | `-- .less + | `-- xml/ + | `-- .xml + `-- views/ + |-- _templates.xml + |-- _views.xml + |-- _templates.xml + `-- _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 ** is only used to set not-updatable data with ``noupdate=1`` + +.. code-block:: xml + + + view.name + object_name + + + + + + + + + + +Naming xml_id +------------- + +Security, View and Action +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the following pattern : + +* For a menu : *_menu* +* For a view : *_view_*, where *view_type* is kanban, form, tree, search, ... +* For an action : the main action respects *_action*. + Others are suffixed with *_*, 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 : *_group_* where *group_name* + is the name of the group, genrally 'user', 'manager', ... +* For a rule : *_rule_* 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 + + + + ... + + + + ... + + + + ... + + + + + ... + + + + ... + + + + + ... + + + + ... + + + + ... + + + + +.. 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 *_inherit_*. A module can extend a view only one time, suffix the orginal name with *_inherit_*, where *current_module_name* is the technical name of the module extending the view. + + +.. code-block:: xml + + + ... + + + +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_* + - Search method : the search method pattern is *_search_* + - Default method : the default method pattern is *_default_* + - Onchange method : the onchange method pattern is *_onchange_* + - Constraint method : the constraint method pattern is *_check_* + - 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_* 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 +