На этой странице представлены новые правила кодирования Оду. Они направлены на улучшение качества кода (например, улучшение читаемости исходного кода) и приложений Odoo. Действительно, правильный код облегчает обслуживание, помогает в отладке, снижает сложность и повышает надежность.
Эти рекомендации должны применяться ко всем новым модулям и новым разработкам. Эти рекомендации будут применяться только к старому модулю ** ** в случае рефакторинга кода (переход на новый API, большой рефакторинг, ...).
Предупреждение
Эти рекомендации написаны с учетом новых модулей и новых файлов. При изменении существующих файлов исходный стиль файла строго заменяет любые другие правила стиля. Другими словами, никогда не изменяйте существующие файлы, чтобы применять эти рекомендации, чтобы не нарушать историю изменений каждой строки. Для получения дополнительной информации см. Наше руководство по запросу pull <https://odoo.com/submit-pr> [UNKNOWN NODE problematic]_.
Структура модуля
Каталоги
Модуль организован в важных каталогах. Они содержат бизнес-логику; Взглянув на них, следует понять цель модуля.
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
|-- __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, ...), следующие в порядке важности.Попробуйте сгруппировать записи по модели. В случае зависимостей между действием / меню / видами это соглашение может быть неприменимым.
Использовать соглашение об создании имен, определенное далее
Тег [UNKNOWN NODE problematic]<data> * используется только для установки не-обновляемых данных с помощью параметра
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 supports custom tags acting as syntactic sugar:
menuitem: использовать его как ярлык для объявления
ir.ui.menu
Workflow: тег <workflow> отправляет сигнал в существующий рабочий процесс.
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 - * - `` в первой строке.
Всегда одобряйте * читаемость * над * краткость * или использование языковых функций или идиом.
Не используйте [UNKNOWN NODE title_reference] `
# 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 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``, [UNKNOWN NODE title_reference], ... Они делают код более легким для чтения.
# 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..."
# 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, используйте методы `` filter``, `` mapped``, [UNKNOWN NODE title_reference], ..., чтобы облегчить чтение кода и производительность.
Сделайте ваш метод работает в пакетном режиме
При добавлении функции убедитесь, что она может обрабатывать несколько записей. Как правило, такой метод декорируется декоратором `` api.multi`` (или берет список * id [UNKNOWN NODE problematic], если он написан в старой api). Тогда вам придется перебирать на `` self`` для обработки каждой записи.
@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)
Распространение контекста
В новом API контекст - это `` frozendict``, который нельзя изменить. Чтобы вызвать метод с другим контекстом, следует использовать метод `` with_context``:
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
Параметр передачи в контексте может иметь опасные побочные эффекты. Поскольку значения распространяются автоматически, может появиться некоторое поведение. При вызове метода [UNKNOWN NODE title_reference] модели с ключом * 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], вы явно создали свой собственный курсор базы данных! И ситуации, когда вам нужно это делать, являются исключительными!
И, кстати, если вы создали свой собственный курсор, вам нужно обрабатывать случаи ошибок и правильный откат, а также правильно закрывать курсор, когда вы закончите с ним.
И вопреки распространенному мнению, вам даже не нужно вызывать [UNKNOWN NODE title_reference] [UNKNOWN NODE title_reference] [UNKNOWN NODE title_reference] объекта * 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 _
Необходимо соблюдать несколько очень важных правил при его использовании, чтобы оно работало и чтобы избежать заполнения переводов бесполезным хламом.
В принципе, этот метод следует использовать только для статических строк, написанных вручную в коде, он не будет работать для перевода значений полей, таких как имена продуктов и т. Д. Это должно быть сделано вместо этого с использованием флага перевода в соответствующем поле.
Правило очень просто: вызовы метода подчёркивания всегда должны быть в форме [UNKNOWN NODE title_reference] [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: используйте camelcase для кода в api v8 (Object-oriented style), подчеркивайте нижний регистр нотации для старой api (стиль SQL).
class AccountInvoice(models.Model):
...
class account_invoice(osv.osv):
...
- Имя переменной:
Используйте camelcase при определении переменных в модели данных
Используйте нижний регистр и подчеркивание при определении обычных переменных.
Так как новый API работает с записью или набором записей вместо списка идентификаторов, не нужно добавлять имя переменной с _id или _ids, если они не содержат id или список 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_<имя_поля>
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
Для javascript :
use strict;
рекомендуется во всех файлах javascriptИспользуйте linter (jshint, ...)
Никогда не добавляйте минифицированные библиотеки Javascript
Используйте camelcase для объявления класса
Если ваш код не должен запускаться на каждой странице, укажите целевые страницы с помощью функции
if_dom_contains
модуля сайта. Нацельте элемент, который является специфическим для страниц, которые ваш код должен запускать с использованием JQuery.
openerp.website.if_dom_contains('.jquery_class_selector', function () {
/*your code here*/
});
Для CSS :
Префикс всех ваших классов с o_<module_name> где module_name - техническое имя модуля ('sale', 'im_chat', ...) или основной маршрут, зарезервированный модулем (для модуля сайта главным образом, т.е. : 'o_forum' для модуля website_forum). Единственным исключением для этого правила является webclient: он просто использует префикс o_.
Избегайте использования id
Использовать собственные классы Bootstrap
Используйте нижний регистр для обозначения класса
Git
Коммиты
Префикс для вашего коммита
[IMP] для улучшения (improvement)
[FIX] для исправления ошибок
[REF] для рефакторинга
[ADD] для добавления новых ресурсов
[REM] для удаления ресурсов
[MOV] для перемещения файлов (не меняйте содержимое перемещенного файла, иначе Git потеряет дорожку, а история будет потеряна!), Или просто переместите код из файла в другой.
[MERGE] для merge коммитов (только для прямого/обратного портирования)
[CLA] для подписи индивидуальной авторской лицензии Odoo
Затем в теле сообщении укажите часть кода, на которую повлияли ваши изменения (имя модуля, библиотека, transversal объект, ...) и описание изменений.
Всегда делайте коммит информативным: он должно быть самодостаточным, включать имя модуля, который был изменен, и причину изменения. Не используйте одиночные слова, такие как «исправление» или «улучшения».
Избегайте коммитов, которые одновременно воздействуют на несколько модулей. Попытайтесь их разделить таким образом чтобы модули были независимы друг от друга (будет полезно, если нам нужно будет откатить модуль отдельно).
[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