Odooを読む
業務システムのバックエンドの作り方を知りたいので、Odooから勉強する。
- 基本的な構成を理解
- 個々の機能を理解
- 機能追加時のDBの変更を確認する
基本的な構成
OwlによるSPAだが、JSONRPCを使っていて一般的なJSON色付けではなさそう。
docker composeで立ち上げる
- 5433でローカルから接続する
- クエリのログを確認できるようにする
services: web: image: odoo:16.0 container_name: odoo_server depends_on: - db ports: - "8069:8069" db: image: postgres:15 container_name: odoo_postgres command: postgres -c log_destination=stderr -c log_statement=all -c log_connections=on -c log_disconnections=on env_file: - .env ports: - "5433:5432" volumes: - odoo_data:/var/lib/postgresql/data volumes: odoo_data:
デフォルトのDBのテーブル定義書をA5:SQL Mk-2で出力
アドオンを追加して、テーブル定義書の差分をとることで、追加されたテーブルを確認する
在庫アドオンを追加すると、以下のテーブルが追加される
diff default_entities/index.html stock_entities/index.html | grep -oE "(\">).*?(</a>)" | sed -e 's/\">//g' | sed -e 's/<\/a>//g'
追加されたテーブル
account_analytic_account account_analytic_applicability account_analytic_distribution_model account_analytic_line account_analytic_plan barcode_nomenclature barcode_rule base_module_install_request base_module_install_review confirm_stock_sms crm_tag crm_team crm_team_member digest_digest digest_digest_res_users_rel digest_tip digest_tip_res_users_rel email_template_attachment_rel fetchmail_server iap_account iap_account_res_company_rel ir_act_server_res_partner_rel lot_label_layout lot_label_layout_stock_picking_rel mail_activity mail_activity_rel mail_activity_type mail_activity_type_mail_template_rel mail_alias mail_blacklist mail_blacklist_remove mail_channel mail_channel_member mail_channel_res_groups_rel mail_channel_rtc_session mail_compose_message mail_compose_message_ir_attachments_rel mail_compose_message_res_partner_rel mail_followers mail_followers_mail_message_subtype_rel mail_gateway_allowed mail_guest mail_ice_server mail_link_preview mail_mail mail_mail_res_partner_rel mail_message mail_message_reaction mail_message_res_partner_rel mail_message_res_partner_starred_rel mail_message_schedule mail_message_subtype mail_notification mail_notification_mail_resend_message_rel mail_resend_message mail_resend_partner mail_shortcode mail_template mail_template_mail_template_reset_rel mail_template_preview mail_template_reset mail_tracking_value mail_wizard_invite mail_wizard_invite_res_partner_rel message_attachment_rel payment_country_rel payment_icon payment_icon_payment_provider_rel payment_link_wizard payment_provider payment_provider_onboarding_wizard payment_token payment_transaction phone_blacklist phone_blacklist_remove picking_label_type picking_label_type_stock_picking_rel portal_share portal_share_res_partner_rel portal_wizard portal_wizard_res_partner_rel portal_wizard_user privacy_log privacy_lookup_wizard privacy_lookup_wizard_line procurement_group product_attribute product_attribute_custom_value product_attribute_product_template_rel product_attribute_value product_attribute_value_product_template_attribute_line_rel product_attr_exclusion_value_ids_rel product_category product_label_layout product_label_layout_product_product_rel product_label_layout_product_template_rel product_label_layout_stock_move_line_rel product_packaging product_pricelist product_pricelist_item product_product product_product_stock_track_confirmation_rel product_removal product_replenish product_replenish_stock_route_rel product_supplierinfo product_tag product_tag_product_product_rel product_tag_product_template_rel product_template product_template_attribute_exclusion product_template_attribute_line product_template_attribute_value product_variant_combination report_stock_quantity resource_calendar resource_calendar_attendance resource_calendar_leaves resource_resource res_country_group_pricelist_rel res_groups_spreadsheet_dashboard_rel res_partner_autocomplete_sync res_users_settings res_users_settings_volumes sms_composer sms_resend sms_resend_recipient sms_sms sms_template sms_template_preview sms_template_reset sms_template_sms_template_reset_rel snailmail_letter snailmail_letter_format_error snailmail_letter_missing_required_fields spreadsheet_dashboard spreadsheet_dashboard_group stock_assign_serial stock_backorder_confirmation stock_backorder_confirmation_line stock_change_product_qty stock_conflict_quant_rel stock_immediate_transfer stock_immediate_transfer_line stock_inventory_adjustment_name stock_inventory_adjustment_name_stock_quant_rel stock_inventory_conflict stock_inventory_conflict_stock_quant_rel stock_inventory_warning stock_inventory_warning_stock_quant_rel stock_location stock_lot stock_move stock_move_line stock_move_line_consume_rel stock_move_move_rel stock_orderpoint_snooze stock_orderpoint_snooze_stock_warehouse_orderpoint_rel stock_package_destination stock_package_level stock_package_type stock_package_type_stock_putaway_rule_rel stock_picking stock_picking_backorder_rel stock_picking_sms_rel stock_picking_transfer_rel stock_picking_type stock_putaway_rule stock_quant stock_quantity_history stock_quant_package stock_quant_stock_request_count_rel stock_quant_stock_track_confirmation_rel stock_replenishment_info stock_replenishment_option stock_request_count stock_return_picking stock_return_picking_line stock_route stock_route_categ stock_route_move stock_route_packaging stock_route_product stock_route_warehouse stock_rule stock_rules_report stock_rules_report_stock_warehouse_rel stock_scheduler_compute stock_scrap stock_storage_category stock_storage_category_capacity stock_traceability_report stock_track_confirmation stock_track_line stock_warehouse stock_warehouse_orderpoint stock_warn_insufficient_qty_scrap stock_wh_resupply_table team_favorite_user_rel uom_category uom_uom utm_campaign utm_medium utm_source utm_stage utm_tag utm_tag_rel
2023-07-02 06:36:28.977 UTC [36] LOG: statement: SELECT "stock_picking_type".id FROM "stock_picking_type" WHERE ("stock_picking_type"."active" = true) AND ("stock_picking_type"."company_id" in (1)) ORDER BY "stock_picking_type"."sequence" ,"stock_picking_type"."id" LIMIT 80 2023-07-02 06:36:28.978 UTC [36] LOG: statement: SELECT "stock_picking_type"."id" AS "id", "stock_picking_type"."color" AS "color", "stock_picking_type"."code" AS "code", COALESCE("stock_picking_type"."name"->>'ja_JP', "stock_picking_type"."name"->>'en_US') AS "name" FROM "stock_picking_type" WHERE ("stock_picking_type"."company_id" in (1)) AND "stock_picking_type".id IN (1, 2, 6) SELECT min("stock_picking".id) AS id, count("stock_picking".id) AS "picking_type_id_count" , "stock_picking"."picking_type_id" as "picking_type_id" FROM "stock_picking" WHERE ((("stock_picking"."state" = 'draft') AND (("stock_picking"."state" not in ('done', 'cancel')) OR "stock_picking"."state" IS NULL)) AND ("stock_picking"."picking_type_id" in (1, 2, 6))) AND ("stock_picking"."company_id" in (1)) GROUP BY "stock_picking"."picking_type_id" SELECT min("stock_picking".id) AS id, count("stock_picking".id) AS "picking_type_id_count" , "stock_picking"."picking_type_id" as "picking_type_id" FROM "stock_picking" WHERE ((("stock_picking"."state" in ('confirmed', 'waiting')) AND (("stock_picking"."state" not in ('done', 'cancel')) OR "stock_picking"."state" IS NULL)) AND ("stock_picking"."picking_type_id" in (1, 2, 6))) AND ("stock_picking"."company_id" in (1)) GROUP BY "stock_picking"."picking_type_id" SELECT min("stock_picking".id) AS id, count("stock_picking".id) AS "picking_type_id_count" , "stock_picking"."picking_type_id" as "picking_type_id" FROM "stock_picking" WHERE ((("stock_picking"."state" = 'assigned') AND (("stock_picking"."state" not in ('done', 'cancel')) OR "stock_picking"."state" IS NULL)) AND ("stock_picking"."picking_type_id" in (1, 2, 6))) AND ("stock_picking"."company_id" in (1)) GROUP BY "stock_picking"."picking_type_id" SELECT min("stock_picking".id) AS id, count("stock_picking".id) AS "picking_type_id_count" , "stock_picking"."picking_type_id" as "picking_type_id" FROM "stock_picking" WHERE ((("stock_picking"."state" in ('assigned', 'waiting', 'confirmed')) AND (("stock_picking"."state" not in ('done', 'cancel')) OR "stock_picking"."state" IS NULL)) AND ("stock_picking"."picking_type_id" in (1, 2, 6))) AND ("stock_picking"."company_id" in (1)) GROUP BY "stock_picking"."picking_type_id" SELECT min("stock_picking".id) AS id, count("stock_picking".id) AS "picking_type_id_count" , "stock_picking"."picking_type_id" as "picking_type_id" FROM "stock_picking" WHERE (((("stock_picking"."scheduled_date" < '2023-07-02 06:36:28') AND ("stock_picking"."state" in ('assigned', 'waiting', 'confirmed'))) AND (("stock_picking"."state" not in ('done', 'cancel')) OR "stock_picking"."state" IS NULL)) AND ("stock_picking"."picking_type_id" in (1, 2, 6))) AND ("stock_picking"."company_id" in (1)) GROUP BY "stock_picking"."picking_type_id" SELECT min("stock_picking".id) AS id, count("stock_picking".id) AS "picking_type_id_count" , "stock_picking"."picking_type_id" as "picking_type_id" FROM "stock_picking" WHERE ((("stock_picking"."backorder_id" IS NOT NULL AND ("stock_picking"."state" in ('confirmed', 'assigned', 'waiting'))) AND (("stock_picking"."state" not in ('done', 'cancel')) OR "stock_picking"."state" IS NULL)) AND ("stock_picking"."picking_type_id" in (1, 2, 6))) AND ("stock_picking"."company_id" in (1)) GROUP BY "stock_picking"."picking_type_id"
クエリした結果をみると、数字が多く何を意味しているかよくわからない。
id | picking_type_id_count | picking_type_id ----+-----------------------+----------------- 9 | 3 | 1 1 | 3 | 2
サーバーへのリクエストを見てみる
2023-07-02 07:00:25,722 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:25] "GET /web HTTP/1.1" 200 - 10 0.008 0.014 2023-07-02 07:00:26,349 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "POST /web/action/load HTTP/1.1" 200 - 9 0.007 0.011 2023-07-02 07:00:26,425 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "POST /mail/init_messaging HTTP/1.1" 200 - 32 0.019 0.023 2023-07-02 07:00:26,708 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "POST /web/dataset/call_kw/res.users/systray_get_activities HTTP/1.1" 200 - 2 0.001 0.003 2023-07-02 07:00:26,800 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "POST /web/dataset/call_kw/stock.picking.type/get_views HTTP/1.1" 200 - 2 0.002 0.011 2023-07-02 07:00:26,823 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "GET /web/image/res.company/1/favicon HTTP/1.1" 304 - 5 0.003 0.008 2023-07-02 07:00:26,846 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "POST /mail/load_message_failures HTTP/1.1" 200 - 9 0.022 0.012 2023-07-02 07:00:26,849 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "POST /web/dataset/call_kw/stock.picking.type/web_search_read HTTP/1.1" 200 - 9 0.012 0.012 2023-07-02 07:00:26,852 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:26] "GET /web/image/res.company/1/favicon HTTP/1.1" 304 - 5 0.007 0.012 2023-07-02 07:00:27,140 1 INFO ? werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:27] "GET /mail/static/src/audio/ting.ogg HTTP/1.1" 206 - 0 0.000 0.001 2023-07-02 07:00:27,146 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:27] "GET /web/image/res.company/1/favicon HTTP/1.1" 304 - 5 0.002 0.006 2023-07-02 07:00:27,185 1 INFO test werkzeug: 172.27.0.1 - - [02/Jul/2023 07:00:27] "GET /web/image?model=res.users&field=avatar_128&id=2 HTTP/1.1" 304 - 9 0.007 0.010
コードを読む
from odoo.api import call_kw @http.route(['/web/dataset/call_kw', '/web/dataset/call_kw/<path:path>'], type='json', auth="user") def call_kw(self, model, method, args, kwargs, path=None): return self._call_kw(model, method, args, kwargs) def _call_kw(self, model, method, args, kwargs): check_method_name(method) return call_kw(request.env[model], method, args, kwargs)
def call_kw(model, name, args, kwargs): """ Invoke the given method ``name`` on the recordset ``model``. """ method = getattr(type(model), name) api = getattr(method, '_api', None) if api == 'model': result = _call_kw_model(method, model, args, kwargs) elif api == 'model_create': result = _call_kw_model_create(method, model, args, kwargs) else: result = _call_kw_multi(method, model, args, kwargs) model.env.flush_all() return result
{ "id": 3, "jsonrpc": "2.0", "method": "call", "params": { "model": "stock.picking.type", "method": "get_views", "args": [], "kwargs": { "context": { "lang": "ja_JP", "tz": "Asia/Tokyo", "uid": 2, "allowed_company_ids": [ 1 ], "params": { "action": 371, "model": "stock.picking.type", "view_type": "kanban", "cids": 1, "menu_id": 204 } }, "views": [ [ false, "kanban" ], [ false, "form" ], [ false, "search" ] ], "options": { "action_id": 371, "load_filters": true, "toolbar": true, "mobile": true } } } }
rpcでほぼ同じパラメータでリクエストしてるのを発見
this._cache.views[viewsKey] = rpc.query({ args: [], kwargs: { context, options, views: views_descr }, model, method: 'get_views', })
これは、web.rpcで定義されている
var rpc = require('web.rpc');
この実際の定義が見つからない。addons/web/static/src/legacy/js/core/rpc.js
にはあるが、legacyとあるのでこれを使っているのかがわからない。
次回、公式ドキュメントから学んでいく https://www.odoo.com/documentation/16.0/developer/tutorials/getting_started/01_architecture.html