From 77c91bede21f156d525f0796981d5963df2e9247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Mon, 2 Aug 2021 16:12:16 +0200 Subject: [PATCH 1/4] [13.0][IMP] queue_job current company Use the current company to trigger the job (+ add related tests) --- queue_job/job.py | 10 ++++++- test_queue_job/tests/test_job.py | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/queue_job/job.py b/queue_job/job.py index 349a73c8ce..49d69c7b80 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -628,7 +628,15 @@ def db_record(self): @property def func(self): - recordset = self.recordset.with_context(job_uuid=self.uuid) + user = self.env["res.users"].browse(self.user_id) + company_ids = user.company_ids.ids + # Insert the current company in top position + company_ids.insert(0, self.company_id) + # Remove duplicates but keep order + company_ids = list(dict.fromkeys(company_ids)) + recordset = self.recordset.with_context( + job_uuid=self.uuid, allowed_company_ids=company_ids + ) return getattr(recordset, self.method_name) @property diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index e0224ebf3d..7c4bc6f4a7 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -200,6 +200,51 @@ def test_store_extra_data(self): stored.invalidate_cache() self.assertEqual(stored.additional_info, "JUST_TESTING_BUT_FAILED") + def test_company_simple(self): + company = self.env.ref("base.main_company") + eta = datetime.now() + timedelta(hours=5) + test_job = Job( + self.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.company_id = company.id + test_job.store() + job_read = Job.load(self.env, test_job.uuid) + self.assertEqual(test_job.func, job_read.func) + result_ctx = test_job.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.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.company_id = company2.id + test_job.store() + job_read = Job.load(self.env, test_job.uuid) + self.assertEqual(test_job.func, job_read.func) + result_ctx = test_job.func(*tuple(test_job.args), **test_job.kwargs) + self.assertEqual( + result_ctx.get("allowed_company_ids"), [company2.id, company1.id] + ) + def test_read(self): eta = datetime.now() + timedelta(hours=5) test_job = Job( From b4bb7acd5a62d8ad4af2917261b5971c718d7752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 18 Aug 2021 09:22:17 +0200 Subject: [PATCH 2/4] [13.0][FIX] queu_job: allowed_company_ids. Fill allowed_company_ids from context with the job's company instead of every allowed companies of the user. Because most of the time, a job is related to only one company. And adding every allowed companies of the user into the context may load some unexpected records (during search for example). Because standards ir.rule use ['|',('company_id','=',False),('company_id', 'in', company_ids)] and this 'company_ids' is filled with every allowed companies from the context. --- queue_job/job.py | 13 +++++++------ test_queue_job/tests/test_job.py | 4 +--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/queue_job/job.py b/queue_job/job.py index 49d69c7b80..0486a20ae8 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -628,12 +628,13 @@ def db_record(self): @property def func(self): - user = self.env["res.users"].browse(self.user_id) - company_ids = user.company_ids.ids - # Insert the current company in top position - company_ids.insert(0, self.company_id) - # Remove duplicates but keep order - company_ids = list(dict.fromkeys(company_ids)) + # We can fill only one company into allowed_company_ids. + # Because if you have many, you can have unexpected records due to ir.rule. + # ir.rule use allowed_company_ids to load every records in many companies. + # But most of the time, a job should be executed on a single company. + company_ids = [] + if self.company_id: + company_ids = [self.company_id] recordset = self.recordset.with_context( job_uuid=self.uuid, allowed_company_ids=company_ids ) diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 7c4bc6f4a7..9b161789b5 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -241,9 +241,7 @@ def test_company_complex(self): job_read = Job.load(self.env, test_job.uuid) self.assertEqual(test_job.func, job_read.func) result_ctx = test_job.func(*tuple(test_job.args), **test_job.kwargs) - self.assertEqual( - result_ctx.get("allowed_company_ids"), [company2.id, company1.id] - ) + self.assertEqual(result_ctx.get("allowed_company_ids"), company2.ids) def test_read(self): eta = datetime.now() + timedelta(hours=5) From b935f55c4912da4da3ac8930f222e5ac8b0b041e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 11 Feb 2022 11:12:52 +0100 Subject: [PATCH 3/4] [13.0][IMP] queue_job: use context to execute the job (optional) --- queue_job/job.py | 43 ++++++++++++++++++------- queue_job/models/base.py | 4 +++ queue_job/models/queue_job.py | 6 ++++ queue_job/views/queue_job_views.xml | 3 ++ test_queue_job/tests/test_job.py | 50 +++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 12 deletions(-) diff --git a/queue_job/job.py b/queue_job/job.py index 0486a20ae8..f1be0348a1 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -11,6 +11,7 @@ from datetime import datetime, timedelta import odoo +from odoo.tools.safe_eval import safe_eval from .exception import FailedJobError, NoSuchJobError, RetryableJobError @@ -59,6 +60,7 @@ def __init__( description=None, channel=None, identity_key=None, + keep_context=False, ): self.recordset = recordset self.priority = priority @@ -67,6 +69,7 @@ def __init__( self.description = description self.channel = channel self.identity_key = identity_key + self.keep_context = keep_context def __getattr__(self, name): if name in self.recordset: @@ -88,6 +91,7 @@ def delay(*args, **kwargs): description=self.description, channel=self.channel, identity_key=self.identity_key, + keep_context=self.keep_context, ) return delay @@ -339,6 +343,7 @@ def enqueue( description=None, channel=None, identity_key=None, + keep_context=False, ): """Create a Job and enqueue it in the queue. Return the job uuid. @@ -359,6 +364,7 @@ def enqueue( description=description, channel=channel, identity_key=identity_key, + keep_context=keep_context, ) if new_job.identity_key: existing = new_job.job_record_with_same_identity_key() @@ -399,6 +405,7 @@ def __init__( description=None, channel=None, identity_key=None, + keep_context=False, ): """ Create a Job @@ -423,8 +430,8 @@ def __init__( :param identity_key: A hash to uniquely identify a job, or a function that returns this hash (the function takes the job as argument) - :param env: Odoo Environment - :type env: :class:`odoo.api.Environment` + :param keep_context: Determine if the current context should be restored + :type keep_context: :bool """ if args is None: args = () @@ -445,6 +452,7 @@ def __init__( self.recordset = recordset self.env = env + self.keep_context = keep_context self.job_model = self.env["queue.job"] self.job_model_name = "queue.job" @@ -594,8 +602,11 @@ def _store_values(self, create=False): "records": self.recordset, "args": self.args, "kwargs": self.kwargs, + "context": "{}", } ) + if self.keep_context: + vals.update({"context": str(self.env.context.copy())}) vals_from_model = self._store_values_from_model() # Sanitize values: make sure you cannot screw core values @@ -615,6 +626,22 @@ def _store_values_from_model(self): vals = handler(self) return vals + def _get_record_context(self): + """ + Get the context to execute the job + """ + # return {} + company_ids = [] + if self.company_id: + company_ids = [self.company_id] + context_txt = self.db_record().context or {} + if isinstance(context_txt, str): + context = safe_eval(context_txt) + else: + context = context_txt + context.update({"job_uuid": self.uuid, "allowed_company_ids": company_ids}) + return context + @property def func_string(self): model = repr(self.recordset) @@ -628,16 +655,8 @@ def db_record(self): @property def func(self): - # We can fill only one company into allowed_company_ids. - # Because if you have many, you can have unexpected records due to ir.rule. - # ir.rule use allowed_company_ids to load every records in many companies. - # But most of the time, a job should be executed on a single company. - company_ids = [] - if self.company_id: - company_ids = [self.company_id] - recordset = self.recordset.with_context( - job_uuid=self.uuid, allowed_company_ids=company_ids - ) + context = self._get_record_context() + recordset = self.recordset.with_context(**context) return getattr(recordset, self.method_name) @property diff --git a/queue_job/models/base.py b/queue_job/models/base.py index a83f457900..0b5f4fe82e 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -44,6 +44,7 @@ def with_delay( description=None, channel=None, identity_key=None, + keep_context=False, ): """ Return a ``DelayableRecordset`` @@ -81,6 +82,8 @@ def with_delay( the new job will not be added. It is either a string, either a function that takes the job as argument (see :py:func:`..job.identity_exact`). + :param keep_context: boolean to set if the current context + should be restored on the recordset (default: False). :return: instance of a DelayableRecordset :rtype: :class:`odoo.addons.queue_job.job.DelayableRecordset` @@ -108,6 +111,7 @@ def with_delay( description=description, channel=channel, identity_key=identity_key, + keep_context=keep_context, ) def _patch_job_auto_delay(self, method_name, context_key=None): diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 40322795c9..d330106d8a 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -57,6 +57,12 @@ class QueueJob(models.Model): comodel_name="res.company", string="Company", index=True ) name = fields.Char(string="Description", readonly=True) + context = fields.Char( + string="Context Value", + default="{}", + help="Context dictionary as Python expression, empty by default (Default: {})", + readonly=True, + ) model_name = fields.Char(string="Model", readonly=True) method_name = fields.Char(readonly=True) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index a3c7adaf8e..94418bd9e6 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -60,6 +60,9 @@ + + +