diff --git a/.badges/code_style-black-000000.svg b/.badges/code_style-black-000000.svg new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmJhZGdlcy9jb2RlX3N0eWxlLWJsYWNrLTAwMDAwMC5zdmc= --- /dev/null +++ b/.badges/code_style-black-000000.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20"> + <linearGradient id="b" x2="0" y2="100%"> + <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> + <stop offset="1" stop-opacity=".1" /> + </linearGradient> + <mask id="anybadge_1"> + <rect width="114" height="20" rx="3" fill="#fff" /> + </mask> + <g mask="url(#anybadge_1)"> + <path fill="#555" d="M0 0h72v20H0z" /> + <path fill="#000000" d="M72 0h42v20H72z" /> + <path fill="url(#b)" d="M0 0h114v20H0z" /> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="37.0" y="15" fill="#010101" fill-opacity=".3">code style</text> + <text x="36.0" y="14">code style</text> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="94.0" y="15" fill="#010101" fill-opacity=".3">black</text> + <text x="93.0" y="14">black</text> + </g> +</svg> diff --git a/.badges/code_style-prettier-ff69b4.svg b/.badges/code_style-prettier-ff69b4.svg new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmJhZGdlcy9jb2RlX3N0eWxlLXByZXR0aWVyLWZmNjliNC5zdmc= --- /dev/null +++ b/.badges/code_style-prettier-ff69b4.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<svg xmlns="http://www.w3.org/2000/svg" width="129" height="20"> + <linearGradient id="b" x2="0" y2="100%"> + <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> + <stop offset="1" stop-opacity=".1" /> + </linearGradient> + <mask id="anybadge_1"> + <rect width="129" height="20" rx="3" fill="#fff" /> + </mask> + <g mask="url(#anybadge_1)"> + <path fill="#555" d="M0 0h72v20H0z" /> + <path fill="#ff69b4" d="M72 0h57v20H72z" /> + <path fill="url(#b)" d="M0 0h129v20H0z" /> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="37.0" y="15" fill="#010101" fill-opacity=".3">code style</text> + <text x="36.0" y="14">code style</text> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="101.5" y="15" fill="#010101" fill-opacity=".3">prettier</text> + <text x="100.5" y="14">prettier</text> + </g> +</svg> diff --git a/.badges/coverage.svg b/.badges/coverage.svg new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmJhZGdlcy9jb3ZlcmFnZS5zdmc= --- /dev/null +++ b/.badges/coverage.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20"> + <linearGradient id="b" x2="0" y2="100%"> + <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> + <stop offset="1" stop-opacity=".1" /> + </linearGradient> + <mask id="anybadge_1"> + <rect width="124" height="20" rx="3" fill="#fff" /> + </mask> + <g mask="url(#anybadge_1)"> + <path fill="#555" d="M0 0h65v20H0z" /> + <path fill="#808080" d="M65 0h59v20H65z" /> + <path fill="url(#b)" d="M0 0h124v20H0z" /> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> + <text x="32.5" y="14">coverage</text> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="95.5" y="15" fill="#010101" fill-opacity=".3">unknown</text> + <text x="94.5" y="14">unknown</text> + </g> +</svg> diff --git a/.badges/licence-AGPL--3-blue.svg b/.badges/licence-AGPL--3-blue.svg new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmJhZGdlcy9saWNlbmNlLUFHUEwtLTMtYmx1ZS5zdmc= --- /dev/null +++ b/.badges/licence-AGPL--3-blue.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<svg xmlns="http://www.w3.org/2000/svg" width="107" height="20"> + <linearGradient id="b" x2="0" y2="100%"> + <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> + <stop offset="1" stop-opacity=".1" /> + </linearGradient> + <mask id="anybadge_1"> + <rect width="107" height="20" rx="3" fill="#fff" /> + </mask> + <g mask="url(#anybadge_1)"> + <path fill="#555" d="M0 0h53v20H0z" /> + <path fill="#0000FF" d="M53 0h54v20H53z" /> + <path fill="url(#b)" d="M0 0h107v20H0z" /> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="27.5" y="15" fill="#010101" fill-opacity=".3">licence</text> + <text x="26.5" y="14">licence</text> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="81.0" y="15" fill="#010101" fill-opacity=".3">AGPL-3</text> + <text x="80.0" y="14">AGPL-3</text> + </g> +</svg> diff --git a/.badges/maturity.svg b/.badges/maturity.svg new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmJhZGdlcy9tYXR1cml0eS5zdmc= --- /dev/null +++ b/.badges/maturity.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<svg xmlns="http://www.w3.org/2000/svg" width="177" height="20"> + <linearGradient id="b" x2="0" y2="100%"> + <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> + <stop offset="1" stop-opacity=".1" /> + </linearGradient> + <mask id="anybadge_1"> + <rect width="177" height="20" rx="3" fill="#fff" /> + </mask> + <g mask="url(#anybadge_1)"> + <path fill="#555" d="M0 0h61v20H0z" /> + <path fill="#4c1" d="M61 0h116v20H61z" /> + <path fill="url(#b)" d="M0 0h177v20H0z" /> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="31.5" y="15" fill="#010101" fill-opacity=".3">maturity</text> + <text x="30.5" y="14">maturity</text> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="120.0" y="15" fill="#010101" fill-opacity=".3">Production/Stable</text> + <text x="119.0" y="14">Production/Stable</text> + </g> +</svg> diff --git a/.badges/pylint.svg b/.badges/pylint.svg new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmJhZGdlcy9weWxpbnQuc3Zn --- /dev/null +++ b/.badges/pylint.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<svg xmlns="http://www.w3.org/2000/svg" width="103" height="20"> + <linearGradient id="b" x2="0" y2="100%"> + <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> + <stop offset="1" stop-opacity=".1" /> + </linearGradient> + <mask id="anybadge_1"> + <rect width="103" height="20" rx="3" fill="#fff" /> + </mask> + <g mask="url(#anybadge_1)"> + <path fill="#555" d="M0 0h44v20H0z" /> + <path fill="#808080" d="M44 0h59v20H44z" /> + <path fill="url(#b)" d="M0 0h103v20H0z" /> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="23.0" y="15" fill="#010101" fill-opacity=".3">pylint</text> + <text x="22.0" y="14">pylint</text> + </g> + <g + fill="#fff" + text-anchor="middle" + font-family="DejaVu Sans,Verdana,Geneva,sans-serif" + font-size="11" + > + <text x="74.5" y="15" fill="#010101" fill-opacity=".3">unknown</text> + <text x="73.5" y="14">unknown</text> + </g> +</svg> diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LmVkaXRvcmNvbmZpZw== --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Configuration for known file extensions +[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{json,yml,yaml,rst,md}] +indent_size = 2 + +# Do not configure editor for libs and autogenerated content +[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fc2696167416ae24aa725cfeba19fc40d7ee4686_LmdpdGxhYi1jaS55bWw=..085f5ffd4857a2edba98996491725a260678ea09_LmdpdGxhYi1jaS55bWw= 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,3 @@ include: -- project: xcg/ci-templates - file: /odoo/13.0/gitlab-ci.yaml + - project: xcg/ci-templates + file: /odoo/13.0/gitlab-ci.yaml diff --git a/.hgignore b/.hgignore index fc2696167416ae24aa725cfeba19fc40d7ee4686_LmhnaWdub3Jl..085f5ffd4857a2edba98996491725a260678ea09_LmhnaWdub3Jl 100644 --- a/.hgignore +++ b/.hgignore @@ -1,25 +1,3 @@ syntax: glob -**/*.pyc -*.pyc -*.pyo -*.swp -.tmp* -*~ -.~* -*.egg-info -dist/* -build/* -lib/* -output/* -*.orig -*.log -.settings/* -storage/* -.project -.idea -.pydevproject -*.db -.ropeproject/* -.mob -pyproject.toml -.isort.cfg +./doc/_build +./doc/autotodo diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LnByZXR0aWVycmMueW1s --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,8 @@ +# Defaults for all prettier-supported languages. +# Prettier will complete this with settings from .editorconfig file. +bracketSpacing: false +printWidth: 88 +proseWrap: always +semi: true +trailingComma: "es5" +xmlWhitespaceSensitivity: "ignore" diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_LnlhbWxsaW50LnlhbWw= --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,4 @@ +rules: + document-start: disable + indentation: + indent-sequences: true diff --git a/NEWS.rst b/NEWS.rst index fc2696167416ae24aa725cfeba19fc40d7ee4686_TkVXUy5yc3Q=..085f5ffd4857a2edba98996491725a260678ea09_TkVXUy5yc3Q= 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -4,6 +4,8 @@ 13.0.2.0.3 ---------- +Update to latest template version and change tests to use TransactionCase whenever possible. + forward port of 11.0 ~~~~~~~~~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index fc2696167416ae24aa725cfeba19fc40d7ee4686_UkVBRE1FLnJzdA==..085f5ffd4857a2edba98996491725a260678ea09_UkVBRE1FLnJzdA== 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,30 @@ Xbus Emitter for Odoo ===================== +.. Update the path to the module bellow and to the branch +.. |coverage| image:: .badges/coverage.svg + :target: https://orus.io/xcg/templates/odoo_module/-/pipelines?ref=branch/13.0 + :alt: Coverage report +.. the image is updated by the CI when building the documentation +.. |pylint| image:: .badges/pylint.svg + :target: https://orus.io/xcg/templates/odoo_module/-/pipelines?ref=branch/13.0 + :alt: pylint score +.. Update the badge bellow depending on status +.. |maturity| image:: .badges/maturity.svg + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |license| image:: .badges/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |black| image:: .badges/code_style-black-000000.svg + :target: https://github.com/psf/black + :alt: Black +.. |prettier| image:: .badges/code_style-prettier-ff69b4.svg + :target: https://github.com/prettier/prettier + :alt: Prettier + +|coverage| |pylint| |maturity| |license| |black| |prettier| + Emit messages to `Xbus <https://xbus.io/>`_ from Odoo. Requires the `xbus-odoo emitter <https://orus.io/xcg/xbus/xbus-odoo>`_ to diff --git a/__manifest__.py b/__manifest__.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_X19tYW5pZmVzdF9fLnB5..085f5ffd4857a2edba98996491725a260678ea09_X19tYW5pZmVzdF9fLnB5 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -1,7 +1,7 @@ ############################################################################## # # Xbus emitter for Odoo -# Copyright (C) 2015 XCG Consulting <http://odoo.consulting> +# Copyright (C) 2015, 2022 XCG Consulting <https://xcg-consulting.fr> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -17,7 +17,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## - { "name": "Xbus Emitter", "license": "AGPL-3", @@ -21,7 +20,7 @@ { "name": "Xbus Emitter", "license": "AGPL-3", - "summary": "emit messages from Odoo to Xbus", + "summary": "Emit messages from Odoo to Xbus", "version": "13.0.2.0.3", "category": "Technical", "author": "XCG Consulting", @@ -25,7 +24,7 @@ "version": "13.0.2.0.3", "category": "Technical", "author": "XCG Consulting", - "website": "http://odoo.consulting/", + "website": "https://odoo.consulting/", "depends": ["base", "web", "base_context"], "data": [ "security/ir.model.access.csv", diff --git a/doc/.badges b/doc/.badges new file mode 120000 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_ZG9jLy5iYWRnZXM= --- /dev/null +++ b/doc/.badges @@ -0,0 +1,1 @@ +../.badges \ No newline at end of file diff --git a/doc/autotodo.py b/doc/autotodo.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_ZG9jL2F1dG90b2RvLnB5..085f5ffd4857a2edba98996491725a260678ea09_ZG9jL2F1dG90b2RvLnB5 100755 --- a/doc/autotodo.py +++ b/doc/autotodo.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2014, 2018 XCG Consulting +# Copyright (C) 2014, 2018, 2022 XCG Consulting # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -34,7 +34,7 @@ tags = sys.argv[3].split(",") todolist = {tag: [] for tag in tags} - for root, dirs, files in os.walk(folder): + for root, _dirs, files in os.walk(folder): scan_folder((exts, tags, todolist), root, files) create_autotodo(folder, todolist) diff --git a/menu.xml b/menu.xml index fc2696167416ae24aa725cfeba19fc40d7ee4686_bWVudS54bWw=..085f5ffd4857a2edba98996491725a260678ea09_bWVudS54bWw= 100644 --- a/menu.xml +++ b/menu.xml @@ -1,2 +1,2 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <odoo> @@ -2,4 +2,3 @@ <odoo> - <!-- Menu for Xbus emission. --> @@ -4,6 +3,8 @@ <!-- Menu for Xbus emission. --> - <menuitem id="xbus_emission_menu_command" name="Xbus emission" - parent="base.menu_administration" /> - + <menuitem + id="xbus_emission_menu_command" + name="Xbus emission" + parent="base.menu_administration" + /> </odoo> diff --git a/models/ir_autovacuum.py b/models/ir_autovacuum.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_bW9kZWxzL2lyX2F1dG92YWN1dW0ucHk=..085f5ffd4857a2edba98996491725a260678ea09_bW9kZWxzL2lyX2F1dG92YWN1dW0ucHk= 100644 --- a/models/ir_autovacuum.py +++ b/models/ir_autovacuum.py @@ -1,7 +1,7 @@ ############################################################################## # # Document attachment tokens, an Odoo module -# Copyright (C) 2020 XCG Consulting <http://odoo.consulting> +# Copyright (C) 2020 XCG Consulting <https://xcg-consulting.fr> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as diff --git a/models/xbus_emitter.py b/models/xbus_emitter.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_bW9kZWxzL3hidXNfZW1pdHRlci5weQ==..085f5ffd4857a2edba98996491725a260678ea09_bW9kZWxzL3hidXNfZW1pdHRlci5weQ== 100644 --- a/models/xbus_emitter.py +++ b/models/xbus_emitter.py @@ -1,7 +1,7 @@ ############################################################################## # # Xbus emitter for Odoo -# Copyright (C) 2015, 2020 XCG Consulting <http://odoo.consulting> +# Copyright (C) 2015, 2020 XCG Consulting <https://xcg-consulting.fr> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as diff --git a/models/xbus_emitter_job.py b/models/xbus_emitter_job.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_bW9kZWxzL3hidXNfZW1pdHRlcl9qb2IucHk=..085f5ffd4857a2edba98996491725a260678ea09_bW9kZWxzL3hidXNfZW1pdHRlcl9qb2IucHk= 100644 --- a/models/xbus_emitter_job.py +++ b/models/xbus_emitter_job.py @@ -1,7 +1,7 @@ ############################################################################## # # Xbus emitter for Odoo -# Copyright (C) 2015, 2020 XCG Consulting <http://odoo.consulting> +# Copyright (C) 2015, 2020 XCG Consulting <https://xcg-consulting.fr> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..085f5ffd4857a2edba98996491725a260678ea09_cHlwcm9qZWN0LnRvbWw= --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 79 +target = 3.8 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index fc2696167416ae24aa725cfeba19fc40d7ee4686_c2V0dXAuY2Zn..0000000000000000000000000000000000000000 --- a/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[odoo_scripts] -modules = ../xbus_emitter -other_sources = requirements requirements-test VERSION other_requirements -odoo_type = odoo11 -module_list = base - xbus_emitter -module_list_tests = - xbus_emitter -registry = registry.xcg.io -image = odoo/odoo:11.0.20191210 - -[flake8] -exclude = venv, xcgd, oca, dependencies,scripts - -[isort] -skip = scripts,venv,oca,xcgd,dependencies diff --git a/static/src/js/debug_manager_xbus.js b/static/src/js/debug_manager_xbus.js index fc2696167416ae24aa725cfeba19fc40d7ee4686_c3RhdGljL3NyYy9qcy9kZWJ1Z19tYW5hZ2VyX3hidXMuanM=..085f5ffd4857a2edba98996491725a260678ea09_c3RhdGljL3NyYy9qcy9kZWJ1Z19tYW5hZ2VyX3hidXMuanM= 100644 --- a/static/src/js/debug_manager_xbus.js +++ b/static/src/js/debug_manager_xbus.js @@ -22,6 +22,6 @@ * - addons/web/static/src/js/tools/debug_manager_backend.js */ -odoo.define('xbus_emitter.debug_manager_xbus', function(require) { - 'use strict'; +odoo.define("xbus_emitter.debug_manager_xbus", function (require) { + "use strict"; @@ -27,4 +27,4 @@ - var DebugManager = require('web.DebugManager'); + var DebugManager = require("web.DebugManager"); DebugManager.include({ @@ -29,4 +29,3 @@ DebugManager.include({ - /* Add an "Xbus messages" link to open related messages from the debug menu. */ @@ -32,6 +31,5 @@ /* Add an "Xbus messages" link to open related messages from the debug menu. */ - open_xbus_messages: function() { - + open_xbus_messages: function () { var controller = this._controller; if (controller === undefined) { return; @@ -39,7 +37,7 @@ var record_ids = controller.getSelectedIds(); if (record_ids === undefined || !record_ids.length) { - console.warn('No active records.'); + console.warn("No active records."); return; } @@ -48,10 +46,12 @@ var self = this; self._rpc({ - domain: [['name', 'in', ['xbus_emitter_job_list', 'xbus_emitter_job_form']]], - fields: ['type'], - limit: 2, - method: 'search_read', - model: 'ir.ui.view', - }).then(function(views) { + domain: [ + ["name", "in", ["xbus_emitter_job_list", "xbus_emitter_job_form"]], + ], + fields: ["type"], + limit: 2, + method: "search_read", + model: "ir.ui.view", + }).then(function (views) { self.do_action({ @@ -57,13 +57,25 @@ self.do_action({ - domain: [['record_id', '=', record_id], ['record_model', '=', record_model]], - name: 'Xbus messages', - res_model: 'xbus.emitter.job', - type: 'ir.actions.act_window', - views: [[_.findWhere(views, { - type: "tree" - }).id, 'list'], [_.findWhere(views, { - type: "form" - }).id, 'form']], + domain: [ + ["record_id", "=", record_id], + ["record_model", "=", record_model], + ], + name: "Xbus messages", + res_model: "xbus.emitter.job", + type: "ir.actions.act_window", + views: [ + [ + _.findWhere(views, { + type: "tree", + }).id, + "list", + ], + [ + _.findWhere(views, { + type: "form", + }).id, + "form", + ], + ], }); }); }, @@ -67,5 +79,4 @@ }); }); }, - }); // DebugManager @@ -71,3 +82,2 @@ }); // DebugManager - }); diff --git a/static/src/xml/debug_manager.xml b/static/src/xml/debug_manager.xml index fc2696167416ae24aa725cfeba19fc40d7ee4686_c3RhdGljL3NyYy94bWwvZGVidWdfbWFuYWdlci54bWw=..085f5ffd4857a2edba98996491725a260678ea09_c3RhdGljL3NyYy94bWwvZGVidWdfbWFuYWdlci54bWw= 100644 --- a/static/src/xml/debug_manager.xml +++ b/static/src/xml/debug_manager.xml @@ -1,2 +1,2 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <templates xml:space="preserve"> @@ -2,8 +2,7 @@ <templates xml:space="preserve"> - <!-- Add an "Xbus messages" link for single records ("Get metadata" only gets shown for single records). Base view ref: addons/web/static/src/xml/debug.xml. --> <t t-extend="WebClient.DebugManager.View"> <t t-jquery="a[data-action='get_metadata']:parent" t-operation="after"> @@ -4,9 +3,16 @@ <!-- Add an "Xbus messages" link for single records ("Get metadata" only gets shown for single records). Base view ref: addons/web/static/src/xml/debug.xml. --> <t t-extend="WebClient.DebugManager.View"> <t t-jquery="a[data-action='get_metadata']:parent" t-operation="after"> - <a role="menuitem" href="#" data-action="open_xbus_messages" class="dropdown-item">Xbus Messages</a> + <a + role="menuitem" + href="#" + data-action="open_xbus_messages" + class="dropdown-item" + > + Xbus Messages + </a> </t> </t> @@ -11,4 +17,3 @@ </t> </t> - </templates> diff --git a/tests/test_export.py b/tests/test_export.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_dGVzdHMvdGVzdF9leHBvcnQucHk=..085f5ffd4857a2edba98996491725a260678ea09_dGVzdHMvdGVzdF9leHBvcnQucHk= 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -1,3 +1,22 @@ -import odoo +############################################################################## +# +# Xbus emitter for Odoo +# Copyright (C) 2022 XCG Consulting <https://xcg-consulting.fr> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## +from odoo.tests import TransactionCase from ..export import SKIP, export @@ -2,5 +21,4 @@ from ..export import SKIP, export -from .util.odoo_tests import TestBase @@ -5,8 +23,6 @@ -@odoo.tests.common.at_install(False) -@odoo.tests.common.post_install(True) -class Test(TestBase): +class Test(TransactionCase): def test_export_char_field(self): record = self.env["res.partner"].create({"name": "my name"}) self.assertEqual( @@ -119,9 +135,9 @@ } } - c1 = self.env["res.partner.category"].create({"name": "cat1"}) + category1 = self.env["res.partner.category"].create({"name": "cat1"}) company = self.env["res.company"].create( {"country_id": self.env.ref("base.fr").id, "name": "company"} ) @@ -123,9 +139,9 @@ company = self.env["res.company"].create( {"country_id": self.env.ref("base.fr").id, "name": "company"} ) - p1 = self.env["res.partner"].create( + partner1 = self.env["res.partner"].create( {"name": "partner 1", "company_id": company.id} ) @@ -129,5 +145,5 @@ {"name": "partner 1", "company_id": company.id} ) - c1.partner_ids = [p1.id] + category1.partner_ids = [partner1.id] @@ -133,5 +149,7 @@ - self.assertEqual({"company": {"name": "company"}}, export(c1, m)) + self.assertEqual( + {"company": {"name": "company"}}, export(category1, m) + ) def test_export_custom_char(self): record = self.env["res.partner"].create({"name": "my name"}) @@ -281,6 +299,10 @@ def test_export_default(self): record = self.env["res.partner"].create({"name": "my name"}) + + def custom(r): + return r is not None and len(r) == 1 + self.assertEqual( {"has_parent": False}, export( @@ -288,7 +310,7 @@ { "has_parent": { "source": "parent_id", - "custom": lambda r: r is not None and len(r) == 1, + "custom": custom, "datatype": "bool", "default": False, } @@ -304,7 +326,7 @@ { "has_parent": { "source": "parent_id", - "custom": lambda r: r is not None and len(r) == 1, + "custom": custom, "datatype": "bool", "default": False, } diff --git a/tests/test_xbus_emitter.py b/tests/test_xbus_emitter.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXIucHk=..085f5ffd4857a2edba98996491725a260678ea09_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXIucHk= 100644 --- a/tests/test_xbus_emitter.py +++ b/tests/test_xbus_emitter.py @@ -1,7 +1,7 @@ ############################################################################## # # Xbus emitter for Odoo -# Copyright (C) 2015 XCG Consulting <http://odoo.consulting> +# Copyright (C) 2015, 2022 XCG Consulting <https://xcg-consulting.fr/> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -18,7 +18,5 @@ # ############################################################################## -import logging - import mock # Odoo req. @@ -23,13 +21,5 @@ import mock # Odoo req. -import odoo - -from .util.odoo_tests import TestBase -from .util.uuidgen import genUuid - -log = logging.getLogger(__name__) - -# Save this method so we can wrap it in a mock. -prev_sql_execute = odoo.sql_db.Cursor.execute +from odoo.tests import TransactionCase @@ -34,9 +24,7 @@ -@odoo.tests.common.at_install(False) -@odoo.tests.common.post_install(True) -class Test(TestBase): - def test_0000_create_xbus_emitter_job(self): +class Test(TransactionCase): + def test_create_xbus_emitter_job(self): """Basic job creation test using public methods provided by this module. """ @@ -50,6 +38,6 @@ emitter = self.env["xbus.emitter"].search([], limit=1) self.assertTrue(emitter) - EVENT_TYPE = genUuid() - DATA = {"foo": "bar"} + event_type = "test_event_type" + data = {"foo": "bar"} @@ -55,3 +43,3 @@ - job = emitter.send_item(EVENT_TYPE, DATA) + job = emitter.send_item(event_type, data) self.assertTrue(job) @@ -57,5 +45,5 @@ self.assertTrue(job) - self.assertEqual(job.event_type, EVENT_TYPE) + self.assertEqual(job.event_type, event_type) self.assertFalse(job.log) # Not processed yet. self.assertEqual(job.items, '{"foo": "bar"}') # JSON-ified self.assertEqual(job.state, "to_send") diff --git a/tests/test_xbus_emitter_job.py b/tests/test_xbus_emitter_job.py index fc2696167416ae24aa725cfeba19fc40d7ee4686_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXJfam9iLnB5..085f5ffd4857a2edba98996491725a260678ea09_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXJfam9iLnB5 100644 --- a/tests/test_xbus_emitter_job.py +++ b/tests/test_xbus_emitter_job.py @@ -18,6 +18,5 @@ # ############################################################################## -import logging from datetime import datetime, timedelta @@ -22,12 +21,5 @@ from datetime import datetime, timedelta -import odoo - -from .util.odoo_tests import TestBase - -log = logging.getLogger(__name__) - -# Save this method so we can wrap it in a mock. -prev_sql_execute = odoo.sql_db.Cursor.execute +from odoo.tests import TransactionCase @@ -32,6 +24,4 @@ -@odoo.tests.common.at_install(False) -@odoo.tests.common.post_install(True) -class Test(TestBase): +class Test(TransactionCase): def test_0001_autovacuum(self): @@ -37,3 +27,3 @@ def test_0001_autovacuum(self): - def past_dt(td): + def past_dt(time_delta): return ( @@ -39,5 +29,7 @@ return ( - (datetime.now() - td).replace(microsecond=0).isoformat(sep=" ") + (datetime.now() - time_delta) + .replace(microsecond=0) + .isoformat(sep=" ") ) class_ = self.env["xbus.emitter.job"] @@ -46,5 +38,5 @@ removal_interval = timedelta(days=class_._removal_interval) one_day = timedelta(days=1) - emitter = self.createAndTest("xbus.emitter", [{}])[0] + emitter = self.env["xbus.emitter"].create([{}])[0] @@ -50,6 +42,5 @@ - self.createAndTest( - "xbus.emitter.job", + self.env["xbus.emitter.job"].create( [ {"emitter_id": emitter.id, "event_type": "evt", "items": ""}, { @@ -66,7 +57,7 @@ "date_done": past_dt(removal_interval - one_day), "date_sent": past_dt(removal_interval - one_day), }, - ], + ] ) class_.autovacuum() @@ -74,5 +65,5 @@ self.assertEqual(2, len(class_.search([]))) def test_0002_autovacuum(self): - def past_dt(td): + def past_dt(time_delta): return ( @@ -78,5 +69,7 @@ return ( - (datetime.now() - td).replace(microsecond=0).isoformat(sep=" ") + (datetime.now() - time_delta) + .replace(microsecond=0) + .isoformat(sep=" ") ) class_ = self.env["xbus.emitter.job"] @@ -85,5 +78,6 @@ removal_interval = timedelta(days=class_._removal_interval) one_day = timedelta(days=1) - emitter = self.createAndTest("xbus.emitter", [{}])[0] + # emitter = self.createAndTest("xbus.emitter", [{}])[0] + emitter = self.env["xbus.emitter"].create([{}])[0] @@ -89,6 +83,6 @@ - jobs = self.createAndTest( - "xbus.emitter.job", + # jobs = self.createAndTest( + jobs = self.env["xbus.emitter.job"].create( [ { "emitter_id": emitter.id, @@ -116,7 +110,7 @@ "state": "sent_success", "date_sent": past_dt(removal_interval - one_day), }, - ], + ] ) self.env.cr.execute( diff --git a/tests/util/__init__.py b/tests/util/__init__.py deleted file mode 100644 diff --git a/tests/util/odoo_tests.py b/tests/util/odoo_tests.py deleted file mode 100644 index fc2696167416ae24aa725cfeba19fc40d7ee4686_dGVzdHMvdXRpbC9vZG9vX3Rlc3RzLnB5..0000000000000000000000000000000000000000 --- a/tests/util/odoo_tests.py +++ /dev/null @@ -1,90 +0,0 @@ -############################################################################## -# -# Xbus emitter for Odoo -# Copyright (C) 2015 XCG Consulting <http://odoo.consulting> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -"""Utilities useful to Odoo tests. -""" - -import datetime - -import odoo.models -import odoo.tests - - -class TestBase(odoo.tests.SingleTransactionCase): - """Provide some test helpers. - """ - - def createAndTest(self, model, value_list, custom_testers=None): - """Create records of the specified Odoo model using the specified - values, and ensure afterwards that records have been succesfully - created and that their values are the same as expected. - - :param custom_testers: Mapping of testing functions to use when - comparing the recorded value to the one asked for by the test. - :type custom_testers: { - 'field': f(test_instance, asked_value, recorded_value), - }. - - :return: The created records. - :rtype: List of odoo.models.BaseModel instances. - """ - - if custom_testers is None: - custom_testers = {} - - records = [] - - for values in value_list: - - # Maintain a local copy as Odoo calls might modify it... - local_values = values.copy() - - record = self.env[model].create(values) - records.append(record) - - self.assertIsInstance(record, odoo.models.BaseModel) - - for field, value in list(local_values.items()): - - tester = ( - custom_testers.get(field) or TestBase.defaultValueTester - ) - tester(self, value, getattr(record, field)) - - return records - - @staticmethod - def defaultValueTester(test_instance, asked_value, recorded_value): - """Ensure what has been recorded is the same as what has been asked - for. - """ - - if type(recorded_value) == datetime.datetime: - recorded_value = recorded_value.__str__() - - # Handle relational fields (Odoo record-sets). - if isinstance(recorded_value, odoo.models.BaseModel): - if isinstance(recorded_value, (tuple, list)): - test_instance.assertEqual(recorded_value.ids, asked_value) - else: - test_instance.assertEqual(recorded_value.id, asked_value) - - else: - test_instance.assertEqual(recorded_value, asked_value) diff --git a/tests/util/uuidgen.py b/tests/util/uuidgen.py deleted file mode 100644 index fc2696167416ae24aa725cfeba19fc40d7ee4686_dGVzdHMvdXRpbC91dWlkZ2VuLnB5..0000000000000000000000000000000000000000 --- a/tests/util/uuidgen.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Utilities to handle unique ID generation. -""" - -import uuid - - -def genUuid(max_chars=None): - """Generate a unique ID and return its hex string representation. - - :param max_chars: Maximum amount of characters to return (might not be a - true UUID then...). - :type max_chars: Integer. - - :rtype: String. - """ - - ret = uuid.uuid4().hex - - if max_chars is not None: - ret = ret[:max_chars] - - return ret diff --git a/views/assets.xml b/views/assets.xml index fc2696167416ae24aa725cfeba19fc40d7ee4686_dmlld3MvYXNzZXRzLnhtbA==..085f5ffd4857a2edba98996491725a260678ea09_dmlld3MvYXNzZXRzLnhtbA== 100644 --- a/views/assets.xml +++ b/views/assets.xml @@ -1,2 +1,2 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <odoo> @@ -2,4 +2,3 @@ <odoo> - <!-- Include assets provided by this module. --> @@ -4,5 +3,8 @@ <!-- Include assets provided by this module. --> - <template id="assets_backend" name="xbus_emitter assets" - inherit_id="web.assets_backend"> + <template + id="assets_backend" + name="xbus_emitter assets" + inherit_id="web.assets_backend" + > <xpath expr="//script[last()]" position="after"> @@ -8,5 +10,7 @@ <xpath expr="//script[last()]" position="after"> - <script type="text/javascript" - src="/xbus_emitter/static/src/js/debug_manager_xbus.js" /> + <script + type="text/javascript" + src="/xbus_emitter/static/src/js/debug_manager_xbus.js" + /> </xpath> </template> @@ -11,4 +15,3 @@ </xpath> </template> - </odoo> diff --git a/views/xbus_emitter.xml b/views/xbus_emitter.xml index fc2696167416ae24aa725cfeba19fc40d7ee4686_dmlld3MveGJ1c19lbWl0dGVyLnhtbA==..085f5ffd4857a2edba98996491725a260678ea09_dmlld3MveGJ1c19lbWl0dGVyLnhtbA== 100644 --- a/views/xbus_emitter.xml +++ b/views/xbus_emitter.xml @@ -1,2 +1,2 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <odoo> @@ -2,8 +2,7 @@ <odoo> - <!-- Views for the xbus.emitter model. --> <record id="xbus_emitter_list" model="ir.ui.view"> <field name="name">xbus_emitter_list</field> <field name="model">xbus.emitter</field> <field name="arch" type="xml"> @@ -4,11 +3,10 @@ <!-- Views for the xbus.emitter model. --> <record id="xbus_emitter_list" model="ir.ui.view"> <field name="name">xbus_emitter_list</field> <field name="model">xbus.emitter</field> <field name="arch" type="xml"> - <tree> <field name="priority" widget="handle" /> <field name="name" /> </tree> @@ -11,8 +9,7 @@ <tree> <field name="priority" widget="handle" /> <field name="name" /> </tree> - </field> </record> @@ -20,5 +17,4 @@ <field name="name">xbus_emitter_form</field> <field name="model">xbus.emitter</field> <field name="arch" type="xml"> - <form> @@ -24,5 +20,4 @@ <form> - <group> <field name="name" /> </group> @@ -30,5 +25,4 @@ <group> <field name="priority" /> </group> - </form> @@ -34,5 +28,4 @@ </form> - </field> </record> @@ -44,7 +37,10 @@ <field name="view_mode">tree,form</field> </record> - <menuitem id="xbus_emitter_menu_command" parent="xbus_emission_menu_command" - sequence="1" action="xbus_emitter_action" /> - + <menuitem + id="xbus_emitter_menu_command" + parent="xbus_emission_menu_command" + sequence="1" + action="xbus_emitter_action" + /> </odoo> diff --git a/views/xbus_emitter_job.xml b/views/xbus_emitter_job.xml index fc2696167416ae24aa725cfeba19fc40d7ee4686_dmlld3MveGJ1c19lbWl0dGVyX2pvYi54bWw=..085f5ffd4857a2edba98996491725a260678ea09_dmlld3MveGJ1c19lbWl0dGVyX2pvYi54bWw= 100644 --- a/views/xbus_emitter_job.xml +++ b/views/xbus_emitter_job.xml @@ -1,2 +1,2 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <odoo> @@ -2,8 +2,7 @@ <odoo> - <!-- Views for the xbus.emitter.job model. --> <record id="xbus_emitter_job_search" model="ir.ui.view"> <field name="name">xbus_emitter_job_search</field> <field name="model">xbus.emitter.job</field> <field name="arch" type="xml"> @@ -4,10 +3,9 @@ <!-- Views for the xbus.emitter.job model. --> <record id="xbus_emitter_job_search" model="ir.ui.view"> <field name="name">xbus_emitter_job_search</field> <field name="model">xbus.emitter.job</field> <field name="arch" type="xml"> - <search> <field name="emitter_id" /> <field name="event_type" /> @@ -15,12 +13,40 @@ <field name="log" /> <field name="chunking" /> - <filter string="To Send" name="state_to_send" domain="[('state', '=', 'to_send')]"/> - <filter string="Sent (Success)" name="state_sent_success" domain="[('state', '=', 'sent_success')]"/> - <filter string="Sent (Error)" name="state_sent_error" domain="[('state', '=', 'sent_error')]"/> - <filter string="Sent, Process Running" name="state_process_running" domain="[('state', '=', 'process_running')]"/> - <filter string="Sent, Process Paused" name="state_process_paused" domain="[('state', '=', 'process_paused')]"/> - <filter string="Sent, Process Done and Successful" name="state_process_done" domain="[('state', '=', 'process_done')]"/> - <filter string="Sent, Process Error" name="state_process_error" domain="[('state', '=', 'process_error')]"/> + <filter + string="To Send" + name="state_to_send" + domain="[('state', '=', 'to_send')]" + /> + <filter + string="Sent (Success)" + name="state_sent_success" + domain="[('state', '=', 'sent_success')]" + /> + <filter + string="Sent (Error)" + name="state_sent_error" + domain="[('state', '=', 'sent_error')]" + /> + <filter + string="Sent, Process Running" + name="state_process_running" + domain="[('state', '=', 'process_running')]" + /> + <filter + string="Sent, Process Paused" + name="state_process_paused" + domain="[('state', '=', 'process_paused')]" + /> + <filter + string="Sent, Process Done and Successful" + name="state_process_done" + domain="[('state', '=', 'process_done')]" + /> + <filter + string="Sent, Process Error" + name="state_process_error" + domain="[('state', '=', 'process_error')]" + /> <group string="Group By"> @@ -25,7 +51,19 @@ <group string="Group By"> - <filter string="Emitter" name="group_by_emitter" context="{'group_by': 'emitter_id'}"/> - <filter string="State" name="group_by_state" context="{'group_by': 'state'}"/> - <filter string="Chunking" name="group_by_chunking" context="{'group_by': 'chunking'}"/> + <filter + string="Emitter" + name="group_by_emitter" + context="{'group_by': 'emitter_id'}" + /> + <filter + string="State" + name="group_by_state" + context="{'group_by': 'state'}" + /> + <filter + string="Chunking" + name="group_by_chunking" + context="{'group_by': 'chunking'}" + /> </group> </search> @@ -30,6 +68,5 @@ </group> </search> - </field> </record> @@ -37,9 +74,10 @@ <field name="name">xbus_emitter_job_list</field> <field name="model">xbus.emitter.job</field> <field name="arch" type="xml"> - - <tree decoration-success="state in ('sent_success', 'process_done')" - decoration-danger="state in ('sent_error', 'process_error')"> + <tree + decoration-success="state in ('sent_success', 'process_done')" + decoration-danger="state in ('sent_error', 'process_error')" + > <field name="emitter_id" /> <field name="event_type" /> <field name="create_date" /> @@ -50,7 +88,6 @@ <!-- Utility fields. --> <field name="log" invisible="1" /> </tree> - </field> </record> @@ -58,6 +95,5 @@ <field name="name">xbus_emitter_job_form</field> <field name="model">xbus.emitter.job</field> <field name="arch" type="xml"> - <form> <header> @@ -62,5 +98,9 @@ <form> <header> - <field name="state" widget="statusbar" statusbar_visible="to_send,sent_success,process_done"/> + <field + name="state" + widget="statusbar" + statusbar_visible="to_send,sent_success,process_done" + /> </header> <sheet> @@ -65,6 +105,5 @@ </header> <sheet> - <div class="oe_left oe_title"> <h1> <field name="event_type" /> @@ -72,9 +111,13 @@ </div> <div name="button_box" class="oe_right oe_button_box"> - <button name="open_source_record" type="object" - class="oe_inline oe_stat_button" icon="fa-link" - attrs="{'invisible': [('record_model', '=', False)]}"> + <button + name="open_source_record" + type="object" + class="oe_inline oe_stat_button" + icon="fa-link" + attrs="{'invisible': [('record_model', '=', False)]}" + > <div class="o_stat_info"> <span class="o_stat_value">1</span> <span class="o_stat_text">Record</span> @@ -83,7 +126,6 @@ </div> <group col="3"> - <group> <field name="emitter_id" /> <field name="record_model" /> @@ -102,7 +144,6 @@ <field name="process_id" /> <field name="chunking" /> </group> - </group> <group> @@ -106,7 +147,10 @@ </group> <group> - <label for="items" attrs="{'invisible': [('items', '=', False)]}"/> + <label + for="items" + attrs="{'invisible': [('items', '=', False)]}" + /> <field name="items" nolabel="1" colspan="2" /> </group> @@ -110,8 +154,7 @@ <field name="items" nolabel="1" colspan="2" /> </group> - <group> <label for="log" /> <field name="log" nolabel="1" colspan="2" /> </group> @@ -114,7 +157,6 @@ <group> <label for="log" /> <field name="log" nolabel="1" colspan="2" /> </group> - </sheet> </form> @@ -119,6 +161,5 @@ </sheet> </form> - </field> </record> @@ -130,7 +171,10 @@ <field name="view_mode">tree,form</field> </record> - <menuitem id="xbus_emitter_job_menu_command" parent="xbus_emission_menu_command" - sequence="2" action="xbus_emitter_job_action" /> - + <menuitem + id="xbus_emitter_job_menu_command" + parent="xbus_emission_menu_command" + sequence="2" + action="xbus_emitter_job_action" + /> </odoo>