Toggle navigation

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

На этой странице представлены новые правила кодирования Оду. Они направлены на улучшение качества кода (например, улучшение читаемости исходного кода) и приложений Odoo. Действительно, правильный код облегчает обслуживание, помогает в отладке, снижает сложность и повышает надежность.

Эти рекомендации должны применяться ко всем новым модулям и новым разработкам. Эти рекомендации будут применяться только к старому модулю ** ** в случае рефакторинга кода (переход на новый API, большой рефакторинг, ...).

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

Каталоги

Модуль организован в важных каталогах. Они содержат бизнес-логику; Взглянув на них, следует понять цель модуля.

  • 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

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' для систем с множеством компаний, ...).

  • Для группы:: samp: [UNKNOWN NODE title_reference] где * имя_группы * - это имя группы, обычно 'пользователь', 'менеджер', ...

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

Унаследованный 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: комментарий блока должен начинаться с '#'

Импорт

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

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

  2. Импорт odoo

  3. Импорт из модулей 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 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``, [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://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 () `` где угодно, очень высока вероятность разрыва системы по-разному, потому что вы будете совершать частичные коммиты и, следовательно, частичные и нечистые откаты, вызывая среди другие:

  1. Несогласованные бизнес-данные, обычно потеря данных

  2. Автоматическая десинхронизация рабочего процесса, постоянно застрявшие документы

  3. Тесты, которые нельзя отменить чисто, и начнут загрязнять базу данных, и инициируют ошибку (это верно, даже если во время транзакции не возникла ошибка)

Вот самое простое правило:

Вы должны ** НИКОГДА ** не называть `` 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().

  • В атрибутах модели данных порядок должен быть следующим
    1. Частные атрибуты (_name, _description, _inherit, ...)

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

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

    4. Методы поиска и вычисления. Они должны идти в том же порядке, что и объявление поля

    5. Методы ограничений (@api.constrains) и onchange методы (@api.onchange)

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

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

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

class Event(models.Model):
    # Private attributes
    _name = 'event.event'
    _description = 'Event'

    # Default methods
    def _default_name(self):
        ...

    # Fields declaration
    name = fields.Char(string='Name', default=_default_name)
    seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
        store=True, readonly=True, compute='_compute_seats')
    seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
        store=True, readonly=True, compute='_compute_seats')
    price = fields.Integer(string='Price')

    # compute and search fields, in the same order 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.

odoo.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 ...