This page introduces the new Odoo Coding Guidelines. Those aim to improve the quality of the code (e.g. better readability of source) and Odoo Apps. Indeed, proper code eases maintenance, aids debugging, lowers complexity and promotes reliability.
These guidelines should be applied to every new module, and new developpment. These guidelines will be applied to old module only in case of code refactoring (migration to new API, big refactoring, ...).
Предупреждение
These guidelines are written with new modules and new files in mind. When modifying existing files, the original style of the file strictly supersedes any other style guidelines. In other words, never modify existing files in order to apply these guidelines, to avoid disrupting the revision history of each line. For more details, see our pull request guide.
Структура модуля
Каталоги
A module is organised in important directories. Those contain the business logic; having a look at them should make understand the purpose of the module.
data/ : демо-данные и файлы данных xml
models/ : здесь определяются модели
- controllers/ : contains controllers (HTTP routes).
views/ : содержит представления и шаблоны
static/ : содержит веб ассеты, подразделяется на подкаталоги по содержимому css/, js/, img/, lib/, ...
Модуль содержит дополнительные необязательные каталоги.
- wizard/ : regroups the transient models (formerly osv_memory) and their views.
- report/ : contains the reports (RML report [deprecated], models based on SQL views (for reporting) and other complex reports). Python objects and XML views are included in this directory.
- tests/ : contains the Python/YML tests
Наименование файлов
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 controllers, the only file should be named main.py. Otherwise, if you need to inherit an existing controller from another module, its name will be <module_name>.py. Unlike models, each controller class should be contained in a separated file.
For static files, since the resources can be used in different contexts (frontend, backend, both), they will be included in only one bundle. So, CSS/Less, JavaScript and XML files should be suffixed with the name of the bundle type. i.e.: im_chat_common.css, im_chat_common.js for 'assets_common' bundle, and im_chat_backend.css, im_chat_backend.js for 'assets_backend' bundle. If the module owns only one file, the convention will be <module_name>.ext (i.e.: project.js). Don't link data (image, libraries) outside Odoo: do not use an URL to an image but copy it in our codebase instead.
Regarding data, split them by purpose: data or demo. The filename will be the main_model name, suffixed by _data.xml or _demo.xml.
Regarding wizards, naming convention is :
<main_transient>.py
<main_transient>_views.xml
Where <main_transient> is the name of the dominant transient model, just like for models. <main_transient>.py can contains the models 'model.action' and 'model.action.line'.
For statistics reports, their names should look like :
<report_name_A>_report.py
<report_name_A>_report_views.py
(often pivot and graph views)
For printable reports, you should have :
<print_report_name>_reports.py
(report actions, paperformat definition, ...)<print_report_name>_templates.xml
(xml report templates)
The complete tree should look like
addons/<my_module_name>/
|-- __init__.py
|-- __openerp__.py
|-- controllers/
| |-- __init__.py
| |-- <inherited_module_name>.py
| `-- main.py
|-- data/
| |-- <main_model>_data.xml
| `-- <inherited_main_model>_demo.xml
|-- models/
| |-- __init__.py
| |-- <main_model>.py
| `-- <inherited_main_model>.py
|-- report/
| |-- __init__.py
| |-- <main_stat_report_model>.py
| |-- <main_stat_report_model>_views.xml
| |-- <main_print_report>_reports.xml
| `-- <main_print_report>_templates.xml
|-- 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
`-- wizard/
|-- <main_transient_A>.py
|-- <main_transient_A>_views.xml
|-- <main_transient_B>.py
`-- <main_transient_B>_views.xml
Примечание
Имена файлов должны содержать только [a-z0-9_]
(строчные буквы, цифры и _
)
Предупреждение
Используйте правильные разрешения для файлов: для каталогов - 755, файлов - 644.
XML файлы
Формат
Чтобы объявить запись в XML, рекомендуется использовать запись record (с использованием <record>):
Поставьте атрибут
id
перед атрибутомmodel
При объявления поля первым ставиться атрибут
name
. Затем поместите значение поля в тегfield
затем атрибутeval
и, наконец, другие атрибуты (widget, options, ...), следующие в порядке важности.Попробуйте сгруппировать записи по модели. В случае зависимостей между действием/меню/представлениями это соглашение может быть неприменимым.
Использовать соглашение об создании имен, определенное далее
- 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>
Odoo поддерживает кастомные теги, действующие как синтаксический сахар:
menuitem: использовать его как ярлык для объявления
ir.ui.menu
- workflow: the <workflow> tag sends a signal to an existing workflow.
template: использовать его для объявления QWeb представления, требующего только раздел
arch
.report: используется для объявления действия отчета
act_window: используется, если определение записи не может делать то, что вы хотите
The 4 first tags are prefered over the record notation.
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_view_form" model="ir.ui.view">
...
</record>
<record id="model_name_view_kanban" model="ir.ui.view">
...
</record>
<menuitem
id="model_name_menu_root"
name="Main Menu"
sequence="5"
/>
<menuitem
id="model_name_menu_action"
name="Sub Menu 1"
parent="module_name.module_name_menu_root"
action="model_name_action"
sequence="10"
/>
<!-- 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="module_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>
Примечание
View names 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 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
Порядок импорта библиотек определен следующим образом
Внешние библиотеки (по одной в строке, отсортированные и разделенные в python stdlib)
Импорт самого
odoo
Импорт из модулей Odoo (редко, и только при необходимости)
Внутри этих 3-х групп строки сортируются по алфавиту.
# 1 : imports of python lib
import base64
import re
import time
from datetime import datetime
# 2 : imports of odoo
import odoo
from odoo import api, fields, models # alphabetically ordered
from odoo.tools.safe_eval import safe_eval as eval
from odoo.tools.translate import _
# 3 : imports from odoo modules
from odoo.addons.website.models.website import slug
from odoo.addons.web.controllers.main import login_redirect
Idiomatics Python Programming
Каждый файл python должен иметь
# -*- coding: utf-8 -*-
в первой строке.Всегда предпочитайте читаемость над краткостью или использование языковых функций или идиом.
Не используйте [UNKNOWN NODE problematic].clone()` `
# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)
- Python dictionnary : creation and update
# -- creation empty dict
my_dict = {}
my_dict2 = dict()
# -- creation with values
# bad
my_dict = {}
my_dict['foo'] = 3
my_dict['bar'] = 4
# good
my_dict = {'foo': 3, 'bar': 4}
# -- update dict
# bad
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# good
my_dict.update(foo=3, bar=4, baz=5)
my_dict = dict(my_dict, **my_dict2)
Используйте осмысленные имена переменных/классов/методов
Бесполезная переменная: временные переменные могут сделать код более понятным, указав имена для объектов, но это не означает, что вам нужно постоянно создавать временные переменные:
# pointless
schema = kw['schema']
params = {'schema': schema}
# simpler
params = {'schema': kw['schema']}
Несколько return это нормально в тех случаях, когда это делает код проще
# a bit complex and with a redundant temp variable
def axes(self, axis):
axes = []
if type(axis) == type([]):
axes.extend(axis)
else:
axes.append(axis)
return axes
# clearer
def axes(self, axis):
if type(axis) == type([]):
return list(axis) # clone the axis
else:
return [axis] # single-element list
Знайте свои встроенные функции: вы должны по крайней мере иметь общее представление о всех стандартных функциях Python (http://docs.python.org/library/functions.html)
value = my_dict.get('key', None) # very very redundant
value= my_dict.get('key') # good
Кроме того, if 'key' in my_dict
и if my_dict.get('key')
имеют разное значение, убедитесь, что используете правильное.
Изучите списки: Использование возможностей списков, словарей базовые манипуляции с помощью
map
,filter
,sum
, ... Они делают код более легким для чтения.
# not very good
cube = []
for i in res:
cube.append((i['id'],i['name']))
# better
cube = [(i['id'], i['name']) for i in res]
Коллекции также булевы: в python многие объекты имеют значение типа «boolean-ish» при оценке в логическом контексте (например, if). Среди них есть коллекции (списки, словари, наборы, ...), которые являются «falsy», когда они пусты и «truthy», когда содержат элементы:
bool([]) is False
bool([1]) is True
bool([False]) is True
Таким образом, вы можете написать if some_collection:
вместо if len (some_collection):
.
Перебор итерируемых объектов
# creates a temporary list and looks bar
for key in my_dict.keys():
"do something..."
# better
for key in my_dict:
"do something..."
# creates a temporary list
for key, value in my_dict.items():
"do something..."
# only iterates
for key, value in my_dict.iteritems():
"do something..."
Используйте dict.setdefault
# longer.. harder to read
values = {}
for element in iterable:
if element not in values:
values[element] = []
values[element].append(other_value)
# better.. use dict.setdefault method
values = {}
for element in iterable:
values.setdefault(element, []).append(other_value)
Как хороший разработчик, документируйте свой код (docstring по методам, простые комментарии для сложной части кода)
В дополнение к этим рекомендациям вы также можете найти следующую ссылку интересную: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html (немного устаревшая, но весьма актуальная)
Программирование в Odoo
Избегайте создания генераторов и декораторов: используйте только те, что предусмотрены API Odoo.
Как и в python, используйте методы
filtered
,mapped
,sorted
, ..., чтобы облегчить чтение кода и производительность.
Make your method works in batch
When adding a function, make sure it can process multiple records. Typically,
such method is decorated with api.multi
decorator (or takes a list of id,
if written in old api). Then you will have to iterate on self
to treat each
record.
@api.multi
def my_method(self)
for record in self:
record.do_cool_stuff()
Avoid to use api.one
decorator : this will probably not do what you expected,
and extending a such method is not as easy than a api.multi method, since it
returns a list of result (ordered by recordset ids).
For performance issue, when developping a 'stat button' (for instance), do not
perform a search
or a search_count
in a loop in a api.multi
method. It
is recommended to use read_group
method, to compute all value in only one request.
@api.multi
def _compute_equipment_count(self):
""" Count the number of equipement per category """
equipment_data = self.env['hr.equipment'].read_group([('category_id', 'in', self.ids)], ['category_id'], ['category_id'])
mapped_data = dict([(m['category_id'][0], m['category_id_count']) for m in equipment_data])
for category in self:
category.equipment_count = mapped_data.get(category.id, 0)
Распространение контекста
In new API, the context is a frozendict
that cannot be modified. To call
a method with a different context, the with_context
method should be used :
records.with_context(new_context).do_stuff() # all the context is replaced
records.with_context(**additionnal_context).do_other_stuff() # additionnal_context values override native context ones
Passing parameter in context can have dangerous side-effects. Since the values
are propagated automatically, some behavior can appears. Calling create()
method of a model with default_my_field key in context will set the default
value of my_field for the concerned model. But if curing this creation, other
object (such as sale.order.line, on sale.order creation) having a field
name my_field, their default value will be set too.
Если вам нужно создать ключ контекста, влияющий на поведение какого-либо объекта, выберите хорошее имя а лучше присвойте префикс с именем модуля, чтобы изолировать его влияние. Хорошим примером являются ключи модуля mail
: mail_create_nosubscribe, mail_notrack*, mail_notify_user_signature, ...
Не обходите ORM
Вы никогда не должны использовать курсор базы данных напрямую в случаях, когда ORM может сделать то же самое! Поступая таким образом, вы обходите все функции ORM такие как, транзакции, права доступа и т. д.
И, скорее всего, вы также делаете код более трудным для чтения и менее безопасным.
# very very wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in (' + ','.join(map(str, ids))+') AND state=%s AND obj_price > 0', ('draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# no injection, but still wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in %s '\
'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]
# better
auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)])
Никаких инъекций SQL, пожалуйста!
Следует проявлять осторожность, чтобы не вводить уязвимости SQL-инъекций при использовании запросов SQL вручную. Уязвимость появляется, когда пользовательский ввод либо неправильно фильтруется или содержит неверные кавычки, что позволяет злоумышленнику вводить нежелательные предложения в SQL-запрос (например, обходить фильтры или выполнять команды UPDATE или DELETE).
Самый лучший способ сохранять безопасность - никогда, НИКОГДА не использовать конкатенацию строковых констант (+) или строковых параметров Python (%) для передачи переменных в строку запроса SQL.
Вторая причина, которая не менее же важна, заключается в том, что работа уровня абстракции базы данных (psycopg2) это решить, как форматировать параметры запроса, а не то что вы написали! Например, psycopg2 знает, что когда вы передаете список значений, он должен отформатировать их как список, разделенный запятыми, заключенный в круглые скобки!
# the following is very bad:
# - it's a SQL injection vulnerability
# - it's unreadable
# - it's not your job to format the list of ids
self.env.cr.execute('SELECT distinct child_id FROM account_account_consol_rel ' +
'WHERE parent_id IN ('+','.join(map(str, ids))+')')
# better
self.env.cr.execute('SELECT DISTINCT child_id '\
'FROM account_account_consol_rel '\
'WHERE parent_id IN %s',
(tuple(ids),))
Это очень важно, поэтому будьте внимательны и при рефакторинге, и, самое главное, не копируйте эти шаблоны!
Вот незабываемый пример, который поможет вам вспомнить, в чем проблема (но не копируйте код там). Прежде чем продолжить, обязательно прочтите онлайн-документацию pyscopg2, чтобы узнать, как правильно ее использовать:
Проблема с параметрами запроса (http://initd.org/psycopg/docs/usage.html#the-problem-with-the-query-parameters)
Как передавать параметры с помощью psycopg2 (http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries)
Расширенные типы параметров (http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types)
Keep your methods short/simple when possible
Functions and methods should not contain too much logic: having a lot of small and simple methods is more advisable than having few large and complex methods. A good rule of thumb is to split a method as soon as: - it has more than one responsibility (see http://en.wikipedia.org/wiki/Single_responsibility_principle) - it is too big to fit on one screen.
Кроме того, называйте свои функции следующим образом: маленькие и правильно названные функции являются отправной точкой кода для чтения/сопровождения и более жесткой документации.
Эта рекомендация также относится к классам, файлам, модулям и пакетам. (См. Также http://en.wikipedia.org/wiki/Cyclomatic_complexity)
Никогда не совершайте транзакции
Структура Odoo отвечает за обеспечение транзакционного контекста для всех вызовов RPC. Принцип состоит в том, что новый курсор базы данных открывается в начале каждого вызова RPC и фиксируется, когда вызов возвращается, непосредственно перед передачей ответа клиенту RPC примерно так:
def execute(self, db_name, uid, obj, method, *args, **kw):
db, pool = pooler.get_db_and_pool(db_name)
# create transaction cursor
cr = db.cursor()
try:
res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
cr.commit() # all good, we commit
except Exception:
cr.rollback() # error, rollback everything atomically
raise
finally:
cr.close() # always close cursor opened manually
return res
Если во время выполнения вызова RPC возникает какая-либо ошибка, транзакция откатывается автоматически, сохраняя состояние системы.
Аналогичным образом, система также предоставляет выделенную транзакцию во время выполнения наборов тестов, поэтому ее можно откатить или нет в зависимости от параметров запуска сервера.
Следствием того, что вы вручную вызываете cr.commit()
, является очень высокая вероятность краха системы по разным причинам, т.к. вы будете совершать частичные коммиты и, следовательно, частичные и грязные откаты, вызывая следом другие проблемы:
несогласованные бизнес-данные, обычно потеря данных
автоматическая десинхронизация рабочего процесса, постоянно застрявшие документы
тесты, которые нельзя откатить чисто, и они начнут загрязнять базу данных, и инициируют ошибку (это так, даже если во время транзакции не возникла ошибка)
- Вот самое простое правило:
Вы должны НИКОГДА не называть
cr.commit()
сами, ДО ТЕХ ПОР, пока вы явно не создали свой собственный курсор базы данных! И ситуации, когда вам нужно это делать, являются исключительными!И, кстати, если вы создали свой собственный курсор, вам нужно обрабатывать случаи ошибок и правильный откат, а также правильно закрывать курсор, когда вы закончите с ним работать.
И вопреки распространенному мнению, вам даже не нужно вызывать метод cr.commit()
в следующих ситуациях: - в методе _auto_init()
объекта models.Model: это сделано с помощью механизма инициализации модулей или транзакцией ORM при создании пользовательских моделей - в отчетах: commit()
обрабатывается фреймворком, так что вы можете обновлять базу данных даже изнутри отчета - в пределах Models.Transient: эти методы называются точно так же, как и обычные models.Transient, внутри транзакции и с соответствующим cr.commit()/rollback()
в конце - и т.д. (См. Общее правило Выше, если у вас есть сомнения!)
Начиная с этого момента все вызовы cr.commit()
в обход фреймворка должны иметь явный комментарий, объясняющий, почему они абсолютно необходимы, почему они действительно правильны и почему они не нарушают транзакции. В противном случае их можно и нужно удалять!
Правильно используйте метод перевода
В Odoo используется похожий на GetText метод под названием «underscore» _()
, чтобы указать, что статическая строка, используемая в коде, должна быть переведена во время выполнения, используя язык контекста. Этот псевдо-метод доступен в вашем коде путем импорта:
from odoo.tools.translate import _
Необходимо соблюдать несколько очень важных правил при его использовании, чтобы он работало и чтобы избежать заполнения переводов бесполезным хламом.
В принципе, этот метод следует использовать только для статических строк, написанных в коде вручную, он не будет работать для перевода значений полей, таких как имена продуктов и т.д. Вместо этого используйте флага перевода в соответствующем поле.
Правило очень простое: вызовы метода подчёркивания всегда должны быть в форме _ ('literal string')
и ничего больше:
# good: plain strings
error = _('This record is locked!')
# good: strings with formatting patterns included
error = _('Record %s cannot be modified!') % record
# ok too: multi-line literal strings
error = _("""This is a bad multiline example
about record %s!""") % record
error = _('Record %s cannot be modified' \
'after being validated!') % record
# bad: tries to translate after string formatting
# (pay attention to brackets!)
# This does NOT work and messes up the translations!
error = _('Record %s cannot be modified!' % record)
# bad: dynamic string, string concatenation, etc are forbidden!
# This does NOT work and messes up the translations!
error = _("'" + que_rec['question'] + "' \n")
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is out of stock!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is out of stock!" % product.name)
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is not available!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is not available!" % product.name)
# Instead you can do the following and everything will be translated,
# including the product name if its field definition has the
# translate flag properly set:
error = _("Product %s is not available!") % product.name
Кроме того, имейте в виду, что переводчикам придется работать с литеральными значениями, которые передаются функции подчеркивания, поэтому попробуйте сделать их легкими для понимания и свести паразитные символы и форматирование к минимуму. Переводчики должны знать, что шаблоны форматирования, такие как %s или %d , переводы строк и т.д. должны быть сохранены, но важно использовать их разумным и очевидным образом:
# Bad: makes the translations hard to work with
error = "'" + question + _("' \nPlease enter an integer value ")
# Better (pay attention to position of the brackets too!)
error = _("Answer to question %s is not valid.\n" \
"Please enter an integer value.") % question
Вообще в Odoo, когда при работе со строками, предпочитаете % `` вместо
.format()`` (когда только одна переменная заменяется в строке), и старайтесь использовать ключевые занчения [UNKNOWN NODE problematic]%(varname) `` вместо позиционных ( Когда необходимо заменить несколько переменных). Это облегчает работу переводчиков сообщества.
Символы и обозначения
- Название модели (с использованием dot-нотации, префикс по имени модуля):
При определении модели Odoo: используйте форму единственного числа (res.partner и sale.order вместо res.partnerS и saleS.orderS)
- When defining an Odoo Transient (wizard) : use
<related_base_model>.<action>
where related_base_model is the base model (defined in models/) related to the transient, and action is the short name of what the transient do. For instance :account.invoice.make
,project.task.delegate.batch
, ... При определении модели report (SQL представление): используйте
<related_base_model>.report.<action>
, исходя из соглашения для временных моделей.
- Odoo Python Class : use camelcase for code in api v8 (Object-oriented style), underscore lowercase notation for old api (SQL style).
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, addself.ensure_one()
at the beginning of the method.
- В атрибутах модели данных порядок должен быть следующим
Приватные атрибуты (
_name
,_description
,_inherit
, ...)Метод по умолчанию и
_default_get
Объявления полей
- Compute and search methods in the same order as field declaration
Методы ограничений (
@api.constrains
) и onchange методы (@api.onchange
)Методы CRUD (переопределения ORM)
Методы действий
И, наконец, другие методы, описывающие бизнес-логику.
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 of 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 (and name_get, name_search, ...) overrides
def create(self, values):
...
# 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 для объявления класса
- Unless your code is supposed to run on every page, target specific pages
using the
if_dom_contains
function of website module. Target an element which is specific to the pages your code needs to run on using JQuery.
openerp.website.if_dom_contains('.jquery_class_selector', function () {
/*your code here*/
});
For CSS :
Префикс всех ваших классов с o_<module_name> где module_name - техническое имя модуля («sale», «im_chat», ...) или основной url-маршрут, зарезервированный модулем (для модуля сайта главным образом, т.е. : «o_forum» для модуля website_forum). Единственным исключением для этого правила является webclient: он просто использует префикс o_.
- Avoid using id
Используйте родные классы Bootstrap
Используйте нижний регистр для обозначения класса
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
- [MOV] for moving files (Do not change content of moved file, otherwise Git will loose track, and the history will be lost !), or simply moving code from a file to another one.
- [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 ...
Примечание
Используйте длинное описание, чтобы объяснить почему а не что, что и так можно увидеть в diff