Odoo - opensource CRM система, которой пользуется медиацентр МИЭМ.
Она имеет множество применений (например сайты проектов), в проекте 553 же используется инвентарь.
Для локальной разработки я делал следующее:
Метод не самый удачный, но я случайно пришел к этому, потому что сначала поставил Odoo, а только потом понял необходимость подсказок в IDE. На будущее советую просто запускать Odoo из исходников, но этого я не пробовал и подводных камней этого метода я не знаю. How hard could it be?
Не забудьте включить Developer Mode
Обязательно стоит прочитать официальный туториал по созданию плагинов. Постараюсь его тут полностью не пересказывать.
С документацией у Odoo есть некоторые сложности. Далеко не все задокументировано на официальном сайте, так что очень часто любое действие сопровождается рытьем в исходниках, поиском примеров, и экспериментами.
Плагин 553 дополняет плагин Inventory, соответственно часто приходится копаться в его исходниках. Он в свою очередь наследует значительную часть моделей из product. Подробнее про особенности Inventory расскажу дальше.
При редактировании видов можно пользоваться средствами Odoo (значок в виде жука на верхней панели -> Edit View). Это удобно еще тем, что там отображается, откуда унаследован текущий вид, и какие у него есть наследники.
Плагины ("модули") пишутся на Python. У них довольно специфичная структура, можно посмотреть на примере плагина 553. Я буду называть модули Python модулями, а модули Odoo плагинами, чтобы избежать путаницы.
В целом плагин является модулем Python (соответственно у него в корне есть __init__.py). Также в корне должен быть файл __manifest__.py со словарем конфигурации плагина. В документации есть все доступные поля, но далеко не все из них обязательны.
В модуле models располагаются модели БД. Подробное описание есть в официальном туториале, но концептуально это не отличается от обычных ORM.
В security располагаются файлы с правами доступа к моделям. Каждой модели надо прописывать права доступа отдельно. В случае 553 я просто скопипастил права доступа модели product.template, которую я наследовал. Подробнее в том же туториале.
В модуле views находятся xml-файлы видов. Подробнее про виды есть в главах 6 и 7 туториала.
Виды описывают интерфейс плагина, то есть как конкретно отображаются модели из БД, и доступные для них действия.
Это основные компоненты плагинов Odoo, но есть еще различные более специфичные вещи вроде веб контроллеров.
От плагина требовалось:
В Inventory есть деление предметов на Product (product.template) и Product Variant.
Product это общее описание предмета. Ему можно добавлять аттрибуты, для каждого из которых можно создать список возможных значений. Возможные сочетания значений создают Product Variant.
В плагине 553 наследуется product.template. Добавлены следующие поля:
aragavaio_type - тип в aragava.io (устройство, провод, или предмет не поддерживает aragava.io)
Для устройств:
port_ids - порты устройства (о них дальше)manual_url - ссылка на мануалДля кабелей:
left_connector_type - тип левого коннектораleft_connector_connector - штекер левого коннектораright_connector_type и right_connector_connectorcable_length - длина кабеля (в метрах)Коннекторы кабелей можно было представить как два автоматически генерирующихся порта, но проще было дублировать некоторые поля портов.
Порты реализованы отдельной моделью, в которой реализованы все необходимые поля.
Отдельно отмечу поля connector и interface. Они сделаны не как текстовые поля, а как ссылки на модели Connector и Interface. Это сделано чтобы можно было использовать повторно уже прописанные типы коннекторов и интерфейсов.
Вид редактирования из Inventory называется product.template_procurement (выяснил с помощью Edit View). External ID у него почему-то stock.product_template_form_view_procurement_button, именно это пишется в inherit_id унаследованного вида.
Но в виде из Inventory определены только некоторые специфичные для Inventory вещи. Чтобы понять, относительно каких элементов располагать новые, я смотрел код вида product.template.common.form, от которого он унаследован.
На главную вкладку (General Information) я добавил только AragavaIO type.
<field name="product_tooltip" position="after">
<field name="aragavaio_type" nolabel="0"/>
</field>
Такая конструкция означает, что элемент расположен после элемента product_tooltip.
Параметры устройства/провода я расположил на отдельных вкладках, которые отображаются только если у предмета выбран соответствующий AragavaIO type.
Параметры для устройства сделаны вот так:
<!-- расположить после страницы inventory -->
<xpath expr="//page[@name='inventory']" position="after">
<!-- создать отдельную страницу, которая не отображается если aragavaio_type!=device -->
<page name="device_options" string="AragavaIO device options" attrs="{'invisible':[('aragavaio_type','!=','device')]}">
<!-- ссылку на мануал вынес в группу из одного поля, потому что иначе у него почему-то не отображается название -->
<group name="group_manual_url">
<field name="manual_url" widget="url" string="User manual URL" nolabel="0"/>
</group>
<group name="group_ports" string="Ports">
<field name="port_ids" widget="one2many" nolabel="1">
<tree editable="bottom">
<field name="name" />
<field name="direction" />
<field name="type" />
<field name="connector" />
<field name="interface" />
<field name="required" />
<field name="amount" />
</tree>
</field>
</group>
</page>
</xpath>
Аналогично сделаны параметры для провода.
С точки зрения UI экспорт делается через кнопку "Export to aragava.io" на вкладке Products верхнего меню.
Эта кнопка вызывает URL action action_export_to_aragavaio, которое открывает страницу aragavaio/redirect.
Сделано это вот так:
<record model="ir.actions.act_url" id="action_export_to_aragavaio">
<field name="name">Export to aragava.io</field>
<field name="url">aragavaio/redirect</field>
</record>
<menuitem id="menu_export_to_aragavaio" name="Export to aragava.io" action="action_export_to_aragavaio" parent="stock.menu_stock_inventory_control" sequence="4" />
Я вставил это в тот же файл views/product_views.xml.
URL action может открыть внешнюю ссылку напрямую, но только через GET-запрос.
Единственным вариантом передать в AragavaIO содержимое библиотеки были бы query params, но их длина обычно ограничена серверами.
Поэтому пришлось вместо этого перекидывать на локальную страницу, которая сделана через Web Controller (controllers/main.py).
Обработчик перебирает все объекты типа product.template, для кабелей генерируются соответствующие API словари с параметрами.
Для устройств отдельно перебираются Product Variants, чтобы каждый вариант кодировался как отдельное устройство со своими параметрами и количеством.
Возвращается страница, состоящая из одного тега <script>. При загрузке страницы выполняется функция redirect(), которая через POST-запрос открывает aragava.io с содержимым инвентаря.