Соглашение об оформлении кода в Odoo - Odoo 8.0

This page introduce the new Odoo Coding Guidelines. These guidelines aim to improve the quality of the code (better readability of source, ...) and Odoo Apps. Indeed, proper code ought ease maintenance, aid debugging, lower complexity and promote reliability.

Структура модуля

Каталоги

A module is organised in a few directory :

  • data/ : демо-данные и файлы данных xml

  • models/ : здесь определяются модели

  • controllers/ : contains controllers (http routes).
  • views/ : содержит представления и шаблоны

  • static/ : содержит веб ассеты, подразделяется на подкаталоги по содержимому css/, js/, img/, lib/, ...

Наименование файлов

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 set select a main model, this model gives its name to the set. If there is only one model, 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 introduces 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 look like

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

XML файлы

Формат

When declaring a record in XML,

  • Поставьте атрибут id перед атрибутом model

  • При объявления поля первым ставиться атрибут name. Затем поместите значение поля в тег field затем атрибут eval и, наконец, другие атрибуты (widget, options, ...), следующие в порядке важности.

  • Try to group the record by model. In case of dependencies between action/menu/views, the convention may not be applicable.
  • Использовать соглашение об создании имен, определенное далее

  • The tag <data> is only used to set not-updatable data with noupdate=1
<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" />
        </tree>
    </field>
</record>

Naming xml_id

Безопасность, Представление и Действие

Используйте следующий шаблон:

  • For a menu: <model_name>_menu
  • Для представления: <model_name>_view_<view_type>, где view_type может быть kanban, form, tree, search, ...

  • Для действия: основное действие соответствует <model_name>_action. Остальные имеют суффикс _<detail>, где detail запись прописными буквами поясняющая действие. Суффиксы используются только для тех моделей, где есть объявление нескольких действий.

  • Для группы пользователей: <model_name>_group_<group_name> где group_name является именем группы, как правило user, manager, ...

  • Для правила безопасности: <model_name>_rule_<concerned_group> где concerned_group короткое имя соответствующей группы (user для model_name_group_user, public для неавторизованных пользователей, company для систем с множеством компаний, ...).

<!-- 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>

Inherited XML

The naming pattern of inherited view is <base_view>_inherit_<current_module_name>. A module may only extend a view once. Suffix the orginal name with _inherit_<current_module_name> where current_module_name is the technical name of the module extending the view.

<record id="inherited_model_view_form_inherit_my_module" model="ir.ui.view">
    ...
</record>

Python

Параметры PEP8

Использование linter может помочь в подсветке синтаксиса, семантических предупреждений или ошибок. Исходный код Odoo пытается уважать стандарты Python, но некоторые из них можно игнорировать.

  • E501: слишком длинная строка

  • E301: ожидается 1 пустая строка, найдено 0

  • E302: ожидается 2 пустые строки, найдено 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 '# '

Import

Порядок импорта библиотек определен следующим образом

  1. Внешние библиотеки (по одной в строке, отсортированные и разделенные в python stdlib)

  2. Imports of openerp
  3. Импорт из модулей Odoo (редко, и только при необходимости)

Внутри этих 3-х групп строки сортируются по алфавиту.

# 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)
  • Используйте осмысленные имена переменных/классов/методов

Symbols

  • Odoo Python Class : use camelcase for code in api v8, underscore lowercase notation for old api.
class AccountInvoice(models.Model):
    ...

class account_invoice(osv.osv):
    ...
  • Имя переменной:
    • Используйте camelcase при определении переменных в модели

    • Используйте нижний регистр и подчеркивание при определении обычных переменных.

    • 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.
ResPartner = self.env['res.partner']
partners = ResPartner.browse(ids)
partner_id = partners[0].id
  • Поля One2Many и Many2Many всегда должны иметь _ids в качестве суффикса (например: sale_order_line_ids)

  • Поля Many2One должны иметь _id в качестве суффикса (пример: partner_id, user_id, ...)

  • Соглашения о методах
    • Compute Field : паттерн метода вычисляемого поля _compute_<field_name>

    • Search method: паттерн метода поиска _search_<field_name>

    • Default method: паттерн метода по умолчанию _default_<field_name>

    • Onchange method: паттерн метода onchange _onchange_<field_name>

    • Constraint method : паттерн метода ограничения _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.
  • В атрибутах модели данных порядок должен быть следующим
    1. Приватные атрибуты (_name, _description, _inherit, ...)

    2. Метод по умолчанию и _default_get

    3. Объявления полей

    4. Compute and search methods in the same order as field declaration
    5. Методы ограничений (@api.constrains) и onchange методы (@api.onchange)

    6. Методы CRUD (переопределения ORM)

    7. Методы действий

    8. И, наконец, другие методы, описывающие бизнес-логику.

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 и CSS

For javascript :

  • use strict; рекомендуется использовать во всех файлах javascript

  • Используйте linter (jshint, ...)

  • Никогда не добавляйте минифицированные библиотеки Javascript

  • Используйте camelcase для объявления класса

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
  • Используйте нижний регистр для обозначения класса

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 include a meaningful commit message: it should be self explanatory (long enough) including the name of the module that has been changed and the reason behind the change. Do not use single words like "bugfix" or "improvements".
  • Avoid commits which simultaneously impact multiple modules. Try to split into different commits where impacted modules are different (It will be helpful if we need to revert a module separately).
[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 ...