diff options
| author | Jérome Maes <jem@openerp.com> | 2015-03-31 14:37:31 +0200 |
|---|---|---|
| committer | Jérome Maes <jem@openerp.com> | 2015-03-31 17:49:41 +0200 |
| commit | b9977ef5d30d98017b9f2d96bf151511b2eeafd2 (patch) | |
| tree | ec9f88824f60e1caaa91dd682e19d6611074e6ef /doc/reference/guidelines.rst | |
| parent | 27594146b518b94a8ae9600a93a4ed42d9a5b755 (diff) | |
[ADD] doc : odoo official coding guidelines. This is the first version of the guidelines. Discussions and conventions are not frozen.
Diffstat (limited to 'doc/reference/guidelines.rst')
| -rw-r--r-- | doc/reference/guidelines.rst | 424 |
1 files changed, 424 insertions, 0 deletions
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 <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 + |
