From 44cd716aea8c40d3d127e432fdbed0bd66beec55 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 28 Oct 2020 16:47:41 +0100 Subject: [PATCH 01/45] Add method to patch a method to be automatically delayed This patch method has to be called in ``_register_hook``. When a method is patched, any call to the method will not directly execute the method's body, but will instead enqueue a job. When a ``context_key`` is set when calling ``_patch_job_auto_delay``, the patched method is automatically delayed only when this key is ``True`` in the caller's context. It is advised to patch the method with a ``context_key``, because making the automatic delay *in any case* can produce nasty and unexpected side effects (e.g. another module calls the method and expects it to be computed before doing something else, expecting a result, ...). A typical use case is when a method in a module we don't control is called synchronously in the middle of another method, and we'd like all the calls to this method become asynchronous. It relies on https://github.com/OCA/queue/pull/274 that deprecates the `@job` decorator. --- queue_job/models/base.py | 92 +++++++++++++++++++++ test_queue_job/models/test_models.py | 27 ++++++ test_queue_job/tests/__init__.py | 1 + test_queue_job/tests/test_job_auto_delay.py | 54 ++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 test_queue_job/tests/test_job_auto_delay.py diff --git a/queue_job/models/base.py b/queue_job/models/base.py index 3bb4d78361..116eb495f9 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -1,6 +1,7 @@ # Copyright 2016 Camptocamp # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +import functools import inspect import logging import os @@ -108,3 +109,94 @@ def with_delay( channel=channel, identity_key=identity_key, ) + + def _patch_job_auto_delay(self, method_name, context_key=None): + """Patch a method to be automatically delayed as job method when called + + This patch method has to be called in ``_register_hook`` (example + below). + + When a method is patched, any call to the method will not directly + execute the method's body, but will instead enqueue a job. + + When a ``context_key`` is set when calling ``_patch_job_auto_delay``, + the patched method is automatically delayed only when this key is + ``True`` in the caller's context. It is advised to patch the method + with a ``context_key``, because making the automatic delay *in any + case* can produce nasty and unexpected side effects (e.g. another + module calls the method and expects it to be computed before doing + something else, expecting a result, ...). + + A typical use case is when a method in a module we don't control is + called synchronously in the middle of another method, and we'd like all + the calls to this method become asynchronous. + + The options of the job usually passed to ``with_delay()`` (priority, + description, identity_key, ...) can be returned in a dictionary by a + method named after the name of the method suffixed by ``_job_options`` + which takes the same parameters as the initial method. + + It is still possible to force synchronous execution of the method by + setting a key ``_job_force_sync`` to True in the environment context. + + Example patching the "foo" method to be automatically delayed as job + (the job options method is optional): + + .. code-block:: python + + # original method: + def foo(self, arg1): + print("hello", arg1) + + def large_method(self): + # doing a lot of things + self.foo("world) + # doing a lot of other things + + def button_x(self): + self.with_context(auto_delay_foo=True).large_method() + + # auto delay patch: + def foo_job_options(self, arg1): + return { + "priority": 100, + "description": "Saying hello to {}".format(arg1) + } + + def _register_hook(self): + self._patch_method( + "foo", + self._patch_job_auto_delay("foo", context_key="auto_delay_foo") + ) + return super()._register_hook() + + The result when ``button_x`` is called, is that a new job for ``foo`` + is delayed. + """ + + def auto_delay_wrapper(self, *args, **kwargs): + # when no context_key is set, we delay in any case (warning, can be + # dangerous) + context_delay = self.env.context.get(context_key) if context_key else True + if ( + self.env.context.get("job_uuid") + or not context_delay + or self.env.context.get("_job_force_sync") + or self.env.context.get("test_queue_job_no_delay") + ): + # we are in the job execution + return auto_delay_wrapper.origin(self, *args, **kwargs) + else: + # replace the synchronous call by a job on itself + method_name = auto_delay_wrapper.origin.__name__ + job_options_method = getattr( + self, "{}_job_options".format(method_name), None + ) + job_options = {} + if job_options_method: + job_options.update(job_options_method(*args, **kwargs)) + delayed = self.with_delay(**job_options) + return getattr(delayed, method_name)(*args, **kwargs) + + origin = getattr(self, method_name) + return functools.update_wrapper(auto_delay_wrapper, origin) diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index 36fdb1c6f9..9bd5b2c9cc 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -61,6 +61,33 @@ def job_alter_mutable(self, mutable_arg, mutable_kwarg=None): mutable_kwarg["b"] = 2 return mutable_arg, mutable_kwarg + def delay_me(self, arg, kwarg=None): + return arg, kwarg + + def delay_me_options_job_options(self): + return { + "identity_key": "my_job_identity", + } + + def delay_me_options(self): + return "ok" + + def delay_me_context_key(self): + return "ok" + + def _register_hook(self): + self._patch_method("delay_me", self._patch_job_auto_delay("delay_me")) + self._patch_method( + "delay_me_options", self._patch_job_auto_delay("delay_me_options") + ) + self._patch_method( + "delay_me_context_key", + self._patch_job_auto_delay( + "delay_me_context_key", context_key="auto_delay_delay_me_context_key" + ), + ) + return super()._register_hook() + class TestQueueChannel(models.Model): diff --git a/test_queue_job/tests/__init__.py b/test_queue_job/tests/__init__.py index 9af8df15a0..502a0752fd 100644 --- a/test_queue_job/tests/__init__.py +++ b/test_queue_job/tests/__init__.py @@ -1,4 +1,5 @@ from . import test_autovacuum from . import test_job +from . import test_job_auto_delay from . import test_job_channels from . import test_related_actions diff --git a/test_queue_job/tests/test_job_auto_delay.py b/test_queue_job/tests/test_job_auto_delay.py new file mode 100644 index 0000000000..5549fc7487 --- /dev/null +++ b/test_queue_job/tests/test_job_auto_delay.py @@ -0,0 +1,54 @@ +# Copyright 2020 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) + +from odoo.tests.common import tagged + +from odoo.addons.queue_job.job import Job + +from .common import JobCommonCase + + +@tagged("post_install", "-at_install") +class TestJobAutoDelay(JobCommonCase): + """Test auto delay of jobs""" + + def test_auto_delay(self): + """method decorated by @job_auto_delay is automatically delayed""" + result = self.env["test.queue.job"].delay_me(1, kwarg=2) + self.assertTrue(isinstance(result, Job)) + self.assertEqual(result.args, (1,)) + self.assertEqual(result.kwargs, {"kwarg": 2}) + + def test_auto_delay_options(self): + """method automatically delayed une _job_options arguments""" + result = self.env["test.queue.job"].delay_me_options() + self.assertTrue(isinstance(result, Job)) + self.assertEqual(result.identity_key, "my_job_identity") + + def test_auto_delay_inside_job(self): + """when a delayed job is processed, it must not delay itself""" + job_ = self.env["test.queue.job"].delay_me(1, kwarg=2) + self.assertTrue(job_.perform(), (1, 2)) + + def test_auto_delay_force_sync(self): + """method forced to run synchronously""" + result = ( + self.env["test.queue.job"] + .with_context(_job_force_sync=True) + .delay_me(1, kwarg=2) + ) + self.assertTrue(result, (1, 2)) + + def test_auto_delay_context_key_set(self): + """patched with context_key delays only if context keys is set""" + result = ( + self.env["test.queue.job"] + .with_context(auto_delay_delay_me_context_key=True) + .delay_me_context_key() + ) + self.assertTrue(isinstance(result, Job)) + + def test_auto_delay_context_key_unset(self): + """patched with context_key do not delay if context keys is not set""" + result = self.env["test.queue.job"].delay_me_context_key() + self.assertEqual(result, "ok") From 01c8411d1c67311f6ab23d08937828482c755ecb Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sun, 22 Nov 2020 20:35:01 +0000 Subject: [PATCH 02/45] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: queue-13.0/queue-13.0-queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/ --- queue_job/i18n/de.po | 6 ------ queue_job/i18n/zh_CN.po | 5 ----- 2 files changed, 11 deletions(-) diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index 0ce7ee2439..7e051fd879 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -44,7 +44,6 @@ msgstr "Aktivitäten" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__activity_exception_decoration #, fuzzy -#| msgid "Exception Information" msgid "Activity Exception Decoration" msgstr "Exception-Information" @@ -274,7 +273,6 @@ msgstr "Identitätsschlüssel" #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, fuzzy, python-format -#| msgid "The selected jobs will be requeued." msgid "If both parameters are 0, ALL jobs will be requeued!" msgstr "Die ausgewählten Jobs werden erneut eingereiht." @@ -337,7 +335,6 @@ msgstr "Job-Warteschlangenverwalter" #. module: queue_job #: model:ir.model.fields.selection,name:queue_job.selection__ir_model_fields__ttype__job_serialized #, fuzzy -#| msgid "Job failed" msgid "Job Serialized" msgstr "Job ist fehlgeschlagen" @@ -426,7 +423,6 @@ msgstr "Nachrichten" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__method #, fuzzy -#| msgid "Method Name" msgid "Method" msgstr "Methodenname" @@ -565,7 +561,6 @@ msgstr "Datensatz" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__records #, fuzzy -#| msgid "Record" msgid "Record(s)" msgstr "Datensatz" @@ -577,7 +572,6 @@ msgstr "Zugehörige Aktion anzeigen" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__edit_related_action #, fuzzy -#| msgid "Related Record" msgid "Related Action" msgstr "Zugehöriger Datensatz" diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po index 2117912c34..d61e9f56ca 100644 --- a/queue_job/i18n/zh_CN.po +++ b/queue_job/i18n/zh_CN.po @@ -272,7 +272,6 @@ msgstr "身份密钥" #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, fuzzy, python-format -#| msgid "The selected jobs will be requeued." msgid "If both parameters are 0, ALL jobs will be requeued!" msgstr "所选作业将重新排队。" @@ -334,7 +333,6 @@ msgstr "作业队列管理员" #. module: queue_job #: model:ir.model.fields.selection,name:queue_job.selection__ir_model_fields__ttype__job_serialized #, fuzzy -#| msgid "Job failed" msgid "Job Serialized" msgstr "作业失败" @@ -423,7 +421,6 @@ msgstr "消息" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__method #, fuzzy -#| msgid "Method Name" msgid "Method" msgstr "方法名称" @@ -562,7 +559,6 @@ msgstr "记录" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__records #, fuzzy -#| msgid "Record" msgid "Record(s)" msgstr "记录" @@ -574,7 +570,6 @@ msgstr "相关的" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__edit_related_action #, fuzzy -#| msgid "Related Record" msgid "Related Action" msgstr "相关记录" From 3f78ea3b83e418f99ed7f96bcf4b2ed39d5a52af Mon Sep 17 00:00:00 2001 From: Atchuthan Ubendran Date: Mon, 30 Nov 2020 17:52:27 +0530 Subject: [PATCH 03/45] [FIX] queue_job: USAGE update --- queue_job/readme/USAGE.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue_job/readme/USAGE.rst b/queue_job/readme/USAGE.rst index 6c472eccf9..c8ff94b793 100644 --- a/queue_job/readme/USAGE.rst +++ b/queue_job/readme/USAGE.rst @@ -25,7 +25,7 @@ Example of job function: .. code-block:: XML - + action_done From 868306c2f2bcc28d748ec12a8d7272d979c2e5fe Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 30 Nov 2020 15:51:06 +0000 Subject: [PATCH 04/45] [UPD] README.rst --- queue_job/README.rst | 2 +- queue_job/static/description/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/queue_job/README.rst b/queue_job/README.rst index a6db9d9247..e3eaefd437 100644 --- a/queue_job/README.rst +++ b/queue_job/README.rst @@ -159,7 +159,7 @@ Example of job function: .. code-block:: XML - + action_done diff --git a/queue_job/static/description/index.html b/queue_job/static/description/index.html index 037e104684..ded805e543 100644 --- a/queue_job/static/description/index.html +++ b/queue_job/static/description/index.html @@ -508,7 +508,7 @@

