From c761ca2756710f846e6752e3e66276f9087ea363 Mon Sep 17 00:00:00 2001 From: KKamaa Date: Mon, 18 Apr 2022 22:11:52 +0300 Subject: [PATCH 1/5] [FIX] keep context --- .../models/base_import_import.py | 12 +++- queue_job/fields.py | 8 +++ queue_job/job.py | 59 ++++++++++++++++--- queue_job/models/base.py | 9 ++- queue_job/models/queue_job.py | 12 ++++ queue_job/tests/__init__.py | 1 + queue_job/tests/test_json_field.py | 1 + queue_job/views/queue_job_views.xml | 4 ++ .../tests/test_queue_job_batch.py | 2 +- 9 files changed, 95 insertions(+), 13 deletions(-) diff --git a/base_import_async/models/base_import_import.py b/base_import_async/models/base_import_import.py index ec35468433..5761aa3442 100644 --- a/base_import_async/models/base_import_import.py +++ b/base_import_async/models/base_import_import.py @@ -65,7 +65,10 @@ def do(self, fields, options, dryrun=False): (translated_model_name, self.file_name) attachment = self._create_csv_attachment( import_fields, data, options, self.file_name) - delayed_job = self.with_delay(description=description)._split_file( + delayed_job = self.with_delay( + description=description, + keep_context=True + )._split_file( model_name=self.res_model, translated_model_name=translated_model_name, attachment=attachment, @@ -166,8 +169,11 @@ def _split_file(self, model_name, translated_model_name, fields, data[row_from:row_to + 1], options, file_name=root + '-' + chunk + ext) delayed_job = self.with_context( - job_batch=batch).with_delay( - description=description, priority=priority)._import_one_chunk( + job_batch=batch.id).with_delay( + description=description, + priority=priority, + keep_context=True + )._import_one_chunk( model_name=model_name, attachment=attachment, options=options) diff --git a/queue_job/fields.py b/queue_job/fields.py index b1990cee57..7e81301d07 100644 --- a/queue_job/fields.py +++ b/queue_job/fields.py @@ -28,12 +28,18 @@ def convert_to_cache(self, value, record, validate=True): class JobEncoder(json.JSONEncoder): """Encode Odoo recordsets so that we can later recompose them""" + def _get_record_context(self, obj): + context = obj.env.context.copy() + return context + def default(self, obj): if isinstance(obj, models.BaseModel): + context = self._get_record_context(obj) return {'_type': 'odoo_recordset', 'model': obj._name, 'ids': obj.ids, 'uid': obj.env.uid, + 'context': context, } elif isinstance(obj, datetime): return {'_type': 'datetime_isoformat', @@ -61,6 +67,8 @@ def object_hook(self, obj): type_ = obj['_type'] if type_ == 'odoo_recordset': model = self.env[obj['model']] + if obj.get("context"): + model = model.with_context(**obj.get("context")) if obj.get('uid'): model = model.sudo(obj['uid']) return model.browse(obj['ids']) diff --git a/queue_job/job.py b/queue_job/job.py index 4463968b48..0380f65ec8 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -5,11 +5,14 @@ import functools import hashlib import logging +import json +import yaml import uuid import sys from datetime import datetime, timedelta import odoo +from odoo.tools.safe_eval import safe_eval from .exception import (NoSuchJobError, FailedJobError, @@ -55,7 +58,7 @@ class DelayableRecordset(object): def __init__(self, recordset, priority=None, eta=None, max_retries=None, description=None, channel=None, - identity_key=None): + identity_key=None, keep_context=False): self.recordset = recordset self.priority = priority self.eta = eta @@ -63,6 +66,7 @@ def __init__(self, recordset, priority=None, eta=None, 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: @@ -87,7 +91,9 @@ def delay(*args, **kwargs): eta=self.eta, description=self.description, channel=self.channel, - identity_key=self.identity_key) + identity_key=self.identity_key, + keep_context=self.keep_context + ) return delay def __str__(self): @@ -297,6 +303,7 @@ def _load_from_db_record(cls, job_db_record): if stored.company_id: job_.company_id = stored.company_id.id job_.identity_key = stored.identity_key + job_.keep_context = stored.context or {} return job_ def job_record_with_same_identity_key(self): @@ -311,7 +318,7 @@ def job_record_with_same_identity_key(self): @classmethod def enqueue(cls, func, args=None, kwargs=None, priority=None, eta=None, max_retries=None, description=None, - channel=None, identity_key=None): + channel=None, identity_key=None, keep_context=None): """Create a Job and enqueue it in the queue. Return the job uuid. This expects the arguments specific to the job to be already extracted @@ -324,7 +331,8 @@ def enqueue(cls, func, args=None, kwargs=None, new_job = cls(func=func, args=args, kwargs=kwargs, priority=priority, eta=eta, max_retries=max_retries, description=description, - channel=channel, identity_key=identity_key) + channel=channel, identity_key=identity_key, + keep_context=keep_context) if new_job.identity_key: existing = new_job.job_record_with_same_identity_key() if existing: @@ -355,7 +363,8 @@ def db_record_from_uuid(env, job_uuid): def __init__(self, func, args=None, kwargs=None, priority=None, eta=None, job_uuid=None, max_retries=None, - description=None, channel=None, identity_key=None): + description=None, channel=None, + identity_key=None, keep_context=False): """ Create a Job :param func: function to execute @@ -381,6 +390,8 @@ def __init__(self, func, 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 or list """ if args is None: args = () @@ -397,6 +408,7 @@ def __init__(self, func, recordset = func.__self__ env = recordset.env + self.keep_context = keep_context self.model_name = recordset._name self.method_name = func.__name__ self.recordset = recordset @@ -500,6 +512,10 @@ def store(self): } dt_to_string = odoo.fields.Datetime.to_string + context = {} + if self.keep_context: + context = self.env.context.copy() + vals.update({"context": json.dumps(context)}) if self.date_enqueued: vals['date_enqueued'] = dt_to_string(self.date_enqueued) if self.date_started: @@ -516,6 +532,9 @@ def store(self): db_record.write(vals) else: date_created = dt_to_string(self.date_created) + # We store the original context used at import on create + ctx = self.env.context.copy() or '{}' + vals.update({'original_context': json.dumps(ctx) or ''}) # The following values must never be modified after the # creation of the job vals.update({'uuid': self.uuid, @@ -532,14 +551,40 @@ def store(self): if self.channel: vals.update({'channel': self.channel}) - self.env[self.job_model_name].sudo().create(vals) + job = self.env[self.job_model_name].sudo().create(vals) def db_record(self): return self.db_record_from_uuid(self.env, self.uuid) + def _get_abs_context(self, original_ctx, ctx): + try: + import_ctx = json.loads(original_ctx) + current_ctx = json.loads(ctx) + except Exception as e: + _logger.error("\n\nERROR CONTEXT JSON CONVERSION: %s\n\n" % e) + return self.env.context.copy() + else: + if isinstance(import_ctx, dict) and isinstance(current_ctx, dict): + import_ctx.update(current_ctx) + return import_ctx + return self.env.context.copy() + + def _get_record_context(self): + """ + Get the context to execute the job + """ + ctx = self._get_abs_context(self.db_record().original_context, + self.db_record().context) + if self.company_id: + ctx.update({'allowed_company_ids': [self.company_id]}) + if self.uuid: + ctx.update({"job_uuid": self.uuid}) + return ctx + @property def func(self): - recordset = self.recordset.with_context(job_uuid=self.uuid) + context = self._get_record_context() + recordset = self.recordset.with_context(**context) recordset = recordset.sudo(self.user_id) return getattr(recordset, self.method_name) diff --git a/queue_job/models/base.py b/queue_job/models/base.py index 82ae657c08..992c4fa1a8 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -34,7 +34,7 @@ def _register_hook(self): @api.multi def with_delay(self, priority=None, eta=None, max_retries=None, description=None, - channel=None, identity_key=None): + channel=None, identity_key=None, keep_context=False): """ Return a ``DelayableRecordset`` The returned instance allow to enqueue any method of the recordset's @@ -62,6 +62,9 @@ def with_delay(self, priority=None, eta=None, :param identity_key: key uniquely identifying the job, if specified and a job with the same key has not yet been run, the new job will not be added. + :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` @@ -90,4 +93,6 @@ def with_delay(self, priority=None, eta=None, max_retries=max_retries, description=description, channel=channel, - identity_key=identity_key) + identity_key=identity_key, + keep_context=keep_context + ) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 6e2726380c..3ab8254bb6 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -44,6 +44,18 @@ class QueueJob(models.Model): company_id = fields.Many2one(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, + ) + original_context = fields.Char( + string="Original Context Value", + default="{}", + help="This is the context dictionary that was used on import" + ) model_name = fields.Char(string='Model', readonly=True) method_name = fields.Char(readonly=True) diff --git a/queue_job/tests/__init__.py b/queue_job/tests/__init__.py index 75f3a5536c..e98a75e068 100644 --- a/queue_job/tests/__init__.py +++ b/queue_job/tests/__init__.py @@ -2,3 +2,4 @@ from . import test_runner_runner from . import test_json_field from . import test_model_job_channel + diff --git a/queue_job/tests/test_json_field.py b/queue_job/tests/test_json_field.py index 6c5e8f1e36..f76041e04c 100644 --- a/queue_job/tests/test_json_field.py +++ b/queue_job/tests/test_json_field.py @@ -23,6 +23,7 @@ def test_encoder_recordset(self): "_type": "odoo_recordset", "model": "res.partner", "ids": [partner.id], + "context": {}, }] self.assertEqual(json.loads(value_json), expected) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 7067f98933..c4f3f2c113 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -53,6 +53,10 @@ + + + +