diff --git a/base_export_async/models/delay_export.py b/base_export_async/models/delay_export.py
index a7dde9ba81..549d44a1e1 100644
--- a/base_export_async/models/delay_export.py
+++ b/base_export_async/models/delay_export.py
@@ -89,14 +89,18 @@ def export(self, params):
export_record = self.sudo().create({"user_ids": [(6, 0, users.ids)]})
name = "{}.{}".format(model_name, export_format)
- attachment = self.env["ir.attachment"].create(
- {
- "name": name,
- "datas": base64.b64encode(content),
- "type": "binary",
- "res_model": self._name,
- "res_id": export_record.id,
- }
+ attachment = (
+ self.env["ir.attachment"]
+ .sudo()
+ .create(
+ {
+ "name": name,
+ "datas": base64.b64encode(content),
+ "type": "binary",
+ "res_model": self._name,
+ "res_id": export_record.id,
+ }
+ )
)
url = "{}/web/content/ir.attachment/{}/datas/{}?download=true".format(
diff --git a/base_export_async/tests/test_base_export_async.py b/base_export_async/tests/test_base_export_async.py
index 482fabaee0..d10ca04281 100644
--- a/base_export_async/tests/test_base_export_async.py
+++ b/base_export_async/tests/test_base_export_async.py
@@ -23,7 +23,7 @@
"domain": [],
"context": {"lang": "en_US", "tz": "Europe/Brussels", "uid": 2},
"import_compat": false,
- "user_ids": [2]
+ "user_ids": [6]
}"""
}
@@ -37,7 +37,7 @@
"domain": [],
"context": {"lang": "en_US", "tz": "Europe/Brussels", "uid": 2},
"import_compat": false,
- "user_ids": [2]
+ "user_ids": [6]
}"""
}
diff --git a/base_import_async/data/queue_job_function_data.xml b/base_import_async/data/queue_job_function_data.xml
index 22cc8dbab0..fb04a63613 100644
--- a/base_import_async/data/queue_job_function_data.xml
+++ b/base_import_async/data/queue_job_function_data.xml
@@ -1,7 +1,13 @@
+
+ base_import
+
+
+
_split_file
+
_import_one_chunk
+
Configure default options for job
Bypass jobs on 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
When writing tests on job-related methods is always tricky to deal with
delayed recordsets. To make your testing life easier
diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py
index c463d3456d..13f1f5f832 100644
--- a/queue_job/tests/common.py
+++ b/queue_job/tests/common.py
@@ -428,7 +428,7 @@ def __init__(
def setUp(self):
"""Log an extra statement which test is started."""
- super(OdooDocTestCase, self).setUp()
+ super().setUp()
logging.getLogger(__name__).info("Running tests for %s", self._dt_test.name)
diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml
index a7099254d0..40d060931a 100644
--- a/queue_job/views/queue_job_views.xml
+++ b/queue_job/views/queue_job_views.xml
@@ -250,6 +250,22 @@
string="Cancelled"
domain="[('state', '=', 'cancelled')]"
/>
+
+
+
+
+
diff --git a/queue_job_cron/models/ir_cron.py b/queue_job_cron/models/ir_cron.py
index 7e4f5b848d..bb09ed075e 100644
--- a/queue_job_cron/models/ir_cron.py
+++ b/queue_job_cron/models/ir_cron.py
@@ -28,13 +28,16 @@ class IrCron(models.Model):
comodel_name="queue.job.channel",
compute="_compute_run_as_queue_job",
readonly=False,
+ store=True,
string="Channel",
)
@api.depends("run_as_queue_job")
def _compute_run_as_queue_job(self):
for cron in self:
- if cron.run_as_queue_job and not cron.channel_id:
+ if cron.channel_id:
+ continue
+ if cron.run_as_queue_job:
cron.channel_id = self.env.ref("queue_job_cron.channel_root_ir_cron").id
else:
cron.channel_id = False
diff --git a/queue_job_cron_jobrunner/models/queue_job.py b/queue_job_cron_jobrunner/models/queue_job.py
index 2e19556b95..a3c3d721e4 100644
--- a/queue_job_cron_jobrunner/models/queue_job.py
+++ b/queue_job_cron_jobrunner/models/queue_job.py
@@ -40,7 +40,7 @@ def _acquire_one_job(self):
FROM queue_job
WHERE state = 'pending'
AND (eta IS NULL OR eta <= (now() AT TIME ZONE 'UTC'))
- ORDER BY date_created DESC
+ ORDER BY priority, date_created
LIMIT 1 FOR NO KEY UPDATE SKIP LOCKED
"""
)
diff --git a/queue_job_cron_jobrunner/tests/test_queue_job.py b/queue_job_cron_jobrunner/tests/test_queue_job.py
index 3f2e0ef637..54800b792c 100644
--- a/queue_job_cron_jobrunner/tests/test_queue_job.py
+++ b/queue_job_cron_jobrunner/tests/test_queue_job.py
@@ -67,5 +67,33 @@ def test_queue_job_cron_trigger_enqueue_dependencies(self):
self.assertEqual(job_record.state, "done", "Processed OK")
# if the state is "waiting_dependencies", it means the "enqueue_waiting()"
- # step has not been doen when the parent job has been done
+ # step has not been done when the parent job has been done
self.assertEqual(job_record_depends.state, "done", "Processed OK")
+
+ def test_acquire_one_job_use_priority(self):
+ with freeze_time("2024-01-01 10:01:01"):
+ self.env["res.partner"].with_delay(priority=3).create({"name": "test"})
+
+ with freeze_time("2024-01-01 10:02:01"):
+ job = (
+ self.env["res.partner"].with_delay(priority=1).create({"name": "test"})
+ )
+
+ with freeze_time("2024-01-01 10:03:01"):
+ self.env["res.partner"].with_delay(priority=2).create({"name": "test"})
+
+ self.assertEqual(self.env["queue.job"]._acquire_one_job(), job.db_record())
+
+ def test_acquire_one_job_consume_the_oldest_first(self):
+ with freeze_time("2024-01-01 10:01:01"):
+ job = (
+ self.env["res.partner"].with_delay(priority=30).create({"name": "test"})
+ )
+
+ with freeze_time("2024-01-01 10:02:01"):
+ self.env["res.partner"].with_delay(priority=30).create({"name": "test"})
+
+ with freeze_time("2024-01-01 10:03:01"):
+ self.env["res.partner"].with_delay(priority=30).create({"name": "test"})
+
+ self.assertEqual(self.env["queue.job"]._acquire_one_job(), job.db_record())
diff --git a/queue_job_subscribe/tests/test_job_subscribe.py b/queue_job_subscribe/tests/test_job_subscribe.py
index 0f1fcddf48..935f15f74a 100644
--- a/queue_job_subscribe/tests/test_job_subscribe.py
+++ b/queue_job_subscribe/tests/test_job_subscribe.py
@@ -8,7 +8,7 @@
class TestJobSubscribe(common.TransactionCase):
def setUp(self):
- super(TestJobSubscribe, self).setUp()
+ super().setUp()
grp_queue_job_manager = self.ref("queue_job.group_queue_job_manager")
self.other_partner_a = self.env["res.partner"].create(
{"name": "My Company a", "is_company": True, "email": "test@tes.ttest"}
diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py
index f810dba862..03fa792137 100644
--- a/test_queue_job/models/test_models.py
+++ b/test_queue_job/models/test_models.py
@@ -40,7 +40,7 @@ class ModelTestQueueJob(models.Model):
# to test the context is serialized/deserialized properly
@api.model
def _job_prepare_context_before_enqueue_keys(self):
- return ("tz", "lang")
+ return ("tz", "lang", "allowed_company_ids")
def testing_method(self, *args, **kwargs):
"""Method used for tests
@@ -76,7 +76,7 @@ def job_with_retry_pattern__no_zero(self):
return
def mapped(self, func):
- return super(ModelTestQueueJob, self).mapped(func)
+ return super().mapped(func)
def job_alter_mutable(self, mutable_arg, mutable_kwarg=None):
mutable_arg.append(2)
diff --git a/test_queue_job/tests/__init__.py b/test_queue_job/tests/__init__.py
index dc59429e71..0405022ce0 100644
--- a/test_queue_job/tests/__init__.py
+++ b/test_queue_job/tests/__init__.py
@@ -4,5 +4,6 @@
from . import test_job
from . import test_job_auto_delay
from . import test_job_channels
+from . import test_job_function
from . import test_related_actions
from . import test_delay_mocks
diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py
index 1585f992f0..d7414ef7aa 100644
--- a/test_queue_job/tests/test_job.py
+++ b/test_queue_job/tests/test_job.py
@@ -15,6 +15,7 @@
RetryableJobError,
)
from odoo.addons.queue_job.job import (
+ CANCELLED,
DONE,
ENQUEUED,
FAILED,
@@ -88,7 +89,7 @@ def test_infinite_retryable_error(self):
self.assertEqual(test_job.retry, 1)
def test_on_instance_method(self):
- class A(object):
+ class A:
def method(self):
pass
@@ -185,6 +186,47 @@ def test_postpone(self):
self.assertEqual(job_a.result, "test")
self.assertFalse(job_a.exc_info)
+ def test_company_simple(self):
+ company = self.env.ref("base.main_company")
+ eta = datetime.now() + timedelta(hours=5)
+ test_job = Job(
+ self.env["test.queue.job"].with_company(company).testing_method,
+ args=("o", "k"),
+ kwargs={"return_context": 1},
+ priority=15,
+ eta=eta,
+ description="My description",
+ )
+ test_job.worker_pid = 99999 # normally set on "set_start"
+ test_job.store()
+ job_read = Job.load(self.env, test_job.uuid)
+ self.assertEqual(test_job.func.__func__, job_read.func.__func__)
+ result_ctx = job_read.func(*tuple(test_job.args), **test_job.kwargs)
+ self.assertEqual(result_ctx.get("allowed_company_ids"), company.ids)
+
+ def test_company_complex(self):
+ company1 = self.env.ref("base.main_company")
+ company2 = company1.create({"name": "Queue job company"})
+ companies = company1 | company2
+ self.env.user.write({"company_ids": [(6, False, companies.ids)]})
+ # Ensure the main company still the first
+ self.assertEqual(self.env.user.company_id, company1)
+ eta = datetime.now() + timedelta(hours=5)
+ test_job = Job(
+ self.env["test.queue.job"].with_company(company2).testing_method,
+ args=("o", "k"),
+ kwargs={"return_context": 1},
+ priority=15,
+ eta=eta,
+ description="My description",
+ )
+ test_job.worker_pid = 99999 # normally set on "set_start"
+ test_job.store()
+ job_read = Job.load(self.env, test_job.uuid)
+ self.assertEqual(test_job.func.__func__, job_read.func.__func__)
+ result_ctx = job_read.func(*tuple(test_job.args), **test_job.kwargs)
+ self.assertEqual(result_ctx.get("allowed_company_ids"), company2.ids)
+
def test_store(self):
test_job = Job(self.method)
test_job.store()
@@ -489,6 +531,42 @@ def test_button_done(self):
stored.result, "Manually set to done by %s" % self.env.user.name
)
+ def test_button_done_enqueue_waiting_dependencies(self):
+ job_root = Job(self.env["test.queue.job"].testing_method)
+ job_child = Job(self.env["test.queue.job"].testing_method)
+ job_child.add_depends({job_root})
+
+ DelayableGraph._ensure_same_graph_uuid([job_root, job_child])
+ job_root.store()
+ job_child.store()
+
+ self.assertEqual(job_child.state, WAIT_DEPENDENCIES)
+ record_root = job_root.db_record()
+ record_child = job_child.db_record()
+ # Trigger button done
+ record_root.button_done()
+ # Check the state
+ self.assertEqual(record_root.state, DONE)
+ self.assertEqual(record_child.state, PENDING)
+
+ def test_button_cancel_dependencies(self):
+ job_root = Job(self.env["test.queue.job"].testing_method)
+ job_child = Job(self.env["test.queue.job"].testing_method)
+ job_child.add_depends({job_root})
+
+ DelayableGraph._ensure_same_graph_uuid([job_root, job_child])
+ job_root.store()
+ job_child.store()
+
+ self.assertEqual(job_child.state, WAIT_DEPENDENCIES)
+ record_root = job_root.db_record()
+ record_child = job_child.db_record()
+ # Trigger button cancelled
+ record_root.button_cancelled()
+ # Check the state
+ self.assertEqual(record_root.state, CANCELLED)
+ self.assertEqual(record_child.state, CANCELLED)
+
def test_requeue(self):
stored = self._create_job()
stored.write({"state": "failed"})
@@ -572,7 +650,7 @@ class TestJobStorageMultiCompany(common.TransactionCase):
"""Test storage of jobs"""
def setUp(self):
- super(TestJobStorageMultiCompany, self).setUp()
+ super().setUp()
self.queue_job = self.env["queue.job"]
grp_queue_job_manager = self.ref("queue_job.group_queue_job_manager")
User = self.env["res.users"]
diff --git a/test_queue_job/tests/test_job_function.py b/test_queue_job/tests/test_job_function.py
new file mode 100644
index 0000000000..320b4973c5
--- /dev/null
+++ b/test_queue_job/tests/test_job_function.py
@@ -0,0 +1,35 @@
+import odoo.tests.common as common
+from odoo import exceptions
+
+
+class TestJobFunction(common.TransactionCase):
+ def setUp(self):
+ super().setUp()
+ self.test_function_model = self.env.ref(
+ "queue_job.job_function_queue_job__test_job"
+ )
+
+ def test_check_retry_pattern_randomized_case(self):
+ randomized_pattern = "{1: (10, 20), 2: (20, 40)}"
+ self.test_function_model.edit_retry_pattern = randomized_pattern
+ self.assertEqual(
+ self.test_function_model.edit_retry_pattern, randomized_pattern
+ )
+
+ def test_check_retry_pattern_fixed_case(self):
+ fixed_pattern = "{1: 10, 2: 20}"
+ self.test_function_model.edit_retry_pattern = fixed_pattern
+ self.assertEqual(self.test_function_model.edit_retry_pattern, fixed_pattern)
+
+ def test_check_retry_pattern_invalid_cases(self):
+ invalid_time_value_pattern = "{1: a, 2: 20}"
+ with self.assertRaises(exceptions.UserError):
+ self.test_function_model.edit_retry_pattern = invalid_time_value_pattern
+
+ invalid_retry_count_pattern = "{a: 10, 2: 20}"
+ with self.assertRaises(exceptions.UserError):
+ self.test_function_model.edit_retry_pattern = invalid_retry_count_pattern
+
+ invalid_randomized_pattern = "{1: (1, 2, 3), 2: 20}"
+ with self.assertRaises(exceptions.UserError):
+ self.test_function_model.edit_retry_pattern = invalid_randomized_pattern