Developers

Example of job function:

 <record id="job_function_sale_order_action_done" model="queue.job.function">
-    <field name="model_id" ref="sale.model_sale_order"</field>
+    <field name="model_id" ref="sale.model_sale_order" />
     <field name="method">action_done</field>
     <field name="channel_id" ref="channel_sale" />
     <field name="related_action" eval='{"func_name": "custom_related_action"}' />

From d5c74d2cf44067880a2a1bb0fa41a46b52d2dfb8 Mon Sep 17 00:00:00 2001
From: Guewen Baconnier 
Date: Tue, 1 Dec 2020 17:52:59 +0100
Subject: [PATCH 05/45] Fix required sudo in Job

Following changes of https://github.com/OCA/queue/pull/281
The initial sudo() is lost when we call "with_env()" with a False su
flag. Ensure the read job.record keeps a su flag.
---
 queue_job/job.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/job.py b/queue_job/job.py
index 568808c002..19742deb48 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -377,7 +377,7 @@ def enqueue(
     def db_record_from_uuid(env, job_uuid):
         model = env["queue.job"].sudo()
         record = model.search([("uuid", "=", job_uuid)], limit=1)
-        return record.with_env(env)
+        return record.with_env(env).sudo()
 
     def __init__(
         self,

From fce6c5d3c2c7d5bcc366e6404bda9962cf421a18 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Thu, 3 Dec 2020 07:16:15 +0000
Subject: [PATCH 06/45] queue_job 13.0.3.2.1

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 537db47cd0..d7df69c9b0 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.2.0",
+    "version": "13.0.3.2.1",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From d070c26a5b0c5ae2687a84cd17a72ff74ee68232 Mon Sep 17 00:00:00 2001
From: Nils Hamerlinck 
Date: Sun, 3 Jan 2021 19:31:16 +0700
Subject: [PATCH 07/45] [FIX] run doctests as part of standard tests

---
 queue_job/tests/common.py               | 38 +++++++++++++++++++++++++
 queue_job/tests/test_runner_channels.py |  7 ++---
 queue_job/tests/test_runner_runner.py   |  7 ++---
 3 files changed, 42 insertions(+), 10 deletions(-)

diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py
index 6795965b76..efbd0e9dba 100644
--- a/queue_job/tests/common.py
+++ b/queue_job/tests/common.py
@@ -1,9 +1,13 @@
 # Copyright 2019 Camptocamp
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+import doctest
 from contextlib import contextmanager
 
 import mock
 
+from odoo.tests import BaseCase, tagged
+
+# pylint: disable=odoo-addons-relative-import
 from odoo.addons.queue_job.job import Job
 
 
@@ -96,3 +100,37 @@ def test_export(self):
         delayable = mock.MagicMock(name="DelayableBinding")
         delayable_cls.return_value = delayable
         yield delayable_cls, delayable
+
+
+@tagged("doctest")
+class OdooDocTestCase(BaseCase):
+    """
+    We need a custom DocTestCase class in order to:
+    - define test_tags to run as part of standard tests
+    - output a more meaningful test name than default "DocTestCase.runTest"
+    """
+
+    __qualname__ = "doctests for "
+
+    def __init__(self, test):
+        self.__test = test
+        self.__name = test._dt_test.name
+        super().__init__(self.__name)
+
+    def __getattr__(self, item):
+        if item == self.__name:
+            return self.__test
+
+
+def load_doctests(module):
+    """
+    Generates a tests loading method for the doctests of the given module
+    https://docs.python.org/3/library/unittest.html#load-tests-protocol
+    """
+
+    def load_tests(loader, tests, ignore):
+        for test in doctest.DocTestSuite(module):
+            tests.addTest(OdooDocTestCase(test))
+        return tests
+
+    return load_tests
diff --git a/queue_job/tests/test_runner_channels.py b/queue_job/tests/test_runner_channels.py
index 93333fa490..d323d00683 100644
--- a/queue_job/tests/test_runner_channels.py
+++ b/queue_job/tests/test_runner_channels.py
@@ -1,13 +1,10 @@
 # Copyright 2015-2016 Camptocamp SA
 # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
 
-import doctest
-
 # pylint: disable=odoo-addons-relative-import
 # we are testing, we want to test as we were an external consumer of the API
 from odoo.addons.queue_job.jobrunner import channels
 
+from .common import load_doctests
 
-def load_tests(loader, tests, ignore):
-    tests.addTests(doctest.DocTestSuite(channels))
-    return tests
+load_tests = load_doctests(channels)
diff --git a/queue_job/tests/test_runner_runner.py b/queue_job/tests/test_runner_runner.py
index 817ac6396e..c6486e27ef 100644
--- a/queue_job/tests/test_runner_runner.py
+++ b/queue_job/tests/test_runner_runner.py
@@ -1,13 +1,10 @@
 # Copyright 2015-2016 Camptocamp SA
 # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
 
-import doctest
-
 # pylint: disable=odoo-addons-relative-import
 # we are testing, we want to test as we were an external consumer of the API
 from odoo.addons.queue_job.jobrunner import runner
 
+from .common import load_doctests
 
-def load_tests(loader, tests, ignore):
-    tests.addTests(doctest.DocTestSuite(runner))
-    return tests
+load_tests = load_doctests(runner)

From 5180e51054a7682b12d435d0e547c4590b5d03f7 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Mon, 4 Jan 2021 08:05:54 +0000
Subject: [PATCH 08/45] queue_job 13.0.3.2.2

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index d7df69c9b0..0a91b2228d 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.2.1",
+    "version": "13.0.3.2.2",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From 87ecf7583a1331850d831434575a091f9b1975d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com>
Date: Mon, 18 Jan 2021 03:44:33 +0000
Subject: [PATCH 09/45] Translated using Weblate (Chinese (Simplified))

Currently translated at 86.5% (116 of 134 strings)

Translation: queue-13.0/queue-13.0-queue_job
Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/zh_CN/
---
 queue_job/i18n/zh_CN.po | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po
index d61e9f56ca..df08587c5f 100644
--- a/queue_job/i18n/zh_CN.po
+++ b/queue_job/i18n/zh_CN.po
@@ -6,7 +6,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Odoo Server 12.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2020-03-23 06:13+0000\n"
+"PO-Revision-Date: 2021-01-18 05:44+0000\n"
 "Last-Translator: 黎伟杰 <674416404@qq.com>\n"
 "Language-Team: none\n"
 "Language: zh_CN\n"
@@ -14,7 +14,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: \n"
 "Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Weblate 3.10\n"
+"X-Generator: Weblate 4.3.2\n"
 
 #. module: queue_job
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
@@ -29,7 +29,7 @@ msgstr ""
 #: code:addons/queue_job/controllers/main.py:0
 #, python-format
 msgid "Access Denied"
-msgstr ""
+msgstr "拒绝访问"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__message_needaction
@@ -217,12 +217,12 @@ msgstr "失败"
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_ir_model_fields__ttype
 msgid "Field Type"
-msgstr ""
+msgstr "字段类型"
 
 #. module: queue_job
 #: model:ir.model,name:queue_job.model_ir_model_fields
 msgid "Fields"
-msgstr ""
+msgstr "字段"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__message_follower_ids

From 21382ccaec1b337f311cdbc642b5cdf3fd24edcf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com>
Date: Fri, 22 Jan 2021 10:15:13 +0000
Subject: [PATCH 10/45] Translated using Weblate (Chinese (Simplified))

Currently translated at 89.5% (120 of 134 strings)

Translation: queue-13.0/queue-13.0-queue_job
Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/zh_CN/
---
 queue_job/i18n/zh_CN.po | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po
index df08587c5f..c4cfcc49c8 100644
--- a/queue_job/i18n/zh_CN.po
+++ b/queue_job/i18n/zh_CN.po
@@ -6,7 +6,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Odoo Server 12.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2021-01-18 05:44+0000\n"
+"PO-Revision-Date: 2021-01-22 12:44+0000\n"
 "Last-Translator: 黎伟杰 <674416404@qq.com>\n"
 "Language-Team: none\n"
 "Language: zh_CN\n"
@@ -332,9 +332,8 @@ msgstr "作业队列管理员"
 
 #. module: queue_job
 #: model:ir.model.fields.selection,name:queue_job.selection__ir_model_fields__ttype__job_serialized
-#, fuzzy
 msgid "Job Serialized"
-msgstr "作业失败"
+msgstr "作业序列化"
 
 #. module: queue_job
 #: model:mail.message.subtype,name:queue_job.mt_job_failed
@@ -362,12 +361,12 @@ msgstr "作业"
 #: model:ir.cron,cron_name:queue_job.ir_cron_queue_job_garbage_collector
 #: model:ir.cron,name:queue_job.ir_cron_queue_job_garbage_collector
 msgid "Jobs Garbage Collector"
-msgstr ""
+msgstr "作业垃圾收集器"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__kwargs
 msgid "Kwargs"
-msgstr "关键字参数"
+msgstr "Kwargs"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job____last_update
@@ -420,9 +419,8 @@ msgstr "消息"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__method
-#, fuzzy
 msgid "Method"
-msgstr "方法名称"
+msgstr "方法"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__method_name
@@ -439,7 +437,7 @@ msgstr "模型"
 #: code:addons/queue_job/models/queue_job.py:0
 #, python-format
 msgid "Model {} not found"
-msgstr ""
+msgstr "Model {} 找不到"
 
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__name

From c77a14db27cddc78fe0f715d96c888180ad45f30 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Tue, 26 Jan 2021 10:27:19 +0000
Subject: [PATCH 11/45] test_queue_job 13.0.2.2.0

---
 test_queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test_queue_job/__manifest__.py b/test_queue_job/__manifest__.py
index c116362ff3..5c1302efaa 100644
--- a/test_queue_job/__manifest__.py
+++ b/test_queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Queue Job Tests",
-    "version": "13.0.2.1.0",
+    "version": "13.0.2.2.0",
     "author": "Camptocamp,Odoo Community Association (OCA)",
     "license": "LGPL-3",
     "category": "Generic Modules",

From 8fe68a475b9dd886c63706509cee2e73dca49002 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Tue, 26 Jan 2021 10:27:21 +0000
Subject: [PATCH 12/45] queue_job 13.0.3.3.0

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 0a91b2228d..38706b5a08 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.2.2",
+    "version": "13.0.3.3.0",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From 31003b3a60cbadcf72b52281942dc4bdafc028dc Mon Sep 17 00:00:00 2001
From: Guewen Baconnier 
Date: Mon, 1 Feb 2021 11:28:10 +0100
Subject: [PATCH 13/45] Change technical fields to read-only

These fields should not be changed by users.
---
 queue_job/models/queue_job.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py
index 1da0eaf86d..dec458809e 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -111,8 +111,8 @@ class QueueJob(models.Model):
         compute="_compute_channel", inverse="_inverse_channel", store=True, index=True
     )
 
-    identity_key = fields.Char()
-    worker_pid = fields.Integer()
+    identity_key = fields.Char(readonly=True)
+    worker_pid = fields.Integer(readonly=True)
 
     def init(self):
         self._cr.execute(

From 510dc4fee20c6f7830b5a39ae7b117723a45ab10 Mon Sep 17 00:00:00 2001
From: Guewen Baconnier 
Date: Mon, 1 Feb 2021 10:32:56 +0100
Subject: [PATCH 14/45] Remove initial create notification and follower

Everytime a job is created, a mail.message "Queue Job created" is
created. This is useless, as we already have the creation date and user,
and nobody will ever want to receive a notification for a created job
anyway.

Removing the on creation auto-subscription of the user that created the
job makes sense as well since we automatically subscribe the queue job
managers for failures. Add the owner of the jobs to the followers on
failures only as well.

It allows to remove a lot of insertions of records (and of deletions
when autovacuuming jobs).
---
 queue_job/models/queue_job.py    | 10 +++++--
 test_queue_job/tests/test_job.py | 47 ++++++++++++++++++++------------
 2 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py
index 1da0eaf86d..1be01406a4 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -186,7 +186,10 @@ def create(self, vals_list):
             raise exceptions.AccessError(
                 _("Queue jobs must created by calling 'with_delay()'.")
             )
-        return super().create(vals_list)
+        return super(
+            QueueJob,
+            self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True),
+        ).create(vals_list)
 
     def write(self, vals):
         if self.env.context.get("_job_edit_sentinel") is not self.EDIT_SENTINEL:
@@ -243,9 +246,10 @@ def _message_post_on_failure(self):
         # subscribe the users now to avoid to subscribe them
         # at every job creation
         domain = self._subscribe_users_domain()
-        users = self.env["res.users"].search(domain)
-        self.message_subscribe(partner_ids=users.mapped("partner_id").ids)
+        base_users = self.env["res.users"].search(domain)
         for record in self:
+            users = base_users | record.user_id
+            record.message_subscribe(partner_ids=users.mapped("partner_id").ids)
             msg = record._message_failed_job()
             if msg:
                 record.message_post(body=msg, subtype="queue_job.mt_job_failed")
diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py
index f9b679cc27..0b8245618d 100644
--- a/test_queue_job/tests/test_job.py
+++ b/test_queue_job/tests/test_job.py
@@ -7,7 +7,6 @@
 import mock
 
 import odoo.tests.common as common
-from odoo import SUPERUSER_ID
 
 from odoo.addons.queue_job.exception import (
     FailedJobError,
@@ -481,7 +480,7 @@ def test_message_when_write_fail(self):
         stored.write({"state": "failed"})
         self.assertEqual(stored.state, FAILED)
         messages = stored.message_ids
-        self.assertEqual(len(messages), 2)
+        self.assertEqual(len(messages), 1)
 
     def test_follower_when_write_fail(self):
         """Check that inactive users doesn't are not followers even if
@@ -540,6 +539,22 @@ def setUp(self):
         User = self.env["res.users"]
         Company = self.env["res.company"]
         Partner = self.env["res.partner"]
+
+        main_company = self.env.ref("base.main_company")
+
+        self.partner_user = Partner.create(
+            {"name": "Simple User", "email": "simple.user@example.com"}
+        )
+        self.simple_user = User.create(
+            {
+                "partner_id": self.partner_user.id,
+                "company_ids": [(4, main_company.id)],
+                "login": "simple_user",
+                "name": "simple user",
+                "groups_id": [],
+            }
+        )
+
         self.other_partner_a = Partner.create(
             {"name": "My Company a", "is_company": True, "email": "test@tes.ttest"}
         )
@@ -556,7 +571,7 @@ def setUp(self):
                 "company_id": self.other_company_a.id,
                 "company_ids": [(4, self.other_company_a.id)],
                 "login": "my_login a",
-                "name": "my user",
+                "name": "my user A",
                 "groups_id": [(4, grp_queue_job_manager)],
             }
         )
@@ -576,16 +591,11 @@ def setUp(self):
                 "company_id": self.other_company_b.id,
                 "company_ids": [(4, self.other_company_b.id)],
                 "login": "my_login_b",
-                "name": "my user 1",
+                "name": "my user B",
                 "groups_id": [(4, grp_queue_job_manager)],
             }
         )
 
-    def _subscribe_users(self, stored):
-        domain = stored._subscribe_users_domain()
-        users = self.env["res.users"].search(domain)
-        stored.message_subscribe(partner_ids=users.mapped("partner_id").ids)
-
     def _create_job(self, env):
         self.cr.execute("delete from queue_job")
         env["test.queue.job"].with_delay().testing_method()
@@ -631,11 +641,14 @@ def test_job_subscription(self):
         # queue_job.group_queue_job_manager must be followers
         User = self.env["res.users"]
         no_company_context = dict(self.env.context, company_id=None)
-        no_company_env = self.env(context=no_company_context)
+        no_company_env = self.env(user=self.simple_user, context=no_company_context)
         stored = self._create_job(no_company_env)
-        self._subscribe_users(stored)
-        users = User.with_context(active_test=False).search(
-            [("groups_id", "=", self.ref("queue_job.group_queue_job_manager"))]
+        stored._message_post_on_failure()
+        users = (
+            User.search(
+                [("groups_id", "=", self.ref("queue_job.group_queue_job_manager"))]
+            )
+            + stored.user_id
         )
         self.assertEqual(len(stored.message_follower_ids), len(users))
         expected_partners = [u.partner_id for u in users]
@@ -649,13 +662,13 @@ def test_job_subscription(self):
         # jobs created for a specific company_id are followed only by
         # company's members
         company_a_context = dict(self.env.context, company_id=self.other_company_a.id)
-        company_a_env = self.env(context=company_a_context)
+        company_a_env = self.env(user=self.simple_user, context=company_a_context)
         stored = self._create_job(company_a_env)
         stored.with_user(self.other_user_a.id)
-        self._subscribe_users(stored)
-        # 2 because admin + self.other_partner_a
+        stored._message_post_on_failure()
+        # 2 because simple_user (creator of job) + self.other_partner_a
         self.assertEqual(len(stored.message_follower_ids), 2)
-        users = User.browse([SUPERUSER_ID, self.other_user_a.id])
+        users = self.simple_user + self.other_user_a
         expected_partners = [u.partner_id for u in users]
         self.assertSetEqual(
             set(stored.message_follower_ids.mapped("partner_id")),

From 56b56f4e8e830e1bd63ea96313f2e4035584f382 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Tue, 2 Feb 2021 07:11:39 +0000
Subject: [PATCH 15/45] queue_job 13.0.3.3.1

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 38706b5a08..d05b46387e 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.3.0",
+    "version": "13.0.3.3.1",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From bb88d1efcd9da47591c915d770c4af7f84315aa5 Mon Sep 17 00:00:00 2001
From: Guewen Baconnier 
Date: Tue, 2 Feb 2021 07:57:50 +0100
Subject: [PATCH 16/45] Optimize queue.job creation

Several fields on queue.job are initialized using computed fields,
then never changed again. On creation of a queue.job record, we'll have
an initial INSERT + at least one following UPDATE for the computed
fields.

Replace all the stored computed fields by a raw initialization of the
values in `Job.store()` when the job is created, so we have only a
single INSERT.

Highlights:

* as channel is no longer a compute/inverse field, override_channel is
  useless, I dropped it (actually the same value was stored in both
  channel and override_channel as the channel field was stored)
* one functional diff is that now, when changing a channel on a
  job.function, the channel is no longer synchronized on existing jobs,
  it will be applied only on new jobs: actually this is an improvement,
  because it was impossible to change the channel of a job function
  in a large queue_job table as it meant writing on all the done/started
  jobs
* searching the queue.job.function is now cached, as each job using the
  same will run a query on queue_job_function
---
 queue_job/controllers/main.py              |   2 +-
 queue_job/job.py                           |  47 +++++----
 queue_job/models/queue_job.py              | 106 ++++++---------------
 queue_job/tests/test_model_job_function.py |   3 +-
 4 files changed, 65 insertions(+), 93 deletions(-)

diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py
index a0814bbbd5..eaa78e9a3b 100644
--- a/queue_job/controllers/main.py
+++ b/queue_job/controllers/main.py
@@ -113,7 +113,7 @@ def retry_postpone(job, message, seconds=None):
 
     @http.route("/queue_job/create_test_job", type="http", auth="user")
     def create_test_job(
-        self, priority=None, max_retries=None, channel="root", description="Test job"
+        self, priority=None, max_retries=None, channel=None, description="Test job"
     ):
         if not http.request.env.user.has_group("base.group_erp_manager"):
             raise Forbidden(_("Access Denied"))
diff --git a/queue_job/job.py b/queue_job/job.py
index 19742deb48..45dca7cbb6 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -441,13 +441,7 @@ def __init__(
         self.job_model_name = "queue.job"
 
         self.job_config = (
-            self.env["queue.job.function"]
-            .sudo()
-            .job_config(
-                self.env["queue.job.function"].job_function_name(
-                    self.model_name, self.method_name
-                )
-            )
+            self.env["queue.job.function"].sudo().job_config(self.job_function_name)
         )
 
         self.state = PENDING
@@ -560,27 +554,35 @@ def store(self):
         if db_record:
             db_record.with_context(_job_edit_sentinel=edit_sentinel).write(vals)
         else:
-            date_created = self.date_created
-            # The following values must never be modified after the
-            # creation of the job
             vals.update(
                 {
+                    "user_id": self.env.uid,
+                    "channel": self.channel,
+                    # The following values must never be modified after the
+                    # creation of the job
                     "uuid": self.uuid,
                     "name": self.description,
-                    "date_created": date_created,
+                    "func_string": self.func_string,
+                    "date_created": self.date_created,
+                    "model_name": self.recordset._name,
                     "method_name": self.method_name,
+                    "job_function_id": self.job_config.job_function_id,
+                    "channel_method_name": self.job_function_name,
                     "records": self.recordset,
                     "args": self.args,
                     "kwargs": self.kwargs,
                 }
             )
-            # it the channel is not specified, lets the job_model compute
-            # the right one to use
-            if self.channel:
-                vals.update({"channel": self.channel})
-
             job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create(vals)
 
+    @property
+    def func_string(self):
+        model = repr(self.recordset)
+        args = [repr(arg) for arg in self.args]
+        kwargs = ["{}={!r}".format(key, val) for key, val in self.kwargs.items()]
+        all_args = ", ".join(args + kwargs)
+        return "{}.{}({})".format(model, self.method_name, all_args)
+
     def db_record(self):
         return self.db_record_from_uuid(self.env, self.uuid)
 
@@ -589,6 +591,11 @@ def func(self):
         recordset = self.recordset.with_context(job_uuid=self.uuid)
         return getattr(recordset, self.method_name)
 
+    @property
+    def job_function_name(self):
+        func_model = self.env["queue.job.function"].sudo()
+        return func_model.job_function_name(self.recordset._name, self.method_name)
+
     @property
     def identity_key(self):
         if self._identity_key is None:
@@ -646,6 +653,14 @@ def eta(self, value):
         else:
             self._eta = value
 
+    @property
+    def channel(self):
+        return self._channel or self.job_config.channel
+
+    @channel.setter
+    def channel(self, value):
+        self._channel = value
+
     def set_pending(self, result=None, reset_retry=True):
         self.state = PENDING
         self.date_enqueued = None
diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py
index 1da0eaf86d..7fa834640a 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -43,27 +43,22 @@ class QueueJob(models.Model):
         "date_created",
         "model_name",
         "method_name",
+        "func_string",
+        "channel_method_name",
+        "job_function_id",
         "records",
         "args",
         "kwargs",
     )
 
     uuid = fields.Char(string="UUID", readonly=True, index=True, required=True)
-    user_id = fields.Many2one(
-        comodel_name="res.users",
-        string="User ID",
-        compute="_compute_user_id",
-        inverse="_inverse_user_id",
-        store=True,
-    )
+    user_id = fields.Many2one(comodel_name="res.users", string="User ID")
     company_id = fields.Many2one(
         comodel_name="res.company", string="Company", index=True
     )
     name = fields.Char(string="Description", readonly=True)
 
-    model_name = fields.Char(
-        string="Model", compute="_compute_model_name", store=True, readonly=True
-    )
+    model_name = fields.Char(string="Model", readonly=True)
     method_name = fields.Char(readonly=True)
     # record_ids field is only for backward compatibility (e.g. used in related
     # actions), can be removed (replaced by "records") in 14.0
@@ -73,9 +68,7 @@ class QueueJob(models.Model):
     )
     args = JobSerialized(readonly=True, base_type=tuple)
     kwargs = JobSerialized(readonly=True, base_type=dict)
-    func_string = fields.Char(
-        string="Task", compute="_compute_func_string", readonly=True, store=True
-    )
+    func_string = fields.Char(string="Task", readonly=True)
 
     state = fields.Selection(STATES, readonly=True, required=True, index=True)
     priority = fields.Integer()
@@ -95,21 +88,13 @@ class QueueJob(models.Model):
         "max. retries.\n"
         "Retries are infinite when empty.",
     )
-    channel_method_name = fields.Char(
-        readonly=True, compute="_compute_job_function", store=True
-    )
+    # FIXME the name of this field is very confusing
+    channel_method_name = fields.Char(readonly=True)
     job_function_id = fields.Many2one(
-        comodel_name="queue.job.function",
-        compute="_compute_job_function",
-        string="Job Function",
-        readonly=True,
-        store=True,
+        comodel_name="queue.job.function", string="Job Function", readonly=True,
     )
 
-    override_channel = fields.Char()
-    channel = fields.Char(
-        compute="_compute_channel", inverse="_inverse_channel", store=True, index=True
-    )
+    channel = fields.Char(index=True)
 
     identity_key = fields.Char()
     worker_pid = fields.Integer()
@@ -126,65 +111,18 @@ def init(self):
                 "'enqueued') AND identity_key IS NOT NULL;"
             )
 
-    @api.depends("records")
-    def _compute_user_id(self):
-        for record in self:
-            record.user_id = record.records.env.uid
-
-    def _inverse_user_id(self):
-        for record in self.with_context(_job_edit_sentinel=self.EDIT_SENTINEL):
-            record.records = record.records.with_user(record.user_id.id)
-
-    @api.depends("records")
-    def _compute_model_name(self):
-        for record in self:
-            record.model_name = record.records._name
-
     @api.depends("records")
     def _compute_record_ids(self):
         for record in self:
             record.record_ids = record.records.ids
 
-    def _inverse_channel(self):
-        for record in self:
-            record.override_channel = record.channel
-
-    @api.depends("job_function_id.channel_id")
-    def _compute_channel(self):
-        for record in self:
-            channel = (
-                record.override_channel or record.job_function_id.channel or "root"
-            )
-            if record.channel != channel:
-                record.channel = channel
-
-    @api.depends("model_name", "method_name", "job_function_id.channel_id")
-    def _compute_job_function(self):
-        for record in self:
-            func_model = self.env["queue.job.function"]
-            channel_method_name = func_model.job_function_name(
-                record.model_name, record.method_name
-            )
-            function = func_model.search([("name", "=", channel_method_name)], limit=1)
-            record.channel_method_name = channel_method_name
-            record.job_function_id = function
-
-    @api.depends("model_name", "method_name", "records", "args", "kwargs")
-    def _compute_func_string(self):
-        for record in self:
-            model = repr(record.records)
-            args = [repr(arg) for arg in record.args]
-            kwargs = ["{}={!r}".format(key, val) for key, val in record.kwargs.items()]
-            all_args = ", ".join(args + kwargs)
-            record.func_string = "{}.{}({})".format(model, record.method_name, all_args)
-
     @api.model_create_multi
     def create(self, vals_list):
         if self.env.context.get("_job_edit_sentinel") is not self.EDIT_SENTINEL:
             # Prevent to create a queue.job record "raw" from RPC.
             # ``with_delay()`` must be used.
             raise exceptions.AccessError(
-                _("Queue jobs must created by calling 'with_delay()'.")
+                _("Queue jobs must be created by calling 'with_delay()'.")
             )
         return super().create(vals_list)
 
@@ -200,10 +138,25 @@ def write(self, vals):
                     )
                 )
 
+        different_user_jobs = self.browse()
+        if vals.get("user_id"):
+            different_user_jobs = self.filtered(
+                lambda records: records.env.user.id != vals["user_id"]
+            )
+
         if vals.get("state") == "failed":
             self._message_post_on_failure()
 
-        return super().write(vals)
+        result = super().write(vals)
+
+        for record in different_user_jobs:
+            # the user is stored in the env of the record, but we still want to
+            # have a stored user_id field to be able to search/groupby, so
+            # synchronize the env of records with user_id
+            super(QueueJob, record).write(
+                {"records": record.records.with_user(vals["user_id"])}
+            )
+        return result
 
     def open_related_action(self):
         """Open the related action associated to the job"""
@@ -515,7 +468,8 @@ class JobFunction(models.Model):
         "retry_pattern "
         "related_action_enable "
         "related_action_func_name "
-        "related_action_kwargs ",
+        "related_action_kwargs "
+        "job_function_id ",
     )
 
     def _default_channel(self):
@@ -637,6 +591,7 @@ def job_default_config(self):
             related_action_enable=True,
             related_action_func_name=None,
             related_action_kwargs={},
+            job_function_id=None,
         )
 
     def _parse_retry_pattern(self):
@@ -669,6 +624,7 @@ def job_config(self, name):
             related_action_enable=config.related_action.get("enable", True),
             related_action_func_name=config.related_action.get("func_name"),
             related_action_kwargs=config.related_action.get("kwargs"),
+            job_function_id=config.id,
         )
 
     def _retry_pattern_format_error_message(self):
diff --git a/queue_job/tests/test_model_job_function.py b/queue_job/tests/test_model_job_function.py
index c9bdea56e8..e6ddf3fcc3 100644
--- a/queue_job/tests/test_model_job_function.py
+++ b/queue_job/tests/test_model_job_function.py
@@ -31,7 +31,7 @@ def test_function_job_config(self):
         channel = self.env["queue.job.channel"].create(
             {"name": "foo", "parent_id": self.env.ref("queue_job.channel_root").id}
         )
-        self.env["queue.job.function"].create(
+        job_function = self.env["queue.job.function"].create(
             {
                 "model_id": self.env.ref("base.model_res_users").id,
                 "method": "read",
@@ -52,5 +52,6 @@ def test_function_job_config(self):
                 related_action_enable=True,
                 related_action_func_name="related_action_foo",
                 related_action_kwargs={"b": 1},
+                job_function_id=job_function.id,
             ),
         )

From 0be16ae7ce6b59f72d2d55b3e58f694029b829cc Mon Sep 17 00:00:00 2001
From: oca-travis 
Date: Thu, 4 Feb 2021 12:56:43 +0000
Subject: [PATCH 17/45] [UPD] Update queue_job.pot

---
 queue_job/i18n/queue_job.pot | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/queue_job/i18n/queue_job.pot b/queue_job/i18n/queue_job.pot
index 96bac153cd..36f7348b8e 100644
--- a/queue_job/i18n/queue_job.pot
+++ b/queue_job/i18n/queue_job.pot
@@ -492,11 +492,6 @@ msgstr ""
 msgid "Number of unread messages"
 msgstr ""
 
-#. module: queue_job
-#: model:ir.model.fields,field_description:queue_job.field_queue_job__override_channel
-msgid "Override Channel"
-msgstr ""
-
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__parent_id
 msgid "Parent Channel"
@@ -540,7 +535,7 @@ msgstr ""
 #. module: queue_job
 #: code:addons/queue_job/models/queue_job.py:0
 #, python-format
-msgid "Queue jobs must created by calling 'with_delay()'."
+msgid "Queue jobs must be created by calling 'with_delay()'."
 msgstr ""
 
 #. module: queue_job

From 15dc56ffdecf1b47262250e9e806079b9449150b Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Thu, 4 Feb 2021 13:43:08 +0000
Subject: [PATCH 18/45] queue_job 13.0.3.4.0

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index d05b46387e..f75289efba 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.3.1",
+    "version": "13.0.3.4.0",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From dd941cfc39f618fdecbb0d29415c346184c8932e Mon Sep 17 00:00:00 2001
From: OCA Transbot 
Date: Thu, 4 Feb 2021 13:43:19 +0000
Subject: [PATCH 19/45] Update translation files

Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: queue-13.0/queue-13.0-queue_job
Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/
---
 queue_job/i18n/de.po    | 10 ++++------
 queue_job/i18n/zh_CN.po | 10 ++++------
 2 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po
index 7e051fd879..ad28299577 100644
--- a/queue_job/i18n/de.po
+++ b/queue_job/i18n/de.po
@@ -501,11 +501,6 @@ msgstr "Das ist die Anzahl von Nachrichten mit Übermittlungsfehler"
 msgid "Number of unread messages"
 msgstr "Das ist die Anzahl von ungelesenen Nachrichten"
 
-#. module: queue_job
-#: model:ir.model.fields,field_description:queue_job.field_queue_job__override_channel
-msgid "Override Channel"
-msgstr "Kanal überschreiben"
-
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__parent_id
 msgid "Parent Channel"
@@ -550,7 +545,7 @@ msgstr "Job einreihen"
 #. module: queue_job
 #: code:addons/queue_job/models/queue_job.py:0
 #, python-format
-msgid "Queue jobs must created by calling 'with_delay()'."
+msgid "Queue jobs must be created by calling 'with_delay()'."
 msgstr ""
 
 #. module: queue_job
@@ -803,6 +798,9 @@ msgstr "Assistent zur erneuten Einreihung einer Job-Auswahl"
 msgid "Worker Pid"
 msgstr ""
 
+#~ msgid "Override Channel"
+#~ msgstr "Kanal überschreiben"
+
 #~ msgid "If checked new messages require your attention."
 #~ msgstr ""
 #~ "Wenn es gesetzt ist, erfordern neue Nachrichten Ihre Aufmerksamkeit."
diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po
index c4cfcc49c8..591a448d58 100644
--- a/queue_job/i18n/zh_CN.po
+++ b/queue_job/i18n/zh_CN.po
@@ -497,11 +497,6 @@ msgstr "递送错误消息数量"
 msgid "Number of unread messages"
 msgstr "未读消息数量"
 
-#. module: queue_job
-#: model:ir.model.fields,field_description:queue_job.field_queue_job__override_channel
-msgid "Override Channel"
-msgstr "覆盖频道"
-
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__parent_id
 msgid "Parent Channel"
@@ -546,7 +541,7 @@ msgstr "队列作业"
 #. module: queue_job
 #: code:addons/queue_job/models/queue_job.py:0
 #, python-format
-msgid "Queue jobs must created by calling 'with_delay()'."
+msgid "Queue jobs must be created by calling 'with_delay()'."
 msgstr ""
 
 #. module: queue_job
@@ -796,6 +791,9 @@ msgstr "重新排队向导所选的作业"
 msgid "Worker Pid"
 msgstr ""
 
+#~ msgid "Override Channel"
+#~ msgstr "覆盖频道"
+
 #~ msgid "If checked new messages require your attention."
 #~ msgstr "查看是否有需要留意的新消息。"
 

From 5e523a11b373d2de5c14361490928b7992b1e2ad Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Thu, 4 Feb 2021 15:02:37 +0000
Subject: [PATCH 20/45] test_queue_job 13.0.2.3.0

---
 test_queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test_queue_job/__manifest__.py b/test_queue_job/__manifest__.py
index 5c1302efaa..bb16bf4097 100644
--- a/test_queue_job/__manifest__.py
+++ b/test_queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Queue Job Tests",
-    "version": "13.0.2.2.0",
+    "version": "13.0.2.3.0",
     "author": "Camptocamp,Odoo Community Association (OCA)",
     "license": "LGPL-3",
     "category": "Generic Modules",

From 563db616ff33496b75e82b98f9734c8c66637ce4 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Thu, 4 Feb 2021 15:02:39 +0000
Subject: [PATCH 21/45] queue_job 13.0.3.5.0

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index f75289efba..f494da3a07 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.4.0",
+    "version": "13.0.3.5.0",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From cb18946367c480fc193886386206a109c0f58aa0 Mon Sep 17 00:00:00 2001
From: Guewen Baconnier 
Date: Wed, 10 Feb 2021 09:42:06 +0100
Subject: [PATCH 22/45] Add model in search view / group by

---
 queue_job/views/queue_job_views.xml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml
index 1197495ead..2ef6eab120 100644
--- a/queue_job/views/queue_job_views.xml
+++ b/queue_job/views/queue_job_views.xml
@@ -123,6 +123,7 @@
                 
                 
                 
+                
                 
+                    
                 
             
         

From c78904c98d0816cda82d8cdefc499fd1b40d90b7 Mon Sep 17 00:00:00 2001
From: Wolfgang Pichler 
Date: Thu, 19 Nov 2020 08:16:47 +0100
Subject: [PATCH 23/45] Fix missing rollback on retried jobs

When RetryableJobError was raised, any change done by the job was not
rollbacked.

Using `raise` would throw the exception up to the core and rollback, but
we would have a stack trace in the logs for each try. Calling rollback
manually (rollback also clears the env) hide the tracebacks, however,
when the last try fails, the full traceback is still shown in the logs.

Fixes #261
---
 queue_job/controllers/main.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py
index eaa78e9a3b..dda0e8d6c4 100644
--- a/queue_job/controllers/main.py
+++ b/queue_job/controllers/main.py
@@ -77,10 +77,10 @@ def retry_postpone(job, message, seconds=None):
                 if err.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
                     raise
 
-                retry_postpone(
-                    job, tools.ustr(err.pgerror, errors="replace"), seconds=PG_RETRY
-                )
                 _logger.debug("%s OperationalError, postponed", job)
+                raise RetryableJobError(
+                    tools.ustr(err.pgerror, errors="replace"), seconds=PG_RETRY
+                )
 
         except NothingToDoJob as err:
             if str(err):
@@ -95,6 +95,10 @@ def retry_postpone(job, message, seconds=None):
             # delay the job later, requeue
             retry_postpone(job, str(err), seconds=err.seconds)
             _logger.debug("%s postponed", job)
+            # Do not trigger the error up because we don't want an exception
+            # traceback in the logs we should have the traceback when all
+            # retries are exhausted
+            env.cr.rollback()
 
         except (FailedJobError, Exception):
             buff = StringIO()

From b42945fb7df5330b73455be31d7c78b7ef4ecee3 Mon Sep 17 00:00:00 2001
From: Guewen Baconnier 
Date: Wed, 10 Feb 2021 12:01:51 +0100
Subject: [PATCH 24/45] Fix date_done set when state changes back to pending

When Job.date_done has been set, for instance because the job has been
performed, if the job is set back to pending (e.g. a RetryableJobError
is raised), the date_done is kept.
---
 queue_job/job.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/queue_job/job.py b/queue_job/job.py
index 45dca7cbb6..dbb74cbf92 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -665,6 +665,7 @@ def set_pending(self, result=None, reset_retry=True):
         self.state = PENDING
         self.date_enqueued = None
         self.date_started = None
+        self.date_done = None
         self.worker_pid = None
         if reset_retry:
             self.retry = 0

From d345e47716bc768bbf03de7596e8db432e8a5cac Mon Sep 17 00:00:00 2001
From: oca-travis 
Date: Thu, 11 Feb 2021 09:19:16 +0000
Subject: [PATCH 25/45] [UPD] Update queue_job.pot

---
 queue_job/i18n/queue_job.pot | 1 +
 1 file changed, 1 insertion(+)

diff --git a/queue_job/i18n/queue_job.pot b/queue_job/i18n/queue_job.pot
index 36f7348b8e..7ddea70a12 100644
--- a/queue_job/i18n/queue_job.pot
+++ b/queue_job/i18n/queue_job.pot
@@ -425,6 +425,7 @@ msgstr ""
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__model_name
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__model_id
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
 msgid "Model"
 msgstr ""
 

From 6efb1dcd29163f76ddb7aaa6ba1e2e66f917d70a Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Thu, 11 Feb 2021 10:05:10 +0000
Subject: [PATCH 26/45] queue_job 13.0.3.6.0

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index f494da3a07..5ef7214eab 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.5.0",
+    "version": "13.0.3.6.0",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From 263fbaac33a5b559c77a1bb08b0616bb42c78d2f Mon Sep 17 00:00:00 2001
From: OCA Transbot 
Date: Thu, 11 Feb 2021 10:05:21 +0000
Subject: [PATCH 27/45] Update translation files

Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: queue-13.0/queue-13.0-queue_job
Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/
---
 queue_job/i18n/de.po    | 1 +
 queue_job/i18n/zh_CN.po | 1 +
 2 files changed, 2 insertions(+)

diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po
index ad28299577..fd162f99ee 100644
--- a/queue_job/i18n/de.po
+++ b/queue_job/i18n/de.po
@@ -434,6 +434,7 @@ msgstr "Methodenname"
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__model_name
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__model_id
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
 msgid "Model"
 msgstr "Modell"
 
diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po
index 591a448d58..edda26afb5 100644
--- a/queue_job/i18n/zh_CN.po
+++ b/queue_job/i18n/zh_CN.po
@@ -430,6 +430,7 @@ msgstr "方法名称"
 #. module: queue_job
 #: model:ir.model.fields,field_description:queue_job.field_queue_job__model_name
 #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__model_id
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
 msgid "Model"
 msgstr "模型"
 

From 5a955530204f487f567d69e598b75084bda83da0 Mon Sep 17 00:00:00 2001
From: Simone Orsi 
Date: Mon, 8 Feb 2021 16:43:43 +0100
Subject: [PATCH 28/45] queue_job: add exec time to view some stats

---
 queue_job/__manifest__.py                     |  2 +-
 queue_job/job.py                              |  9 +++++
 .../migrations/13.0.3.7.0/pre-migration.py    | 35 +++++++++++++++++++
 queue_job/models/queue_job.py                 |  5 +++
 queue_job/views/queue_job_views.xml           | 26 +++++++++++++-
 test_queue_job/tests/test_job.py              |  3 ++
 6 files changed, 78 insertions(+), 2 deletions(-)
 create mode 100644 queue_job/migrations/13.0.3.7.0/pre-migration.py

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 5ef7214eab..1b873fc086 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.6.0",
+    "version": "13.0.3.7.0",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",
diff --git a/queue_job/job.py b/queue_job/job.py
index 45dca7cbb6..7edcb5c1ef 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -529,6 +529,7 @@ def store(self):
             "date_enqueued": False,
             "date_started": False,
             "date_done": False,
+            "exec_time": False,
             "eta": False,
             "identity_key": False,
             "worker_pid": self.worker_pid,
@@ -540,6 +541,8 @@ def store(self):
             vals["date_started"] = self.date_started
         if self.date_done:
             vals["date_done"] = self.date_done
+        if self.exec_time:
+            vals["exec_time"] = self.exec_time
         if self.eta:
             vals["eta"] = self.eta
         if self.identity_key:
@@ -661,6 +664,12 @@ def channel(self):
     def channel(self, value):
         self._channel = value
 
+    @property
+    def exec_time(self):
+        if self.date_done and self.date_started:
+            return (self.date_done - self.date_started).total_seconds()
+        return None
+
     def set_pending(self, result=None, reset_retry=True):
         self.state = PENDING
         self.date_enqueued = None
diff --git a/queue_job/migrations/13.0.3.7.0/pre-migration.py b/queue_job/migrations/13.0.3.7.0/pre-migration.py
new file mode 100644
index 0000000000..c14d6800ad
--- /dev/null
+++ b/queue_job/migrations/13.0.3.7.0/pre-migration.py
@@ -0,0 +1,35 @@
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
+
+import logging
+
+from odoo.tools.sql import column_exists
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+    if not column_exists(cr, "queue_job", "exec_time"):
+        # Disable trigger otherwise the update takes ages.
+        cr.execute(
+            """
+            ALTER TABLE queue_job DISABLE TRIGGER queue_job_notify;
+        """
+        )
+        cr.execute(
+            """
+            ALTER TABLE queue_job ADD COLUMN exec_time double precision DEFAULT 0;
+        """
+        )
+        cr.execute(
+            """
+            UPDATE
+                queue_job
+            SET
+                exec_time = EXTRACT(EPOCH FROM (date_done - date_started));
+        """
+        )
+        cr.execute(
+            """
+            ALTER TABLE queue_job ENABLE TRIGGER queue_job_notify;
+        """
+        )
diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py
index 1395499c0b..2b2b838961 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -79,6 +79,11 @@ class QueueJob(models.Model):
     date_started = fields.Datetime(string="Start Date", readonly=True)
     date_enqueued = fields.Datetime(string="Enqueue Time", readonly=True)
     date_done = fields.Datetime(readonly=True)
+    exec_time = fields.Float(
+        string="Execution Time (avg)",
+        group_operator="avg",
+        help="Time required to execute this job in seconds. Average when grouped.",
+    )
 
     eta = fields.Datetime(string="Execute only after")
     retry = fields.Integer(string="Current try")
diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml
index 2ef6eab120..03db708400 100644
--- a/queue_job/views/queue_job_views.xml
+++ b/queue_job/views/queue_job_views.xml
@@ -56,6 +56,8 @@
                             
                             
                             
+                            
+                            
                         
                     
                     
@@ -107,12 +109,34 @@
                 
                 
                 
+                
                 
                 
                 
             
         
     
+    
+        queue.job.pivot
+        queue.job
+        
+            
+                
+                
+                
+            
+        
+    
+    
+        queue.job.graph
+        queue.job
+        
+            
+                
+                
+            
+        
+    
     
         queue.job.search
         queue.job
@@ -178,7 +202,7 @@
     
         Jobs
         queue.job
-        tree,form
+        tree,form,pivot,graph
         {'search_default_pending': 1,
                                'search_default_enqueued': 1,
                                'search_default_started': 1,
diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py
index 0b8245618d..308b52501e 100644
--- a/test_queue_job/tests/test_job.py
+++ b/test_queue_job/tests/test_job.py
@@ -149,6 +149,7 @@ def test_worker_pid(self):
 
     def test_set_done(self):
         job_a = Job(self.method)
+        job_a.date_started = datetime(2015, 3, 15, 16, 40, 0)
         datetime_path = "odoo.addons.queue_job.job.datetime"
         with mock.patch(datetime_path, autospec=True) as mock_datetime:
             mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
@@ -157,6 +158,7 @@ def test_set_done(self):
         self.assertEquals(job_a.state, DONE)
         self.assertEquals(job_a.result, "test")
         self.assertEquals(job_a.date_done, datetime(2015, 3, 15, 16, 41, 0))
+        self.assertEquals(job_a.exec_time, 60.0)
         self.assertFalse(job_a.exc_info)
 
     def test_set_failed(self):
@@ -233,6 +235,7 @@ def test_read(self):
         self.assertAlmostEqual(job_read.date_started, test_date, delta=delta)
         self.assertAlmostEqual(job_read.date_enqueued, test_date, delta=delta)
         self.assertAlmostEqual(job_read.date_done, test_date, delta=delta)
+        self.assertAlmostEqual(job_read.exec_time, 0.0)
 
     def test_job_unlinked(self):
         test_job = Job(self.method, args=("o", "k"), kwargs={"c": "!"})

From 9bf3f24130886ba3a0509234404ee406a1f54073 Mon Sep 17 00:00:00 2001
From: oca-travis 
Date: Thu, 4 Mar 2021 11:37:57 +0000
Subject: [PATCH 29/45] [UPD] Update queue_job.pot

---
 queue_job/i18n/queue_job.pot | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/queue_job/i18n/queue_job.pot b/queue_job/i18n/queue_job.pot
index 7ddea70a12..03bb9e37ea 100644
--- a/queue_job/i18n/queue_job.pot
+++ b/queue_job/i18n/queue_job.pot
@@ -203,6 +203,11 @@ msgstr ""
 msgid "Execute only after"
 msgstr ""
 
+#. module: queue_job
+#: model:ir.model.fields,field_description:queue_job.field_queue_job__exec_time
+msgid "Execution Time (avg)"
+msgstr ""
+
 #. module: queue_job
 #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__failed
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
@@ -347,6 +352,8 @@ msgstr ""
 #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__job_ids
 #: model:ir.ui.menu,name:queue_job.menu_queue_job
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_graph
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_pivot
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
 msgid "Jobs"
 msgstr ""
@@ -711,6 +718,16 @@ msgstr ""
 msgid "The selected jobs will be set to done."
 msgstr ""
 
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
+msgid "Time (s)"
+msgstr ""
+
+#. module: queue_job
+#: model:ir.model.fields,help:queue_job.field_queue_job__exec_time
+msgid "Time required to execute this job in seconds. Average when grouped."
+msgstr ""
+
 #. module: queue_job
 #: model:ir.model.fields,help:queue_job.field_queue_job__activity_exception_decoration
 msgid "Type of the exception activity on record."

From 764f7320eb137091c4caa1e6d498cbeb2ca5aaab Mon Sep 17 00:00:00 2001
From: OCA Transbot 
Date: Thu, 4 Mar 2021 11:58:47 +0000
Subject: [PATCH 30/45] Update translation files

Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: queue-13.0/queue-13.0-queue_job
Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/
---
 queue_job/i18n/de.po    | 17 +++++++++++++++++
 queue_job/i18n/zh_CN.po | 17 +++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po
index fd162f99ee..c0c5260445 100644
--- a/queue_job/i18n/de.po
+++ b/queue_job/i18n/de.po
@@ -209,6 +209,11 @@ msgstr "Exception-Information"
 msgid "Execute only after"
 msgstr "Erst ausführen nach"
 
+#. module: queue_job
+#: model:ir.model.fields,field_description:queue_job.field_queue_job__exec_time
+msgid "Execution Time (avg)"
+msgstr ""
+
 #. module: queue_job
 #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__failed
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
@@ -355,6 +360,8 @@ msgstr "Job unterbrochen und als Erledigt markiert: Es ist nicht zu tun."
 #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__job_ids
 #: model:ir.ui.menu,name:queue_job.menu_queue_job
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_graph
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_pivot
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
 msgid "Jobs"
 msgstr "Jobs"
@@ -735,6 +742,16 @@ msgstr "Die ausgewählten Jobs werden erneut eingereiht."
 msgid "The selected jobs will be set to done."
 msgstr "Die ausgewählten Jobs werden als Erledigt markiert."
 
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
+msgid "Time (s)"
+msgstr ""
+
+#. module: queue_job
+#: model:ir.model.fields,help:queue_job.field_queue_job__exec_time
+msgid "Time required to execute this job in seconds. Average when grouped."
+msgstr ""
+
 #. module: queue_job
 #: model:ir.model.fields,help:queue_job.field_queue_job__activity_exception_decoration
 msgid "Type of the exception activity on record."
diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po
index edda26afb5..1c66c1b2cf 100644
--- a/queue_job/i18n/zh_CN.po
+++ b/queue_job/i18n/zh_CN.po
@@ -208,6 +208,11 @@ msgstr "异常信息"
 msgid "Execute only after"
 msgstr "仅在此之后执行"
 
+#. module: queue_job
+#: model:ir.model.fields,field_description:queue_job.field_queue_job__exec_time
+msgid "Execution Time (avg)"
+msgstr ""
+
 #. module: queue_job
 #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__failed
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
@@ -352,6 +357,8 @@ msgstr "作业中断并设置为已完成:无需执行任何操作。"
 #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__job_ids
 #: model:ir.ui.menu,name:queue_job.menu_queue_job
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_graph
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_pivot
 #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
 msgid "Jobs"
 msgstr "作业"
@@ -728,6 +735,16 @@ msgstr "所选作业将重新排队。"
 msgid "The selected jobs will be set to done."
 msgstr "所选作业将设置为完成。"
 
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form
+msgid "Time (s)"
+msgstr ""
+
+#. module: queue_job
+#: model:ir.model.fields,help:queue_job.field_queue_job__exec_time
+msgid "Time required to execute this job in seconds. Average when grouped."
+msgstr ""
+
 #. module: queue_job
 #: model:ir.model.fields,help:queue_job.field_queue_job__activity_exception_decoration
 msgid "Type of the exception activity on record."

From f70788913aeb7aebfc40197fa93b882805ef9323 Mon Sep 17 00:00:00 2001
From: OCA-git-bot 
Date: Mon, 15 Mar 2021 14:13:48 +0000
Subject: [PATCH 31/45] queue_job 13.0.3.7.1

---
 queue_job/__manifest__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 1b873fc086..3d7192525c 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Job Queue",
-    "version": "13.0.3.7.0",
+    "version": "13.0.3.7.1",
     "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/queue/queue_job",
     "license": "LGPL-3",

From c7efaca42105bbe560489ef5e63a5e27ef83cf39 Mon Sep 17 00:00:00 2001
From: Simone Orsi 
Date: Mon, 29 Mar 2021 08:39:28 +0200
Subject: [PATCH 32/45] queue_job: close buffer when done

---
 queue_job/controllers/main.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py
index dda0e8d6c4..e9007da65e 100644
--- a/queue_job/controllers/main.py
+++ b/queue_job/controllers/main.py
@@ -111,6 +111,7 @@ def retry_postpone(job, message, seconds=None):
                     job.set_failed(exc_info=buff.getvalue())
                     job.store()
                     new_cr.commit()
+                    buff.close()
             raise
 
         return ""

From 7c612ac64cfbef2bb0cdd8bb1dee4cb205fb7c9c Mon Sep 17 00:00:00 2001
From: Simone Orsi 
Date: Mon, 29 Mar 2021 09:12:07 +0200
Subject: [PATCH 33/45] queue_job: store exception name and message

---
 queue_job/controllers/main.py       | 20 +++++++++++++++++---
 queue_job/job.py                    | 21 ++++++++++++++++++---
 queue_job/models/queue_job.py       |  2 ++
 queue_job/views/queue_job_views.xml | 22 ++++++++++++++--------
 test_queue_job/tests/test_job.py    |  8 +++++++-
 5 files changed, 58 insertions(+), 15 deletions(-)

diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py
index e9007da65e..936b7b1592 100644
--- a/queue_job/controllers/main.py
+++ b/queue_job/controllers/main.py
@@ -100,15 +100,17 @@ def retry_postpone(job, message, seconds=None):
             # retries are exhausted
             env.cr.rollback()
 
-        except (FailedJobError, Exception):
+        except (FailedJobError, Exception) as orig_exception:
             buff = StringIO()
             traceback.print_exc(file=buff)
-            _logger.error(buff.getvalue())
+            traceback_txt = buff.getvalue()
+            _logger.error(traceback_txt)
             job.env.clear()
             with odoo.api.Environment.manage():
                 with odoo.registry(job.env.cr.dbname).cursor() as new_cr:
                     job.env = job.env(cr=new_cr)
-                    job.set_failed(exc_info=buff.getvalue())
+                    vals = self._get_failure_values(job, traceback_txt, orig_exception)
+                    job.set_failed(**vals)
                     job.store()
                     new_cr.commit()
                     buff.close()
@@ -116,6 +118,18 @@ def retry_postpone(job, message, seconds=None):
 
         return ""
 
+    def _get_failure_values(self, job, traceback_txt, orig_exception):
+        """Collect relevant data from exception."""
+        exception_name = orig_exception.__class__.__name__
+        if hasattr(orig_exception, "__module__"):
+            exception_name = orig_exception.__module__ + "." + exception_name
+        exc_message = getattr(orig_exception, "name", str(orig_exception))
+        return {
+            "exc_info": traceback_txt,
+            "exc_name": exception_name,
+            "exc_message": exc_message,
+        }
+
     @http.route("/queue_job/create_test_job", type="http", auth="user")
     def create_test_job(
         self, priority=None, max_retries=None, channel=None, description="Test job"
diff --git a/queue_job/job.py b/queue_job/job.py
index 1329196166..9cfdf2e79b 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -214,6 +214,14 @@ class Job(object):
 
         A description of the result (for humans).
 
+    .. attribute:: exc_name
+
+        Exception error name when the job failed.
+
+    .. attribute:: exc_message
+
+        Exception error message when the job failed.
+
     .. attribute:: exc_info
 
         Exception information (traceback) when the job failed.
@@ -478,6 +486,8 @@ def __init__(
         self.date_done = None
 
         self.result = None
+        self.exc_name = None
+        self.exc_message = None
         self.exc_info = None
 
         if "company_id" in env.context:
@@ -523,6 +533,8 @@ def store(self):
             "priority": self.priority,
             "retry": self.retry,
             "max_retries": self.max_retries,
+            "exc_name": self.exc_name,
+            "exc_message": self.exc_message,
             "exc_info": self.exc_info,
             "company_id": self.company_id,
             "result": str(self.result) if self.result else False,
@@ -694,15 +706,17 @@ def set_started(self):
 
     def set_done(self, result=None):
         self.state = DONE
+        self.exc_name = None
         self.exc_info = None
         self.date_done = datetime.now()
         if result is not None:
             self.result = result
 
-    def set_failed(self, exc_info=None):
+    def set_failed(self, **kw):
         self.state = FAILED
-        if exc_info is not None:
-            self.exc_info = exc_info
+        for k, v in kw.items():
+            if v is not None:
+                setattr(self, k, v)
 
     def __repr__(self):
         return "" % (self.uuid, self.priority)
@@ -734,6 +748,7 @@ def postpone(self, result=None, seconds=None):
         """
         eta_seconds = self._get_retry_seconds(seconds)
         self.eta = timedelta(seconds=eta_seconds)
+        self.exc_name = None
         self.exc_info = None
         if result is not None:
             self.result = result
diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py
index 2b2b838961..fd848861df 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -72,6 +72,8 @@ class QueueJob(models.Model):
 
     state = fields.Selection(STATES, readonly=True, required=True, index=True)
     priority = fields.Integer()
+    exc_name = fields.Char(string="Exception", readonly=True)
+    exc_message = fields.Char(string="Exception Message", readonly=True)
     exc_info = fields.Text(string="Exception Info", readonly=True)
     result = fields.Text(readonly=True)
 
diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml
index 03db708400..f2f0384e51 100644
--- a/queue_job/views/queue_job_views.xml
+++ b/queue_job/views/queue_job_views.xml
@@ -70,6 +70,18 @@
                             > If the max. retries is 0, the number of retries is infinite.
                         
                     
+                    
+                        
+
+ +
- - -
@@ -106,10 +111,11 @@ - + + diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 308b52501e..c90a0c1fba 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -163,9 +163,15 @@ def test_set_done(self): def test_set_failed(self): job_a = Job(self.method) - job_a.set_failed(exc_info="failed test") + job_a.set_failed( + exc_info="failed test", + exc_name="FailedTest", + exc_message="Sadly this job failed", + ) self.assertEquals(job_a.state, FAILED) self.assertEquals(job_a.exc_info, "failed test") + self.assertEquals(job_a.exc_name, "FailedTest") + self.assertEquals(job_a.exc_message, "Sadly this job failed") def test_postpone(self): job_a = Job(self.method) From bbf9a17d6fc551cef0d509b695a6d162e0ea7b67 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 09:15:36 +0200 Subject: [PATCH 34/45] queue_job: improve filtering and grouping --- queue_job/views/queue_job_views.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index f2f0384e51..0df8fafa8c 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -116,6 +116,7 @@ + @@ -154,6 +155,10 @@ + + + + + + From 58e1e2b36bed42a2c72310e15424df7ccd430ddb Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 10:06:32 +0200 Subject: [PATCH 35/45] queue_job: add hook to customize stored values --- queue_job/job.py | 45 +++++++++++++++++++++------- queue_job/models/base.py | 18 ++++++++++- test_queue_job/models/test_models.py | 8 +++++ test_queue_job/tests/test_job.py | 10 +++++++ 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/queue_job/job.py b/queue_job/job.py index 9cfdf2e79b..349a73c8ce 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -528,6 +528,22 @@ def perform(self): def store(self): """Store the Job""" + job_model = self.env["queue.job"] + # The sentinel is used to prevent edition sensitive fields (such as + # method_name) from RPC methods. + edit_sentinel = job_model.EDIT_SENTINEL + + db_record = self.db_record() + if db_record: + db_record.with_context(_job_edit_sentinel=edit_sentinel).write( + self._store_values() + ) + else: + job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create( + self._store_values(create=True) + ) + + def _store_values(self, create=False): vals = { "state": self.state, "priority": self.priority, @@ -560,15 +576,7 @@ def store(self): if self.identity_key: vals["identity_key"] = self.identity_key - job_model = self.env["queue.job"] - # The sentinel is used to prevent edition sensitive fields (such as - # method_name) from RPC methods. - edit_sentinel = job_model.EDIT_SENTINEL - - db_record = self.db_record() - if db_record: - db_record.with_context(_job_edit_sentinel=edit_sentinel).write(vals) - else: + if create: vals.update( { "user_id": self.env.uid, @@ -588,7 +596,24 @@ def store(self): "kwargs": self.kwargs, } ) - job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create(vals) + + vals_from_model = self._store_values_from_model() + # Sanitize values: make sure you cannot screw core values + vals_from_model = {k: v for k, v in vals_from_model.items() if k not in vals} + vals.update(vals_from_model) + return vals + + def _store_values_from_model(self): + vals = {} + value_handlers_candidates = ( + "_job_store_values_for_" + self.method_name, + "_job_store_values", + ) + for candidate in value_handlers_candidates: + handler = getattr(self.recordset, candidate, None) + if handler is not None: + vals = handler(self) + return vals @property def func_string(self): diff --git a/queue_job/models/base.py b/queue_job/models/base.py index 116eb495f9..a83f457900 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -6,7 +6,7 @@ import logging import os -from odoo import models +from odoo import api, models from ..job import DelayableRecordset @@ -200,3 +200,19 @@ def auto_delay_wrapper(self, *args, **kwargs): origin = getattr(self, method_name) return functools.update_wrapper(auto_delay_wrapper, origin) + + @api.model + def _job_store_values(self, job): + """Hook for manipulating job stored values. + + You can define a more specific hook for a job function + by defining a method name with this pattern: + + `_queue_job_store_values_${func_name}` + + NOTE: values will be stored only if they match stored fields on `queue.job`. + + :param job: current queue_job.job.Job instance. + :return: dictionary for setting job values. + """ + return {} diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index 9bd5b2c9cc..2812855a20 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -10,6 +10,8 @@ class QueueJob(models.Model): _inherit = "queue.job" + additional_info = fields.Char() + def testing_related_method(self, **kwargs): return self, kwargs @@ -88,6 +90,12 @@ def _register_hook(self): ) return super()._register_hook() + def _job_store_values(self, job): + value = "JUST_TESTING" + if job.state == "failed": + value += "_BUT_FAILED" + return {"additional_info": value} + class TestQueueChannel(models.Model): diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index c90a0c1fba..e0224ebf3d 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -190,6 +190,16 @@ def test_store(self): stored = self.queue_job.search([("uuid", "=", test_job.uuid)]) self.assertEqual(len(stored), 1) + def test_store_extra_data(self): + test_job = Job(self.method) + test_job.store() + stored = self.queue_job.search([("uuid", "=", test_job.uuid)]) + self.assertEqual(stored.additional_info, "JUST_TESTING") + test_job.set_failed(exc_info="failed test", exc_name="FailedTest") + test_job.store() + stored.invalidate_cache() + self.assertEqual(stored.additional_info, "JUST_TESTING_BUT_FAILED") + def test_read(self): eta = datetime.now() + timedelta(hours=5) test_job = Job( From 72325ae56b6506c4429d1567597f184e69db09b7 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 11:34:30 +0200 Subject: [PATCH 36/45] queue_job: migration step to store exception data --- queue_job/__manifest__.py | 2 +- .../migrations/13.0.3.8.0/post-migration.py | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 queue_job/migrations/13.0.3.8.0/post-migration.py diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 3d7192525c..97c6e615c4 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Job Queue", - "version": "13.0.3.7.1", + "version": "13.0.3.8.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue/queue_job", "license": "LGPL-3", diff --git a/queue_job/migrations/13.0.3.8.0/post-migration.py b/queue_job/migrations/13.0.3.8.0/post-migration.py new file mode 100644 index 0000000000..f6eff72707 --- /dev/null +++ b/queue_job/migrations/13.0.3.8.0/post-migration.py @@ -0,0 +1,47 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) + +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + _logger.info("Computing exception name for failed jobs") + _compute_jobs_new_values(env) + + +def _compute_jobs_new_values(env): + for job in env["queue.job"].search( + [("state", "=", "failed"), ("exc_info", "!=", False)] + ): + exception_details = _get_exception_details(job) + if exception_details: + job.update(exception_details) + + +def _get_exception_details(job): + for line in reversed(job.exc_info.splitlines()): + if _find_exception(line): + name, msg = line.split(":", 1) + return { + "exc_name": name.strip(), + "exc_message": msg.strip("()', \""), + } + + +def _find_exception(line): + # Just a list of common errors. + # If you want to target others, add your own migration step for your db. + exceptions = ( + "Error:", # catch all well named exceptions + # other live instance errors found + "requests.exceptions.MissingSchema", + "botocore.errorfactory.NoSuchKey", + ) + for exc in exceptions: + if exc in line: + return exc From 59969ecdb2efb14ad97ec8c76d01003a0314a8e4 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Mon, 19 Apr 2021 07:00:11 +0000 Subject: [PATCH 37/45] [UPD] Update queue_job.pot --- queue_job/i18n/queue_job.pot | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/queue_job/i18n/queue_job.pot b/queue_job/i18n/queue_job.pot index 03bb9e37ea..62025f0d1a 100644 --- a/queue_job/i18n/queue_job.pot +++ b/queue_job/i18n/queue_job.pot @@ -188,6 +188,12 @@ msgstr "" msgid "Enqueued" msgstr "" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_name +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_info msgid "Exception Info" @@ -198,6 +204,21 @@ msgstr "" msgid "Exception Information" msgstr "" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_message +msgid "Exception Message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Exception:" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__eta msgid "Execute only after" From f43c58319e5f23500e34bc8bcd719c63b67a45ae Mon Sep 17 00:00:00 2001 From: oca-travis Date: Mon, 19 Apr 2021 07:00:12 +0000 Subject: [PATCH 38/45] [UPD] Update test_queue_job.pot --- test_queue_job/i18n/test_queue_job.pot | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test_queue_job/i18n/test_queue_job.pot b/test_queue_job/i18n/test_queue_job.pot index ba5e3fc45f..1b9d45a9a2 100644 --- a/test_queue_job/i18n/test_queue_job.pot +++ b/test_queue_job/i18n/test_queue_job.pot @@ -13,6 +13,11 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: test_queue_job +#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info +msgid "Additional Info" +msgstr "" + #. module: test_queue_job #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid From f63b9b5d96584896a69a4caa509da6156971284c Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 19 Apr 2021 07:14:18 +0000 Subject: [PATCH 39/45] test_queue_job 13.0.2.4.0 --- test_queue_job/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_queue_job/__manifest__.py b/test_queue_job/__manifest__.py index bb16bf4097..56661d6659 100644 --- a/test_queue_job/__manifest__.py +++ b/test_queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Queue Job Tests", - "version": "13.0.2.3.0", + "version": "13.0.2.4.0", "author": "Camptocamp,Odoo Community Association (OCA)", "license": "LGPL-3", "category": "Generic Modules", From ae4c01d64c8dcc2145fec695d417382e49f6234e Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 19 Apr 2021 07:14:20 +0000 Subject: [PATCH 40/45] queue_job 13.0.3.9.0 --- queue_job/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 97c6e615c4..bab2167036 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Job Queue", - "version": "13.0.3.8.0", + "version": "13.0.3.9.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue/queue_job", "license": "LGPL-3", From d23b00f2a6c7167394eeafb41a3f0d32bb8c85bd Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Mon, 19 Apr 2021 07:14:29 +0000 Subject: [PATCH 41/45] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: queue-13.0/queue-13.0-queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-queue_job/ --- queue_job/i18n/de.po | 21 +++++++++++++++++++++ queue_job/i18n/zh_CN.po | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index c0c5260445..f5dd5f2459 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -194,6 +194,12 @@ msgstr "Zeit der Einreihung" msgid "Enqueued" msgstr "Eingereiht" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_name +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_info msgid "Exception Info" @@ -204,6 +210,21 @@ msgstr "Exception-Info" msgid "Exception Information" msgstr "Exception-Information" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_message +msgid "Exception Message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Exception:" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__eta msgid "Execute only after" diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po index 1c66c1b2cf..241e5a166c 100644 --- a/queue_job/i18n/zh_CN.po +++ b/queue_job/i18n/zh_CN.po @@ -193,6 +193,12 @@ msgstr "排队时间" msgid "Enqueued" msgstr "排队" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_name +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_info msgid "Exception Info" @@ -203,6 +209,21 @@ msgstr "异常信息" msgid "Exception Information" msgstr "异常信息" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_message +msgid "Exception Message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Exception:" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__eta msgid "Execute only after" From 87346515fd0cd2080f4793d19c85be05e25affe0 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Mon, 19 Apr 2021 07:14:29 +0000 Subject: [PATCH 42/45] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: queue-13.0/queue-13.0-test_queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-13-0/queue-13-0-test_queue_job/ --- test_queue_job/i18n/zh_CN.po | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test_queue_job/i18n/zh_CN.po b/test_queue_job/i18n/zh_CN.po index b8e3b8ea9a..8cac393e3c 100644 --- a/test_queue_job/i18n/zh_CN.po +++ b/test_queue_job/i18n/zh_CN.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * test_queue_job +# * test_queue_job # msgid "" msgstr "" @@ -16,6 +16,11 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.8\n" +#. module: test_queue_job +#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info +msgid "Additional Info" +msgstr "" + #. module: test_queue_job #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid From e411ab22b0bc9694ef12edcbbd828806343a88cf Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 31 May 2021 21:34:19 +0200 Subject: [PATCH 43/45] Fix display on exec_time on tree view as seconds The float_time widget shows hours seconds, we only want seconds. The widget had been removed on the form view, but not on the tree view. --- queue_job/views/queue_job_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 0df8fafa8c..a3c7adaf8e 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -114,7 +114,7 @@ - + From 851a56026137893c702dd90fcb4773f8cc4dcad5 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 1 Jun 2021 06:16:43 +0000 Subject: [PATCH 44/45] queue_job 13.0.3.10.0 --- queue_job/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index bab2167036..ff7029c0da 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Job Queue", - "version": "13.0.3.9.0", + "version": "13.0.3.10.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue/queue_job", "license": "LGPL-3", From e81299e52280e84f5c7b0fa21eda2dfdb391f81e Mon Sep 17 00:00:00 2001 From: Kalpesh Gajera Date: Wed, 12 Jan 2022 16:57:42 +0530 Subject: [PATCH 45/45] APX-825: [IMP] queue_job: Queue job link with parent job and queue job will not execute untill parent will be done. --- queue_job/__manifest__.py | 2 +- queue_job/controllers/main.py | 15 +++++++++++++-- queue_job/models/queue_job.py | 1 + queue_job/views/queue_job_views.xml | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index ff7029c0da..e81bac5eba 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Job Queue", - "version": "13.0.3.10.0", + "version": "13.0.4.1.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue/queue_job", "license": "LGPL-3", diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index 936b7b1592..4b658d627b 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -53,10 +53,12 @@ def retry_postpone(job, message, seconds=None): # ensure the job to run is in the correct state and lock the record env.cr.execute( - "SELECT state FROM queue_job WHERE uuid=%s AND state=%s FOR UPDATE", + "SELECT state, parent_id FROM queue_job WHERE uuid=%s AND state=%s FOR UPDATE", (job_uuid, ENQUEUED), ) - if not env.cr.fetchone(): + data = env.cr.fetchone() + + if not data: _logger.warn( "was requested to run job %s, but it does not exist, " "or is not in state %s", @@ -68,8 +70,17 @@ def retry_postpone(job, message, seconds=None): job = Job.load(env, job_uuid) assert job and job.state == ENQUEUED + if data and data[1]: + env.cr.execute( + "SELECT state FROM queue_job WHERE id=%s FOR UPDATE", ((data[1],)), + ) + st = env.cr.fetchone() + if st and st[0] != "done": + retry_postpone(job, "Sync Call", seconds=2) + return "" try: try: + self._try_perform_job(env, job) except OperationalError as err: # Automatically retry the typical transaction serialization diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index fd848861df..d42fd5b7fc 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -105,6 +105,7 @@ class QueueJob(models.Model): identity_key = fields.Char(readonly=True) worker_pid = fields.Integer(readonly=True) + parent_id = fields.Many2one("queue.job", string="Parent Job") def init(self): self._cr.execute( diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index a3c7adaf8e..c143da9eea 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -44,6 +44,7 @@ +