This page introduces the Odoo Coding Guidelines. Those aim to improve the quality of Odoo Apps code. Indeed proper code improves readability, eases maintenance, helps debugging, lowers complexity and promotes reliability. These guidelines should be applied to every new module and to all new development.
Предупреждение
When modifying existing files in stable version the original file style strictly supersedes any other style guidelines. In other words please never modify existing files in order to apply these guidelines. It avoids disrupting the revision history of code lines. Diff should be kept minimal. For more details, see our pull request guide.
Предупреждение
When modifying existing files in master (development) version apply those guidelines to existing code only for modified code or if most of the file is under revision. In other words modify existing files structure only if it is going under major changes. In that case first do a move commit then apply the changes related to the feature.
Структура модуля
Каталоги
A module is organised in important directories. Those contain the business logic; having a look at them should make you 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
Наименование файлов
Для объявления представлений для бэкэнда и фронтэнда, разделите их на два разных файла
Для моделей данных, разделите бизнес-логику на несколько наборов моделей данных, в каждом наборе выберите основную модель, она даст свое имя набору. Если есть только одна модель данных, то ее имя будет являться именем модуля. Для каждого набора с именем <main_model> могут быть созданы следующие файлы:
models/<main_model>.py
models/<inherited_main_model>.py
views/<main_model>_templates.xml
views/<main_model>_views.xml
Например, модуль sale объявляет модели данных sale_order
и sale_order_line
, где доминирует sale_order
. Таким образом, файлы <main_model>
будут называться: models/sale_order.py
и views/sale_order_views.py
.
Для data разделите их по видам: демо или служебные данные. Имя файла будет именем main_model, с суффиксом _demo.xml или _data.xml.
Для контроллеров, единственный файл должен называться main.py. В противном случае, если вам нужно наследовать существующий контроллер от другого модуля, его имя будет <module_name>.py. В отличие от моделей, каждый класс контроллера должен содержаться в отдельном файле.
Для статических файлов, поскольку ресурсы могут использоваться в разных контекстах (frontend, backend или оба), они будут включены только в один пакет. Таким образом, CSS/Less, JavaScript и XML-файлы должны быть дополнены именем типа пакета. Т.е.: im_chat_common.css, im_chat_common.js для комплекта «assets_common» и im_chat_backend.css, im_chat_backend.js для пакета «assets_backend». Если модулю принадлежит только один файл, его имя будет <имя_модуля>.ext (т.е. project.js). Не ссылайтесь на данные (изображения, библиотеки) за пределами Odoo: не используйте ссылки на изображения - копируйте изображения в соотвествующий каталог.
Что касается данных, разделите их по целям: данные или демо. Имя будет начинаться main_model, а заканчиваться суффиксом _data.xml или _demo.xml.
Что касается мастеров, имена должны быть такими:
<main_transient>.py
<main_transient>_views.xml
Где <main_transient> - имя доминирующей модели переходных процессов, как и для моделей данных. <main_transient>.py содержит модели 'model.action' и 'model.action.line'.
Имена статистических отчетов должны выглядеть так:
<report_name_A>_report.py
<report_name_A>_report_views.py
(часто сводные и графические представления)
Имена отчетов для печати должны быть такими:
<print_report_name>_reports.py
(действия с отчетами, определение формата бумаги, ...)<print_report_name>_templates.xml
(шаблоны отчетов xml)
Полное дерево каталогов должно выглядеть следующим образом:
addons/<my_module_name>/
|-- __init__.py
|-- __manifest__.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
. If there is only not-updatable data in the file, thenoupdate=1
can be set on the<odoo>
tag and do not set a<data>
tag.
<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 supports custom tags acting as syntactic sugar:
menuitem: использовать его как ярлык для объявления
ir.ui.menu
template: использовать его для объявления QWeb View, требующего только раздел
arch
представления.report: использовать для объявления действия отчета
Act_window: используйте его, если запись записи не может делать то, что вы хотите
4 первых тега предпочтительнее записи * record [UNKNOWN NODE problematic].
Формирование имен xml_id
Безопасность, Представление и Действие
Используйте следующий шаблон:
для меню:
<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>
Примечание
Имена представлений используют точечную нотацию my.model.view_type
или my.model.view_type.inherit
вместо * "This is the form view of My Model" [UNKNOWN NODE problematic].
Унаследованный XML-файл
Шаблон для формирования имени расширенного представления <base_view>_inherit_<current_module_name>
.Модуль может быть создан исключительно для расширения представления. Создайте суффикс для оригинального имени _inherit_<current_module_name>
где current_module_name техническое имя модуля расширяющего представление.
<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: строка продолжается надписью для подвешивания отступа
E123: закрывающая скобка не соответствует отступу строки открывающей скобки
E127: строка заходит за отступ для визуализации отступа
E128: строка продолжения под отступом для визуального отступа
E265: комментарий блока должен начинаться с '#'
Импорт
Порядок импорта библиотек определен следующим образом:
Внешние библиотеки (по одной в строке, отсортированные и разделенные в 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
Программирование на идиотском языке Python
Каждый файл python должен иметь `` # - * - coding: utf-8 - * - `` в первой строке.
Всегда одобряйте * читаемость * над * краткость * или использование языковых функций или идиом.
Не используйте
.clone ()
`
# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)
- Python dictionary : 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']}
Несколько точек возврата в порядке, когда они проще
# 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' в my_dict`` и `` если 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). Среди них есть коллекции (списки, диктовки, наборы, ...), которые являются «фальшивыми», когда они пусты и «истинны», когда содержат элементы:
bool([]) is False
bool([1]) is True
bool([False]) is True
Таким образом, вы можете написать `` if some_collection: `` вместо `` if len (some_collection): [UNKNOWN NODE problematic].
Итерация по итерируемым элементам
# creates a temporary list and looks bar
for key in my_dict.keys():
"do something..."
# better
for key in my_dict:
"do something..."
# accessing the key,value pair
for key, value in my_dict.items():
"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, используйте методы `` filter``, `` mapped``,
sorted`
, ..., чтобы облегчить чтение кода и производительность.
Make your method work in batch
When adding a function, make sure it can process multiple records. Typically,
such methods are decorated with the api.multi
decorator. 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()
Избегайте использования декоратора `` api.one``: это, вероятно, не будет работать так, как вы ожидали, и расширение такого метода не так просто, как метод * api.multi [UNKNOWN NODE problematic], поскольку он возвращает список результатов (упорядоченный по набору записей идентификаторы).
При возникновении проблем с производительностью при разработке кнопки «stat» (например) не выполняйте `` поиск`` или `` search_count`` в цикле в методе `` api.multi``. Рекомендуется использовать метод `` read_group``, чтобы вычислить все значения только в одном запросе.
@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)
Распространение контекста
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
Параметр передачи в контексте может иметь опасные побочные эффекты. Поскольку значения распространяются автоматически, может появиться некоторое поведение. При вызове метода create ()
модели с ключом * default_my_field * в контексте будет задано значение по умолчанию * my_field * для соответствующей модели. Но если исцелить это создание, будет установлен и другой объект (например, sale.order.line, при создании продажи.порядка) с именем поля * my_field [UNKNOWN NODE problematic], значение по умолчанию.
Если вам нужно создать ключевой контекст, влияющий на поведение какого-либо объекта, выберите хорошее имя и в конечном итоге префикс его именем модуля, чтобы изолировать его воздействие. Хорошим примером являются ключи модуля `` mail``: * mail_create_nosubscribe [UNKNOWN NODE problematic], * mail_notrack [UNKNOWN NODE problematic], * mail_notify_user_signature [UNKNOWN NODE problematic], ...
Не обходите 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)
Держите свои методы короткими / простыми, когда это возможно
Функции и методы не должны содержать слишком много логики: иметь много маленьких и простых методов более целесообразно, чем иметь несколько больших и сложных методов. Хорошее эмпирическое правило состоит в том, чтобы разбить метод, как только: - он имеет более одной ответственности (см. Http://en.wikipedia.org/wiki/Single_responsibility_principle) - он слишком велик, чтобы уместиться на одном экране.
Кроме того, назовите свои функции соответственно: маленькие и правильно названные функции являются отправной точкой кода для чтения / сопровождения и более жесткой документации.
Эта рекомендация также относится к классам, файлам, модулям и пакетам. (См. Также 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 () `` сами, ** UNLESS [UNKNOWN NODE problematic], вы явно создали свой собственный курсор базы данных! И ситуации, когда вам нужно это делать, являются исключительными!
И, кстати, если вы создали свой собственный курсор, вам нужно обрабатывать случаи ошибок и правильный откат, а также правильно закрывать курсор, когда вы закончите с ним.
И вопреки распространенному мнению, вам даже не нужно вызывать cr.commit ()
в следующих ситуациях: - в методе
_auto_init ()
объекта * models.Model [UNKNOWN NODE problematic]: это сделано Уход с помощью метода инициализации аддонов или транзакция ORM при создании пользовательских моделей - в отчетах: `` commit () `` обрабатывается инфраструктурой, так что вы можете обновлять базу данных даже изнутри отчета - в пределах * Models.Transient [UNKNOWN NODE problematic]: эти методы называются точно так же, как и обычные * модели. Моделей [UNKNOWN NODE problematic], внутри транзакции и с соответствующим `` cr.commit () / rollback () `` в конце - и т. Д. (См. Общее правило Выше, если у вас есть сомнения!)
Начиная с этого момента все вызовы `` cr.commit () `` вне рамок сервера должны иметь ** явный комментарий [UNKNOWN NODE problematic], объясняющий, почему они абсолютно необходимы, почему они действительно правильны и почему они не нарушают транзакции. В противном случае их можно и удалять!
Правильно используйте метод перевода
В Odoo используется похожий на GetText метод под названием "underscore" _()
, чтобы указать, что статическая строка, используемая в коде, должна быть переведена во время выполнения, используя язык контекста. Этот псевдо-метод доступен в вашем коде путем импорта следующим образом:
from odoo.tools.translate import _
Необходимо соблюдать несколько очень важных правил при его использовании, чтобы оно работало и чтобы избежать заполнения переводов бесполезным хламом.
В принципе, этот метод следует использовать только для статических строк, написанных вручную в коде, он не будет работать для перевода значений полей, таких как имена продуктов и т. Д. Это должно быть сделано вместо этого с использованием флага перевода в соответствующем поле.
Правило очень просто: вызовы метода подчёркивания всегда должны быть в форме _ ('literal string')
[UNKNOWN NODE problematic]и ничего больше:
# 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, когда манипулируют строками, предпочитаете % `` over
.format () `` (когда только одна переменная заменяется в строке), и предпочитаете [UNKNOWN NODE problematic]% (varname) `` вместо position ( Когда необходимо заменить несколько переменных). Это облегчает перевод переводчикам сообщества.
Символы и обозначения
- Название модели (с использованием точечной нотации, префикс по имени модуля):
При определении Оду-модели: используйте единственную форму имени (* res.partner * и * sale.order * вместо * res.partnerS * и * saleS.orderS [UNKNOWN NODE problematic])
При определении переходника Odoo (wizard): используйте `` <related_base_model>. <Action> `` where * related_base_model * - базовая модель (определенная в * models / [UNKNOWN NODE problematic]), относящаяся к переходному процессу, а * action * - это короткое имя Того, что делает переходный процесс. Например: `` account.invoice.make``, `` project.task.delegate.batch``, ...
При определении * report * model (SQL views ei): используйте `` <related_base_model> .report. <Action> [UNKNOWN NODE problematic], исходя из соглашения Transient.
- Odoo Python Class : use camelcase for code.
class AccountInvoice(models.Model):
...
- Имя переменной:
Используйте camelcase при определении переменных в модели данных
Используйте нижний регистр и подчеркивание при определении обычных переменных.
- Odoo works with a record or a recordset, don't suffix variable names with _id or _ids if they don't contain an id or a list of ids.
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_<имя_поля>
Search method: шаблон метода поиска _search_<field_name>
Default method: шаблон метода по умолчанию _default_<field_name>
Onchange method: шаблон метода onchange _onchange_<field_name>
Constraint method : шаблон метода ограничения _check_<constraint_name>
Action method : начинается с префикса action_. Его декоратор
@api.multi
, но когда используется только одна запись добавьте в начале методаself.ensure_one()
.
- В атрибутах модели данных порядок должен быть следующим
Частные атрибуты (
_name
,_description
,_inherit
, ...)Метод по умолчанию и
_default_get
Объявления полей
Методы поиска и вычисления. Они должны идти в том же порядке, что и объявление поля
Методы ограничений (
@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
Static files organization
Odoo addons have some conventions on how to structure various files. We explain here in more details how web assets are supposed to be organized.
The first thing to know is that the Odoo server will serve (statically) all files located in a static/ folder, but prefixed with the addon name. So, for example, if a file is located in addons/web/static/src/js/some_file.js, then it will be statically available at the url your-odoo-server.com/web/static/src/js/some_file.js
The convention is to organize the code according to the following structure:
- static: all static files in general
- static/lib: this is the place where js libs should be located, in a sub folder. So, for example, all files from the jquery library are in addons/web/static/lib/jquery
- static/src: the generic static source code folder
- static/src/css: all css files
- static/src/fonts
- static/src/img
- static/src/js
- static/src/less: less files
- static/src/xml: all qweb templates that will be rendered in JS
- static/tests: this is where we put all test related files.
Javascript coding guidelines
use strict;
рекомендуется во всех файлах javascriptИспользуйте linter (jshint, ...)
Никогда не добавляйте минифицированные библиотеки Javascript
Используйте camelcase для объявления класса
Если ваш код не должен запускаться на каждой странице, укажите целевые страницы с помощью функции
if_dom_contains
модуля сайта. Нацельте элемент, который является специфическим для страниц, которые ваш код должен запускать с использованием JQuery.
odoo.website.if_dom_contains('.jquery_class_selector', function () {
/*your code here*/
});
CSS coding guidelines
Префикс всех ваших классов с o_<module_name> где module_name - техническое имя модуля ('sale', 'im_chat', ...) или основной маршрут, зарезервированный модулем (для модуля сайта главным образом, т.е. : 'o_forum' для модуля website_forum). Единственным исключением для этого правила является webclient: он просто использует префикс o_.
Избегайте использования id
Использовать собственные классы Bootstrap
Используйте нижний регистр для обозначения класса
Git
Configure your git
Based on ancestral experience and oral tradition, the following things go a long way towards making your commits more helpful:
Be sure to define both the user.email and user.name in your local git config
git config --global <var> <value>
- Be sure to add your full name to your Github profile here. Please feel fancy and add your team, avatar, your favorite quote, and whatnot ;-)
Commit message structure
Commit message has four parts: tag, module, short description and full description. Try to follow the preferred structure for your commit messages
[TAG] module: describe your change in a short sentence (ideally < 50 chars)
Long version of the change description, including the rationale for the change,
or a summary of the feature being introduced.
Please spend a lot more time describing WHY the change is being done rather
than WHAT is being changed. This is usually easy to grasp by actually reading
the diff. WHAT should be explained only if there are technical choices
or decision involved. In that case explain WHY this decision was taken.
End the message with references, such as task or bug numbers, PR numbers, and
OPW tickets, following the suggested format:
Related to task #taskId
Fixes #12345 (link and close issue on Github)
Closes #7865 (link and close PR on Github)
OPW-112233
Tag and module name
Tags are used to prefix your commit. They should be one of the following
- [FIX] for bug fixes: mostly used in stable version but also valid if you are fixing a recent bug in development version;
- [REF] for refactoring: when a feature is heavily rewritten;
- [ADD] for adding new modules;
- [REM] for removing resources: removing dead code, removing views, removing modules, ...;
- [REV] for reverting commits: if a commit causes issues or is not wanted reverting it is done using this tag;
- [MOV] for moving files: use git move and do not change content of moved file otherwise Git may loose track and history of the file; also used when moving code from one file to another;
- [REL] for release commits: new major or minor stable versions;
- [IMP] for improvements: most of the changes done in development version are incremental improvements not related to another tag;
- [MERGE] for merge commits: used in forward port of bug fixes but also as main commit for feature involving several separated commits;
- [CLA] for signing the Odoo Individual Contributor License;
- [I18N] for changes in translation files;
After tag comes the modified module name. Use the technical name as functional name may change with time. If several modules are modified, list them or use various to tell it is cross-modules. Unless really required or easier avoid modifying code across several modules in the same commit. Understanding module history may become difficult.
Commit message header
After tag and module name comes a meaningful commit message header. It should be self explanatory and include the reason behind the change. Do not use single words like "bugfix" or "improvements". Try to limit the header length to about 50 characters for readability.
Commit message header should make a valid sentence once concatenated with
if applied, this commit will <header>
. For example [IMP] base: prevent to
archive users linked to active partners
is correct as it makes a valid sentence
if applied, this commit will prevent users to archive...
.
Commit message full description
In the message description specify the part of the code impacted by your changes (module name, lib, transversal object, ...) and a description of the changes.
First explain WHY you are modifying code. What is important if someone goes back to your commit in about 4 decades (or 3 days) is why you did it. It is the purpose of the change.
What you did can be found in the commit itself. If there was some technical choices involved it is a good idea to explain it also in the commit message after the why. For Odoo R&D developers "PO team asked me to do it" is not a valid why, by the way.
Please 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 changes in a given module separately.
Don't hesitate to be a bit verbose. Most people will only see your commit message and judge everything you did in your life just based on those few sentences. No pressure at all.
You spend several hours, days or weeks working on meaningful features. Take some time to calm down and write clear and understandable commit messages.
If you are an Odoo R&D developer the WHY should be the purpose of the task you are working on. Full specifications make the core of the commit message. If you are working on a task that lacks purpose and specifications please consider making them clear before continuing.
Finally here are some examples of correct commit messages :
[REF] models: use `parent_path` to implement parent_store
This replaces the former modified preorder tree traversal (MPTT) with the
fields `parent_left`/`parent_right`[...]
[FIX] account: remove frenglish
[...]
Closes #22793
Fixes #22769
[FIX] website: 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.
Примечание
Используйте длинное описание, чтобы объяснить почему а не что, что и так можно увидеть в diff