Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -1002,5 +1036,6 @@ diff --git a/README.md b/README.md
index 02ceb9d200..eca36e6e10 100644
--- a/README.md
+++ b/README.md
@@ -22,11 +22,11 @@ Available addons
addon | version | maintainers | summary
--- | --- | --- | ---
[base_import_async](base_import_async/) | 17.0.1.0.0 | | Import CSV files in the background
-[queue_job](queue_job/) | 17.0.1.3.1 |
| Job Queue
-[queue_job_cron](queue_job_cron/) | 17.0.1.0.0 | | Scheduled Actions as Queue Jobs
-[queue_job_cron_jobrunner](queue_job_cron_jobrunner/) | 17.0.1.0.0 |
| Run jobs without a dedicated JobRunner
+[queue_job](queue_job/) | 17.0.1.4.0 |
| Job Queue
+[queue_job_cron](queue_job_cron/) | 17.0.1.1.0 | | Scheduled Actions as Queue Jobs
+[queue_job_cron_jobrunner](queue_job_cron_jobrunner/) | 17.0.1.1.0 |
| Run jobs without a dedicated JobRunner
[queue_job_subscribe](queue_job_subscribe/) | 17.0.1.0.0 | | Control which users are subscribed to queue job notifications
-[test_queue_job](test_queue_job/) | 17.0.1.0.1 | | Queue Job Tests
+[test_queue_job](test_queue_job/) | 17.0.1.1.0 | | Queue Job Tests
[//]: # (end addons)
diff --git a/queue_job/README.rst b/queue_job/README.rst
index e9e2ea99fa..a0aa31e054 100644
--- a/queue_job/README.rst
+++ b/queue_job/README.rst
@@ -1,3 +1,7 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
=========
Job Queue
=========
@@ -7,13 +11,13 @@ Job Queue
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:8a75c10ad3e4ec6ac8b6178e6b04dfc40ac1bbe4130128f9fe3946eed920228f
+ !! source digest: sha256:0e908a7e024c5995acea7783a1f9ad76851955d69d8a88236d8508f6301c38b4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
:target: https://odoo-community.org/page/development-status
:alt: Mature
-.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
+.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github
@@ -281,6 +285,39 @@ is at the top of the graph. In the example above, if it was called on
``group_a``, then ``group_b`` would never be delayed (but a warning
would be shown).
+It is also possible to split a job into several jobs, each one
+processing a part of the work. This can be useful to avoid very long
+jobs, parallelize some task and get more specific errors. Usage is as
+follows:
+
+.. code:: python
+
+ def button_split_delayable(self):
+ (
+ self # Can be a big recordset, let's say 1000 records
+ .delayable()
+ .generate_thumbnail((50, 50))
+ .set(priority=30)
+ .set(description=_("generate xxx"))
+ .split(50) # Split the job in 20 jobs of 50 records each
+ .delay()
+ )
+
+The ``split()`` method takes a ``chain`` boolean keyword argument. If
+set to True, the jobs will be chained, meaning that the next job will
+only start when the previous one is done:
+
+.. code:: python
+
+ def button_increment_var(self):
+ (
+ self
+ .delayable()
+ .increment_counter()
+ .split(1, chain=True) # Will exceute the jobs one after the other
+ .delay()
+ )
+
Enqueing Job Options
~~~~~~~~~~~~~~~~~~~~
@@ -435,7 +472,7 @@ running Odoo**
When you are developing (ie: connector modules) you might want to bypass
the queue job and run your code immediately.
-To do so you can set QUEUE_JOB\__NO_DELAY=1 in your enviroment.
+To do so you can set QUEUE_JOB\__NO_DELAY=1 in your environment.
**Bypass jobs in tests**
diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 7433bdfdbe..76156b77d8 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -2,7 +2,7 @@
{
"name": "Job Queue",
- "version": "17.0.1.3.1",
+ "version": "17.0.1.4.0",
"author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/queue",
"license": "LGPL-3",
diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py
index ca3e02acaa..fce3049fa0 100644
--- a/queue_job/controllers/main.py
+++ b/queue_job/controllers/main.py
@@ -36,6 +36,9 @@ def _try_perform_job(self, env, job):
_logger.debug("%s started", job)
job.perform()
+ # Triggers any stored computed fields before calling 'set_done'
+ # so that will be part of the 'exec_time'
+ env.flush_all()
job.set_done()
job.store()
env.flush_all()
diff --git a/queue_job/delay.py b/queue_job/delay.py
index 9b596b1665..0ba54e48a9 100644
--- a/queue_job/delay.py
+++ b/queue_job/delay.py
@@ -232,7 +232,7 @@ def _ensure_same_graph_uuid(jobs):
elif jobs_count == 1:
if jobs[0].graph_uuid:
raise ValueError(
- f"Job {jobs[0]} is a single job, it should not" " have a graph uuid"
+ f"Job {jobs[0]} is a single job, it should not have a graph uuid"
)
else:
graph_uuids = {job.graph_uuid for job in jobs if job.graph_uuid}
@@ -483,11 +483,10 @@ def _tail(self):
return [self]
def __repr__(self):
- return "Delayable({}.{}({}, {}))".format(
- self.recordset,
- self._job_method.__name__ if self._job_method else "",
- self._job_args,
- self._job_kwargs,
+ return (
+ f"Delayable({self.recordset}."
+ f"{self._job_method.__name__ if self._job_method else ''}"
+ f"({self._job_args}, {self._job_kwargs}))"
)
def __del__(self):
@@ -525,6 +524,51 @@ def delay(self):
"""Delay the whole graph"""
self._graph.delay()
+ def split(self, size, chain=False):
+ """Split the Delayables.
+
+ Use `DelayableGroup` or `DelayableChain`
+ if `chain` is True containing batches of size `size`
+ """
+ if not self._job_method:
+ raise ValueError("No method set on the Delayable")
+
+ total_records = len(self.recordset)
+
+ delayables = []
+ for index in range(0, total_records, size):
+ recordset = self.recordset[index : index + size]
+ delayable = Delayable(
+ recordset,
+ priority=self.priority,
+ eta=self.eta,
+ max_retries=self.max_retries,
+ description=self.description,
+ channel=self.channel,
+ identity_key=self.identity_key,
+ )
+ # Update the __self__
+ delayable._job_method = getattr(recordset, self._job_method.__name__)
+ delayable._job_args = self._job_args
+ delayable._job_kwargs = self._job_kwargs
+
+ delayables.append(delayable)
+
+ description = self.description or (
+ self._job_method.__doc__.splitlines()[0].strip()
+ if self._job_method.__doc__
+ else f"{self.recordset._name}.{self._job_method.__name__}"
+ )
+ for index, delayable in enumerate(delayables):
+ delayable.set(
+ description=f"{description} (split {index + 1}/{len(delayables)})"
+ )
+
+ # Prevent warning on deletion
+ self._generated_job = True
+
+ return (DelayableChain if chain else DelayableGroup)(*delayables)
+
def _build_job(self):
if self._generated_job:
return self._generated_job
@@ -611,9 +655,9 @@ def _delay_delayable(*args, **kwargs):
return _delay_delayable
def __str__(self):
- return "DelayableRecordset({}{})".format(
- self.delayable.recordset._name,
- getattr(self.delayable.recordset, "_ids", ""),
+ return (
+ f"DelayableRecordset({self.delayable.recordset._name}"
+ f"{getattr(self.delayable.recordset, '_ids', '')})"
)
__repr__ = __str__
diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po
index db0b269e36..f575a55297 100644
--- a/queue_job/i18n/de.po
+++ b/queue_job/i18n/de.po
@@ -101,6 +101,7 @@ msgstr ""
#. module: queue_job
#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
msgid "Cancelled"
msgstr ""
@@ -176,6 +177,11 @@ msgstr "Erstellt am"
msgid "Created by"
msgstr "Erstellt von"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Created date"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__create_date
@@ -484,6 +490,21 @@ msgstr ""
msgid "Kwargs"
msgstr "Kwargs"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 24 hours"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 30 days"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 7 days"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__write_uid
@@ -907,8 +928,9 @@ msgstr ""
#, python-format
msgid ""
"Unexpected format of Retry Pattern for {}.\n"
-"Example of valid format:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+"Example of valid formats:\n"
+"{{1: 300, 5: 600, 10: 1200, 15: 3000}}\n"
+"{{1: (1, 10), 5: (11, 20), 10: (21, 30), 15: (100, 300)}}"
msgstr ""
#. module: queue_job
diff --git a/queue_job/i18n/es.po b/queue_job/i18n/es.po
index aaa626a4d8..5d599f54c9 100644
--- a/queue_job/i18n/es.po
+++ b/queue_job/i18n/es.po
@@ -104,6 +104,7 @@ msgstr "Cancelar trabajos"
#. module: queue_job
#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
msgid "Cancelled"
msgstr "Cancelada"
@@ -179,6 +180,11 @@ msgstr "Fecha de creación"
msgid "Created by"
msgstr "Creado por"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Created date"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__create_date
@@ -485,6 +491,21 @@ msgstr "Trabajos para gráfico %s"
msgid "Kwargs"
msgstr "Kwargs"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 24 hours"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 30 days"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 7 days"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__write_uid
@@ -923,12 +944,10 @@ msgstr ""
#, python-format
msgid ""
"Unexpected format of Retry Pattern for {}.\n"
-"Example of valid format:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+"Example of valid formats:\n"
+"{{1: 300, 5: 600, 10: 1200, 15: 3000}}\n"
+"{{1: (1, 10), 5: (11, 20), 10: (21, 30), 15: (100, 300)}}"
msgstr ""
-"Formato inesperado en el patrón de reintentos de {}.\n"
-"Ejemplo de un formato válido:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job__user_id
@@ -951,6 +970,16 @@ msgstr "Asistente para volver a poner en cola una selección de trabajos"
msgid "Worker Pid"
msgstr "Pid del trabajador"
+#, python-format
+#~ msgid ""
+#~ "Unexpected format of Retry Pattern for {}.\n"
+#~ "Example of valid format:\n"
+#~ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+#~ msgstr ""
+#~ "Formato inesperado en el patrón de reintentos de {}.\n"
+#~ "Ejemplo de un formato válido:\n"
+#~ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+
#, python-format
#~ msgid "If both parameters are 0, ALL jobs will be requeued!"
#~ msgstr ""
diff --git a/queue_job/i18n/it.po b/queue_job/i18n/it.po
index 2994eb9681..0886b8e240 100644
--- a/queue_job/i18n/it.po
+++ b/queue_job/i18n/it.po
@@ -104,6 +104,7 @@ msgstr "Annulla lavori"
#. module: queue_job
#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
msgid "Cancelled"
msgstr "Annullata"
@@ -179,6 +180,11 @@ msgstr "Data creazione"
msgid "Created by"
msgstr "Creato da"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Created date"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__create_date
@@ -484,6 +490,21 @@ msgstr "Lavori per grafico %s"
msgid "Kwargs"
msgstr "Kwargs"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 24 hours"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 30 days"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 7 days"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__write_uid
@@ -920,12 +941,10 @@ msgstr ""
#, python-format
msgid ""
"Unexpected format of Retry Pattern for {}.\n"
-"Example of valid format:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+"Example of valid formats:\n"
+"{{1: 300, 5: 600, 10: 1200, 15: 3000}}\n"
+"{{1: (1, 10), 5: (11, 20), 10: (21, 30), 15: (100, 300)}}"
msgstr ""
-"Formato inaspettato di schema tentativo per {}.\n"
-"Esempio di formato valido:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job__user_id
@@ -948,6 +967,16 @@ msgstr "Procedura guidata per riaccodare una selezione di lavori"
msgid "Worker Pid"
msgstr "PID worker"
+#, python-format
+#~ msgid ""
+#~ "Unexpected format of Retry Pattern for {}.\n"
+#~ "Example of valid format:\n"
+#~ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+#~ msgstr ""
+#~ "Formato inaspettato di schema tentativo per {}.\n"
+#~ "Esempio di formato valido:\n"
+#~ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+
#, python-format
#~ msgid "If both parameters are 0, ALL jobs will be requeued!"
#~ msgstr "Se entrambi i parametri sono 0, tutti i lavori verranno riaccodati!"
diff --git a/queue_job/i18n/queue_job.pot b/queue_job/i18n/queue_job.pot
index 8aaa602147..fc8e2bbbdb 100644
--- a/queue_job/i18n/queue_job.pot
+++ b/queue_job/i18n/queue_job.pot
@@ -97,6 +97,7 @@ msgstr ""
#. module: queue_job
#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
msgid "Cancelled"
msgstr ""
@@ -172,6 +173,11 @@ msgstr ""
msgid "Created by"
msgstr ""
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Created date"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__create_date
@@ -477,6 +483,21 @@ msgstr ""
msgid "Kwargs"
msgstr ""
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 24 hours"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 30 days"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 7 days"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__write_uid
@@ -882,8 +903,9 @@ msgstr ""
#, python-format
msgid ""
"Unexpected format of Retry Pattern for {}.\n"
-"Example of valid format:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+"Example of valid formats:\n"
+"{{1: 300, 5: 600, 10: 1200, 15: 3000}}\n"
+"{{1: (1, 10), 5: (11, 20), 10: (21, 30), 15: (100, 300)}}"
msgstr ""
#. module: queue_job
diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po
index 897aa1e4ea..804ca86780 100644
--- a/queue_job/i18n/zh_CN.po
+++ b/queue_job/i18n/zh_CN.po
@@ -104,6 +104,7 @@ msgstr "取消作业"
#. module: queue_job
#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
msgid "Cancelled"
msgstr "已取消"
@@ -179,6 +180,11 @@ msgstr "创建日期"
msgid "Created by"
msgstr "创建者"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Created date"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__create_date
@@ -484,6 +490,21 @@ msgstr "图表 %s 的作业"
msgid "Kwargs"
msgstr "关键字参数"
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 24 hours"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 30 days"
+msgstr ""
+
+#. module: queue_job
+#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search
+msgid "Last 7 days"
+msgstr ""
+
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid
#: model:ir.model.fields,field_description:queue_job.field_queue_job_lock__write_uid
@@ -914,11 +935,10 @@ msgstr ""
#, python-format
msgid ""
"Unexpected format of Retry Pattern for {}.\n"
-"Example of valid format:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+"Example of valid formats:\n"
+"{{1: 300, 5: 600, 10: 1200, 15: 3000}}\n"
+"{{1: (1, 10), 5: (11, 20), 10: (21, 30), 15: (100, 300)}}"
msgstr ""
-"对于 {},重试模式的格式不符合预期。 有效格式的示例:\n"
-"{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job__user_id
@@ -941,6 +961,15 @@ msgstr "重新排队向导所选的作业"
msgid "Worker Pid"
msgstr "工作进程PID"
+#, python-format
+#~ msgid ""
+#~ "Unexpected format of Retry Pattern for {}.\n"
+#~ "Example of valid format:\n"
+#~ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+#~ msgstr ""
+#~ "对于 {},重试模式的格式不符合预期。 有效格式的示例:\n"
+#~ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+
#, python-format
#~ msgid "If both parameters are 0, ALL jobs will be requeued!"
#~ msgstr "如果两个参数都为0,所有任务都将被重新排队!"
diff --git a/queue_job/job.py b/queue_job/job.py
index e03dd2b517..a473be5cd0 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -594,8 +594,8 @@ def perform(self):
return self.result
- def enqueue_waiting(self):
- sql = """
+ def _get_common_dependent_jobs_query(self):
+ return """
UPDATE queue_job
SET state = %s
FROM (
@@ -623,9 +623,17 @@ def enqueue_waiting(self):
AND %s = ALL(jobs.parent_states)
AND state = %s;
"""
+
+ def enqueue_waiting(self):
+ sql = self._get_common_dependent_jobs_query()
self.env.cr.execute(sql, (PENDING, self.uuid, DONE, WAIT_DEPENDENCIES))
self.env["queue.job"].invalidate_model(["state"])
+ def cancel_dependent_jobs(self):
+ sql = self._get_common_dependent_jobs_query()
+ self.env.cr.execute(sql, (CANCELLED, self.uuid, CANCELLED, WAIT_DEPENDENCIES))
+ self.env["queue.job"].invalidate_model(["state"])
+
def store(self):
"""Store the Job"""
job_model = self.env["queue.job"]
diff --git a/queue_job/migrations/17.0.1.3.2/pre-migration.py b/queue_job/migrations/17.0.1.3.2/pre-migration.py
new file mode 100644
index 0000000000..53d9690caa
--- /dev/null
+++ b/queue_job/migrations/17.0.1.3.2/pre-migration.py
@@ -0,0 +1,10 @@
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
+
+from odoo.tools.sql import table_exists
+
+
+def migrate(cr, version):
+ if table_exists(cr, "queue_job"):
+ # Drop index 'queue_job_identity_key_state_partial_index',
+ # it will be recreated during the update
+ cr.execute("DROP INDEX IF EXISTS queue_job_identity_key_state_partial_index;")
diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py
index 33dbf2346d..55ee7e526c 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -93,7 +93,7 @@ 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_message = fields.Char(string="Exception Message", readonly=True, tracking=True)
exc_info = fields.Text(string="Exception Info", readonly=True)
result = fields.Text(readonly=True)
@@ -138,7 +138,7 @@ def init(self):
self._cr.execute(
"CREATE INDEX queue_job_identity_key_state_partial_index "
"ON queue_job (identity_key) WHERE state in ('pending', "
- "'enqueued') AND identity_key IS NOT NULL;"
+ "'enqueued', 'wait_dependencies') AND identity_key IS NOT NULL;"
)
@api.depends("records")
@@ -325,6 +325,8 @@ def _change_job_state(self, state, result=None):
elif state == CANCELLED:
job_.set_cancelled(result=result)
job_.store()
+ record.env["queue.job"].flush_model()
+ job_.cancel_dependent_jobs()
else:
raise ValueError("State not supported: %s" % state)
diff --git a/queue_job/models/queue_job_function.py b/queue_job/models/queue_job_function.py
index 10b19345b7..7cf73ea370 100644
--- a/queue_job/models/queue_job_function.py
+++ b/queue_job/models/queue_job_function.py
@@ -155,10 +155,12 @@ def _parse_retry_pattern(self):
try:
# as json can't have integers as keys and the field is stored
# as json, convert back to int
- retry_pattern = {
- int(try_count): postpone_seconds
- for try_count, postpone_seconds in self.retry_pattern.items()
- }
+ retry_pattern = {}
+ for try_count, postpone_value in self.retry_pattern.items():
+ if isinstance(postpone_value, int):
+ retry_pattern[int(try_count)] = postpone_value
+ else:
+ retry_pattern[int(try_count)] = tuple(postpone_value)
except ValueError:
_logger.error(
"Invalid retry pattern for job function %s,"
@@ -187,8 +189,9 @@ def job_config(self, name):
def _retry_pattern_format_error_message(self):
return _(
"Unexpected format of Retry Pattern for {}.\n"
- "Example of valid format:\n"
- "{{1: 300, 5: 600, 10: 1200, 15: 3000}}"
+ "Example of valid formats:\n"
+ "{{1: 300, 5: 600, 10: 1200, 15: 3000}}\n"
+ "{{1: (1, 10), 5: (11, 20), 10: (21, 30), 15: (100, 300)}}"
).format(self.name)
@api.constrains("retry_pattern")
@@ -201,12 +204,20 @@ def _check_retry_pattern(self):
all_values = list(retry_pattern) + list(retry_pattern.values())
for value in all_values:
try:
- int(value)
+ self._retry_value_type_check(value)
except ValueError as ex:
raise exceptions.UserError(
record._retry_pattern_format_error_message()
) from ex
+ def _retry_value_type_check(self, value):
+ if isinstance(value, (tuple | list)):
+ if len(value) != 2:
+ raise ValueError
+ [self._retry_value_type_check(element) for element in value]
+ return
+ int(value)
+
def _related_action_format_error_message(self):
return _(
"Unexpected format of Related Action for {}.\n"
diff --git a/queue_job/readme/USAGE.md b/queue_job/readme/USAGE.md
index fb160bfa48..deb6fe2aca 100644
--- a/queue_job/readme/USAGE.md
+++ b/queue_job/readme/USAGE.md
@@ -108,6 +108,38 @@ is at the top of the graph. In the example above, if it was called on
`group_a`, then `group_b` would never be delayed (but a warning would be
shown).
+It is also possible to split a job into several jobs, each one processing
+a part of the work. This can be useful to avoid very long jobs, parallelize
+some task and get more specific errors. Usage is as follows:
+
+``` python
+def button_split_delayable(self):
+ (
+ self # Can be a big recordset, let's say 1000 records
+ .delayable()
+ .generate_thumbnail((50, 50))
+ .set(priority=30)
+ .set(description=_("generate xxx"))
+ .split(50) # Split the job in 20 jobs of 50 records each
+ .delay()
+ )
+```
+
+The `split()` method takes a `chain` boolean keyword argument. If set to
+True, the jobs will be chained, meaning that the next job will only start
+when the previous one is done:
+
+``` python
+def button_increment_var(self):
+ (
+ self
+ .delayable()
+ .increment_counter()
+ .split(1, chain=True) # Will exceute the jobs one after the other
+ .delay()
+ )
+```
+
### Enqueing Job Options
- priority: default is 10, the closest it is to 0, the faster it will be
@@ -258,7 +290,7 @@ running Odoo**
When you are developing (ie: connector modules) you might want to bypass
the queue job and run your code immediately.
-To do so you can set QUEUE_JOB\_\_NO_DELAY=1 in your enviroment.
+To do so you can set QUEUE_JOB\_\_NO_DELAY=1 in your environment.
**Bypass jobs in tests**
diff --git a/queue_job/static/description/index.html b/queue_job/static/description/index.html
index 522f4c3abd..0ea18c2065 100644
--- a/queue_job/static/description/index.html
+++ b/queue_job/static/description/index.html
@@ -3,7 +3,7 @@
This addon adds an integrated Job Queue to Odoo.
It allows to postpone method calls executed asynchronously.
Jobs are executed in the background by a Jobrunner, in their own @@ -445,7 +450,7 @@
Odoo treats task synchronously, like when you import a list of products it will treat each line in one big task. “Queue job” gives you the ability to detail big tasks in many smaller ones.
@@ -473,11 +478,11 @@To use this module, you need to:
The fast way to enqueue a job for a method is to use with_delay() on a record or model:
@@ -627,9 +632,38 @@Delaying jobs
is at the top of the graph. In the example above, if it was called on group_a, then group_b would never be delayed (but a warning would be shown). +It is also possible to split a job into several jobs, each one +processing a part of the work. This can be useful to avoid very long +jobs, parallelize some task and get more specific errors. Usage is as +follows:
++def button_split_delayable(self): + ( + self # Can be a big recordset, let's say 1000 records + .delayable() + .generate_thumbnail((50, 50)) + .set(priority=30) + .set(description=_("generate xxx")) + .split(50) # Split the job in 20 jobs of 50 records each + .delay() + ) ++The split() method takes a chain boolean keyword argument. If +set to True, the jobs will be chained, meaning that the next job will +only start when the previous one is done:
++def button_increment_var(self): + ( + self + .delayable() + .increment_counter() + .split(1, chain=True) # Will exceute the jobs one after the other + .delay() + ) +
In earlier versions, jobs could be configured using the @job decorator. This is now obsolete, they can be configured using optional queue.job.function and queue.job.channel XML records.
@@ -757,7 +791,7 @@When you are developing (ie: connector modules) you might want to bypass the queue job and run your code immediately.
-To do so you can set QUEUE_JOB__NO_DELAY=1 in your enviroment.
+To do so you can set QUEUE_JOB__NO_DELAY=1 in your environment.
Bypass jobs in tests
When writing tests on job-related methods is always tricky to deal with delayed recordsets. To make your testing life easier you can set @@ -776,7 +810,7 @@
Asserting enqueued jobs
The recommended way to test jobs, rather than running them directly and synchronously is to split the tests in two parts:
@@ -891,7 +925,7 @@Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -960,16 +994,16 @@
Do not contact contributors directly about support or help with technical issues.