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_connector
cable_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 с содержимым инвентаря.