From 0e6a65eb01b14868528c18a8d0a802ea916618dd Mon Sep 17 00:00:00 2001 From: Milan Topuzov Date: Wed, 1 Oct 2025 21:57:02 +0200 Subject: [PATCH 1/3] [DON'T MERGE] test-requirements.txt (temp refs to base PR 840) - Use temporary references so CI/runboat picks queue_job from PR 840\n- Remove after base PR merges --- queue_job_cron/README.rst | 10 +- queue_job_cron/__manifest__.py | 4 +- queue_job_cron/models/ir_cron.py | 4 - queue_job_cron/static/description/index.html | 6 +- queue_job_cron/tests/test_queue_job_cron.py | 138 ++++++++++++------ queue_job_cron_jobrunner/README.rst | 10 +- queue_job_cron_jobrunner/__manifest__.py | 4 +- .../static/description/index.html | 6 +- test-requirements.txt | 2 + 9 files changed, 115 insertions(+), 69 deletions(-) create mode 100644 test-requirements.txt diff --git a/queue_job_cron/README.rst b/queue_job_cron/README.rst index 9bc2b222b..623569c38 100644 --- a/queue_job_cron/README.rst +++ b/queue_job_cron/README.rst @@ -21,13 +21,13 @@ Scheduled Actions as Queue Jobs :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github - :target: https://github.com/OCA/queue/tree/18.0/queue_job_cron + :target: https://github.com/OCA/queue/tree/19.0/queue_job_cron :alt: OCA/queue .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/queue-18-0/queue-18-0-queue_job_cron + :target: https://translation.odoo-community.org/projects/queue-19-0/queue-19-0-queue_job_cron :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -89,7 +89,7 @@ Bug Tracker 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -128,6 +128,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/queue `_ project on GitHub. +This module is part of the `OCA/queue `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/queue_job_cron/__manifest__.py b/queue_job_cron/__manifest__.py index 5d14b5d60..4e21325fd 100644 --- a/queue_job_cron/__manifest__.py +++ b/queue_job_cron/__manifest__.py @@ -3,12 +3,12 @@ { "name": "Scheduled Actions as Queue Jobs", - "version": "18.0.1.1.1", + "version": "19.0.1.0.0", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue", "license": "AGPL-3", "category": "Generic Modules", "depends": ["queue_job"], "data": ["data/data.xml", "views/ir_cron_view.xml"], - "installable": False, + "installable": True, } diff --git a/queue_job_cron/models/ir_cron.py b/queue_job_cron/models/ir_cron.py index 7fc99cef5..ca34c6b1c 100644 --- a/queue_job_cron/models/ir_cron.py +++ b/queue_job_cron/models/ir_cron.py @@ -1,13 +1,9 @@ # Copyright 2019 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) -import logging - from odoo import api, fields, models from odoo.addons.queue_job.job import identity_exact -_logger = logging.getLogger(__name__) - class IrCron(models.Model): _inherit = "ir.cron" diff --git a/queue_job_cron/static/description/index.html b/queue_job_cron/static/description/index.html index 563795213..7ad2ae193 100644 --- a/queue_job_cron/static/description/index.html +++ b/queue_job_cron/static/description/index.html @@ -374,7 +374,7 @@

Scheduled Actions as Queue Jobs

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:61571266d30481c36fe1d1751209760e580f0fdd528d8a39b9610f37a442c920 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

This module extends the functionality of queue_job and allows to run an Odoo cron as a queue job.

Table of contents

@@ -445,7 +445,7 @@

Bug Tracker

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 -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -479,7 +479,7 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/queue project on GitHub.

+

This module is part of the OCA/queue project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/queue_job_cron/tests/test_queue_job_cron.py b/queue_job_cron/tests/test_queue_job_cron.py index 8457ef404..a6915a61d 100644 --- a/queue_job_cron/tests/test_queue_job_cron.py +++ b/queue_job_cron/tests/test_queue_job_cron.py @@ -1,5 +1,6 @@ # Copyright 2019 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from odoo.tests.common import TransactionCase @@ -8,20 +9,23 @@ def setUp(self): super().setUp() def test_queue_job_cron(self): - QueueJob = self.env["queue.job"] default_channel = self.env.ref("queue_job_cron.channel_root_ir_cron") cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") self.assertFalse(cron.run_as_queue_job) - cron.method_direct_trigger() - nb_jobs = QueueJob.search_count([("name", "=", cron.name)]) + # Use core helper enter_registry_test_mode so method_direct_trigger + # runs safely under 19.0 test harness (avoids cross-cursor + # visibility/locking quirks during tests). + with self.enter_registry_test_mode(): + cron.method_direct_trigger() + nb_jobs = self.env["queue.job"].search_count([("name", "=", cron.name)]) self.assertEqual(nb_jobs, 0) + # Enable run_as_queue_job and trigger via method_direct_trigger cron.write({"run_as_queue_job": True, "channel_id": default_channel.id}) - - cron.method_direct_trigger() - qjob = QueueJob.search([("name", "=", cron.name)]) - + with self.enter_registry_test_mode(): + cron.method_direct_trigger() + qjob = self.env["queue.job"].search([("name", "=", cron.name)]) self.assertTrue(qjob) self.assertEqual(qjob.name, cron.name) self.assertEqual(qjob.priority, cron.priority) @@ -32,8 +36,13 @@ def test_queue_job_cron_depends(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") default_channel = self.env.ref("queue_job_cron.channel_root_ir_cron") self.assertFalse(cron.run_as_queue_job) - cron.write({"run_as_queue_job": True}) - self.assertEqual(cron.channel_id.id, default_channel.id) + # Write + assert in a fresh cursor to avoid ir.cron row lock + # serialization under 19.0 when scheduler touches it. + with self.registry.cursor() as cr: + env2 = self.env(cr=cr) + cron2 = env2["ir.cron"].browse(cron.id) + cron2.write({"run_as_queue_job": True}) + self.assertEqual(cron2.channel_id.id, default_channel.id) def test_queue_job_cron_run(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") @@ -43,42 +52,81 @@ def test_queue_job_cron_run(self): def test_queue_job_no_parallelism(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") default_channel = self.env.ref("queue_job_cron.channel_root_ir_cron") - cron.write( - { - "no_parallel_queue_job_run": True, - "run_as_queue_job": True, - "channel_id": default_channel.id, - } - ) - cron.method_direct_trigger() - cron.method_direct_trigger() - nb_jobs = self.env["queue.job"].search_count([("name", "=", cron.name)]) - self.assertEqual(nb_jobs, 1) - cron.no_parallel_queue_job_run = False - cron.method_direct_trigger() - nb_jobs = self.env["queue.job"].search_count([("name", "=", cron.name)]) - self.assertEqual(nb_jobs, 2) + # Configure + enqueue in a fresh cursor to avoid serialization + # conflicts; call _delay_run_job_as_queue_job twice to exercise + # identity-based dedup under no_parallel setting. + with self.registry.cursor() as cr: + env2 = self.env(cr=cr) + cron2 = env2["ir.cron"].browse(cron.id) + cron2.write( + { + "no_parallel_queue_job_run": True, + "run_as_queue_job": True, + "channel_id": default_channel.id, + } + ) + # Enqueue twice via the queue path; identity prevents duplicates + cron2._delay_run_job_as_queue_job(server_action=cron2.ir_actions_server_id) + cron2._delay_run_job_as_queue_job(server_action=cron2.ir_actions_server_id) + nb_jobs2 = env2["queue.job"].search_count([("name", "=", cron2.name)]) + self.assertEqual(nb_jobs2, 1) + # Allow parallelism and enqueue once more; count increases + cron2.write({"no_parallel_queue_job_run": False}) + cron2._delay_run_job_as_queue_job(server_action=cron2.ir_actions_server_id) + nb_jobs2 = env2["queue.job"].search_count([("name", "=", cron2.name)]) + self.assertEqual(nb_jobs2, 2) + # Cleanup: enqueues above happen in a committed cursor; remove them to + # avoid leaking pending jobs into subsequent modules (e.g. jobrunner). + with self.registry.cursor() as cr: + env2 = self.env(cr=cr) + env2["queue.job"].sudo().search([("name", "=", cron.name)]).unlink() def test_queue_job_cron_callback(self): - nb_partners = self.env["res.partner"].search_count([]) - nb_jobs = self.env["queue.job"].search_count([]) - partner_model = self.env.ref("base.model_res_partner") - action = self.env["ir.actions.server"].create( - { - "name": "Queue job cron callback action create partner", - "state": "code", - "model_id": partner_model.id, - "crud_model_id": partner_model.id, - "code": "model.name_create('job Cron partner')", - } - ) cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") - cron._callback("Test queue job cron", action.id) - nb_partners_after_cron = self.env["res.partner"].search_count([]) - self.assertEqual(nb_partners_after_cron, nb_partners + 1) - cron.write({"run_as_queue_job": True}) - cron._callback("Test queue job cron", action.id) - nb_partners_after_cron = self.env["res.partner"].search_count([]) - self.assertEqual(nb_partners_after_cron, nb_partners + 1) - nb_jobs_after_cron = self.env["queue.job"].search_count([]) - self.assertEqual(nb_jobs_after_cron, nb_jobs + 1) + # Run _callback in a separate cursor because core _callback + # commits/rollbacks; main test cursor forbids it. Assert within the + # same cursor for deterministic visibility. + with self.registry.cursor() as cr: + env2 = self.env(cr=cr) + count_before = env2["res.partner"].search_count([]) + partner_model = env2.ref("base.model_res_partner") + action = env2["ir.actions.server"].create( + { + "name": "Queue job cron callback action create partner", + "state": "code", + "model_id": partner_model.id, + "crud_model_id": partner_model.id, + "code": "model.name_create('job Cron partner')", + } + ) + env2["ir.cron"].browse(cron.id)._callback("Test queue job cron", action.id) + partners_after = env2["res.partner"].search_count([]) + self.assertEqual(partners_after, count_before + 1) + # Phase 2: enable run_as_queue_job and ensure callback enqueues a job + # (not synchronous); use a separate cursor and assert within it. + with self.registry.cursor() as cr: + env2 = self.env(cr=cr) + env2["ir.cron"].browse(cron.id).write({"run_as_queue_job": True}) + count_before = env2["res.partner"].search_count([]) + jobs_before = env2["queue.job"].search_count([]) + partner_model = env2.ref("base.model_res_partner") + action = env2["ir.actions.server"].create( + { + "name": "Queue job cron callback action create partner", + "state": "code", + "model_id": partner_model.id, + "crud_model_id": partner_model.id, + "code": "model.name_create('job Cron partner')", + } + ) + env2["ir.cron"].browse(cron.id)._callback("Test queue job cron", action.id) + partners_after = env2["res.partner"].search_count([]) + jobs_after = env2["queue.job"].search_count([]) + self.assertEqual(partners_after, count_before) + self.assertEqual(jobs_after, jobs_before + 1) + # Cleanup: ensure no leakage across tests when using a shared DB name + with self.registry.cursor() as cr: + env2 = self.env(cr=cr) + cron2 = env2["ir.cron"].browse(cron.id) + jobs = env2["queue.job"].sudo().search([("name", "=", cron2.name)]) + jobs.unlink() diff --git a/queue_job_cron_jobrunner/README.rst b/queue_job_cron_jobrunner/README.rst index 8615285de..0b41ea48d 100644 --- a/queue_job_cron_jobrunner/README.rst +++ b/queue_job_cron_jobrunner/README.rst @@ -21,13 +21,13 @@ Queue Job Cron Jobrunner :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github - :target: https://github.com/OCA/queue/tree/18.0/queue_job_cron_jobrunner + :target: https://github.com/OCA/queue/tree/19.0/queue_job_cron_jobrunner :alt: OCA/queue .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/queue-18-0/queue-18-0-queue_job_cron_jobrunner + :target: https://translation.odoo-community.org/projects/queue-19-0/queue-19-0-queue_job_cron_jobrunner :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -102,7 +102,7 @@ Bug Tracker 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -142,6 +142,6 @@ Current `maintainer `__: |maintainer-ivantodorovich| -This module is part of the `OCA/queue `_ project on GitHub. +This module is part of the `OCA/queue `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/queue_job_cron_jobrunner/__manifest__.py b/queue_job_cron_jobrunner/__manifest__.py index c61918262..dad488178 100644 --- a/queue_job_cron_jobrunner/__manifest__.py +++ b/queue_job_cron_jobrunner/__manifest__.py @@ -1,7 +1,7 @@ { "name": "Queue Job Cron Jobrunner", "summary": "Run jobs without a dedicated JobRunner", - "version": "18.0.1.0.1", + "version": "19.0.1.0.0", "development_status": "Alpha", "author": "Camptocamp SA, Odoo Community Association (OCA)", "maintainers": ["ivantodorovich"], @@ -13,5 +13,5 @@ "data/ir_cron.xml", "views/ir_cron.xml", ], - "installable": False, + "installable": True, } diff --git a/queue_job_cron_jobrunner/static/description/index.html b/queue_job_cron_jobrunner/static/description/index.html index 9efaba869..44a07ec12 100644 --- a/queue_job_cron_jobrunner/static/description/index.html +++ b/queue_job_cron_jobrunner/static/description/index.html @@ -374,7 +374,7 @@

Queue Job Cron Jobrunner

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:3c8052bb9647ac1c2222b7bbe43133884ab314534ef09686a3aef58e6b38f712 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Alpha License: AGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

+

Alpha License: AGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

This module implements a simple queue.job runner using ir.cron triggers.

It’s meant to be used on environments where the regular job runner can’t @@ -446,7 +446,7 @@

Bug Tracker

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 -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -480,7 +480,7 @@

Maintainers

promote its widespread use.

Current maintainer:

ivantodorovich

-

This module is part of the OCA/queue project on GitHub.

+

This module is part of the OCA/queue project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000..dca911c65 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +odoo-addon-queue_job @ git+https://github.com/OCA/queue.git@refs/pull/840/head#subdirectory=queue_job +odoo-addon-test_queue_job @ git+https://github.com/OCA/queue.git@refs/pull/840/head#subdirectory=test_queue_job From 88aadce9460af8190409cac447045a47c153b8cc Mon Sep 17 00:00:00 2001 From: Milan Topuzov Date: Wed, 1 Oct 2025 21:57:14 +0200 Subject: [PATCH 2/3] [pre-commit] update excluded addons (queue_job_cron now included) --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d0fa1036..db873b2e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,8 +4,6 @@ exclude: | ^base_import_async/| ^queue_job/| ^queue_job_batch/| - ^queue_job_cron/| - ^queue_job_cron_jobrunner/| ^queue_job_subscribe/| ^test_queue_job/| ^test_queue_job_batch/| From 0b879296e505b8dba42caa6389e1fa5336fe913b Mon Sep 17 00:00:00 2001 From: Milan Topuzov Date: Wed, 1 Oct 2025 21:58:10 +0200 Subject: [PATCH 3/3] pre-commit: autofixes for cron scope (drop requirements.txt, trim whitespace) - Delete root requirements.txt auto-removed by hooks\n- Trim trailing whitespace in cron tests\n\nNon-functional changes only --- queue_job_cron/tests/test_queue_job_cron.py | 14 +++++++------- requirements.txt | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 requirements.txt diff --git a/queue_job_cron/tests/test_queue_job_cron.py b/queue_job_cron/tests/test_queue_job_cron.py index a6915a61d..4b762e8de 100644 --- a/queue_job_cron/tests/test_queue_job_cron.py +++ b/queue_job_cron/tests/test_queue_job_cron.py @@ -13,8 +13,8 @@ def test_queue_job_cron(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") self.assertFalse(cron.run_as_queue_job) - # Use core helper enter_registry_test_mode so method_direct_trigger - # runs safely under 19.0 test harness (avoids cross-cursor + # Use core helper enter_registry_test_mode so method_direct_trigger + # runs safely under 19.0 test harness (avoids cross-cursor # visibility/locking quirks during tests). with self.enter_registry_test_mode(): cron.method_direct_trigger() @@ -36,7 +36,7 @@ def test_queue_job_cron_depends(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") default_channel = self.env.ref("queue_job_cron.channel_root_ir_cron") self.assertFalse(cron.run_as_queue_job) - # Write + assert in a fresh cursor to avoid ir.cron row lock + # Write + assert in a fresh cursor to avoid ir.cron row lock # serialization under 19.0 when scheduler touches it. with self.registry.cursor() as cr: env2 = self.env(cr=cr) @@ -52,8 +52,8 @@ def test_queue_job_cron_run(self): def test_queue_job_no_parallelism(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") default_channel = self.env.ref("queue_job_cron.channel_root_ir_cron") - # Configure + enqueue in a fresh cursor to avoid serialization - # conflicts; call _delay_run_job_as_queue_job twice to exercise + # Configure + enqueue in a fresh cursor to avoid serialization + # conflicts; call _delay_run_job_as_queue_job twice to exercise # identity-based dedup under no_parallel setting. with self.registry.cursor() as cr: env2 = self.env(cr=cr) @@ -83,8 +83,8 @@ def test_queue_job_no_parallelism(self): def test_queue_job_cron_callback(self): cron = self.env.ref("queue_job.ir_cron_autovacuum_queue_jobs") - # Run _callback in a separate cursor because core _callback - # commits/rollbacks; main test cursor forbids it. Assert within the + # Run _callback in a separate cursor because core _callback + # commits/rollbacks; main test cursor forbids it. Assert within the # same cursor for deterministic visibility. with self.registry.cursor() as cr: env2 = self.env(cr=cr) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b4d39fb9e..000000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# generated from manifests external_dependencies -requests