Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion queue_job/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{
"name": "Job Queue",
"version": "13.0.3.1.0",
"version": "13.0.3.2.0",
"author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/queue/queue_job",
"license": "LGPL-3",
Expand Down
27 changes: 19 additions & 8 deletions queue_job/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,29 @@ class JobSerialized(fields.Field):

_slots = {"_base_type": type}

_default_json_mapping = {dict: "{}", list: "[]", tuple: "[]"}
# these are the default values when we convert an empty value
_default_json_mapping = {
dict: "{}",
list: "[]",
tuple: "[]",
models.BaseModel: lambda env: json.dumps(
{"_type": "odoo_recordset", "model": "base", "ids": [], "uid": env.uid}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we store record.context too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clearly opens this possibility, this is definitely where the context has to be stored. I mentioned it in the commit message :)
However, there are some questions about what should be allowed or not to store in the context (cf #121), also whether we should or not always store it (can take useless space?), so it should not be part of this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created #283

),
}

def __init__(self, string=fields.Default, base_type=fields.Default, **kwargs):
super().__init__(string=string, _base_type=base_type, **kwargs)

def _setup_attrs(self, model, name):
super()._setup_attrs(model, name)
if not self._base_type_default_json():
if self._base_type not in self._default_json_mapping:
raise ValueError("%s is not a supported base type" % (self._base_type))

def _base_type_default_json(self):
return self._default_json_mapping.get(self._base_type)
def _base_type_default_json(self, env):
default_json = self._default_json_mapping.get(self._base_type)
if not isinstance(default_json, str):
default_json = default_json(env)
return default_json

def convert_to_column(self, value, record, values=None, validate=True):
return self.convert_to_cache(value, record, validate=validate)
Expand All @@ -51,7 +62,7 @@ def convert_to_cache(self, value, record, validate=True):
return value or None

def convert_to_record(self, value, record):
default = self._base_type_default_json()
default = self._base_type_default_json(record.env)
return json.loads(value or default, cls=JobDecoder, env=record.env)


Expand All @@ -65,6 +76,7 @@ def default(self, obj):
"model": obj._name,
"ids": obj.ids,
"uid": obj.env.uid,
"su": obj.env.su,
}
elif isinstance(obj, datetime):
return {"_type": "datetime_isoformat", "value": obj.isoformat()}
Expand Down Expand Up @@ -94,9 +106,8 @@ def object_hook(self, obj):
return obj
type_ = obj["_type"]
if type_ == "odoo_recordset":
model = self.env[obj["model"]]
if obj.get("uid"):
model = model.with_user(obj["uid"])
model = self.env(user=obj.get("uid"), su=obj.get("su"))[obj["model"]]

return model.browse(obj["ids"])
elif type_ == "datetime_isoformat":
return dateutil.parser.parse(obj["value"])
Expand Down
4 changes: 2 additions & 2 deletions queue_job/i18n/queue_job.pot
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,8 @@ msgid "Queue jobs must created by calling 'with_delay()'."
msgstr ""

#. module: queue_job
#: model:ir.model.fields,field_description:queue_job.field_queue_job__record_ids
msgid "Record"
#: model:ir.model.fields,field_description:queue_job.field_queue_job__records
msgid "Record(s)"
msgstr ""

#. module: queue_job
Expand Down
24 changes: 11 additions & 13 deletions queue_job/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,15 +257,12 @@ def load(cls, env, job_uuid):
@classmethod
def _load_from_db_record(cls, job_db_record):
stored = job_db_record
env = job_db_record.env

args = stored.args
kwargs = stored.kwargs
method_name = stored.method_name

model = env[stored.model_name]

recordset = model.browse(stored.record_ids)
recordset = stored.records
method = getattr(recordset, method_name)

eta = None
Expand Down Expand Up @@ -299,8 +296,6 @@ def _load_from_db_record(cls, job_db_record):
job_.state = stored.state
job_.result = stored.result if stored.result else None
job_.exc_info = stored.exc_info if stored.exc_info else None
job_.user_id = stored.user_id.id if stored.user_id else None
job_.model_name = stored.model_name if stored.model_name else None
job_.retry = stored.retry
job_.max_retries = stored.max_retries
if stored.company_id:
Expand Down Expand Up @@ -438,7 +433,6 @@ def __init__(

recordset = func.__self__
env = recordset.env
self.model_name = recordset._name
self.method_name = func.__name__
self.recordset = recordset

Expand Down Expand Up @@ -492,7 +486,6 @@ def __init__(
self.result = None
self.exc_info = None

self.user_id = env.uid
if "company_id" in env.context:
company_id = env.context["company_id"]
else:
Expand Down Expand Up @@ -537,7 +530,6 @@ def store(self):
"retry": self.retry,
"max_retries": self.max_retries,
"exc_info": self.exc_info,
"user_id": self.user_id or self.env.uid,
"company_id": self.company_id,
"result": str(self.result) if self.result else False,
"date_enqueued": False,
Expand Down Expand Up @@ -576,9 +568,8 @@ def store(self):
"uuid": self.uuid,
"name": self.description,
"date_created": date_created,
"model_name": self.model_name,
"method_name": self.method_name,
"record_ids": self.recordset.ids,
"records": self.recordset,
"args": self.args,
"kwargs": self.kwargs,
}
Expand All @@ -596,7 +587,6 @@ def db_record(self):
@property
def func(self):
recordset = self.recordset.with_context(job_uuid=self.uuid)
recordset = recordset.with_user(self.user_id)
return getattr(recordset, self.method_name)

@property
Expand Down Expand Up @@ -633,6 +623,14 @@ def uuid(self):
self._uuid = str(uuid.uuid4())
return self._uuid

@property
def model_name(self):
return self.recordset._name

@property
def user_id(self):
return self.recordset.env.uid

@property
def eta(self):
return self._eta
Expand Down Expand Up @@ -909,7 +907,7 @@ class QueueJob(models.Model):
def related_action_partner(self):
self.ensure_one()
model = self.model_name
partner = self.env[model].browse(self.record_ids)
partner = self.records
# possibly get the real ID if partner_id is a binding ID
action = {
'name': _("Partner"),
Expand Down
28 changes: 28 additions & 0 deletions queue_job/migrations/13.0.3.2.0/pre-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

import logging

from odoo.tools.sql import column_exists

_logger = logging.getLogger(__name__)


def migrate(cr, version):
if not column_exists(cr, "queue_job", "records"):
cr.execute(
"""
ALTER TABLE queue_job
ADD COLUMN records text;
"""
)
cr.execute(
"""
UPDATE queue_job
SET records = '{"_type": "odoo_recordset"'
|| ', "model": "' || model_name || '"'
|| ', "uid": ' || user_id
|| ', "ids": ' || record_ids
|| '}'
WHERE records IS NULL;
"""
)
48 changes: 39 additions & 9 deletions queue_job/models/queue_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,34 @@ class QueueJob(models.Model):
"date_created",
"model_name",
"method_name",
"record_ids",
"records",
"args",
"kwargs",
)

uuid = fields.Char(string="UUID", readonly=True, index=True, required=True)
user_id = fields.Many2one(comodel_name="res.users", string="User ID", required=True)
user_id = fields.Many2one(
comodel_name="res.users",
string="User ID",
compute="_compute_user_id",
inverse="_inverse_user_id",
store=True,
)
company_id = fields.Many2one(
comodel_name="res.company", string="Company", index=True
)
name = fields.Char(string="Description", readonly=True)

model_name = fields.Char(string="Model", readonly=True)
model_name = fields.Char(
string="Model", compute="_compute_model_name", store=True, readonly=True
)
method_name = fields.Char(readonly=True)
record_ids = JobSerialized(readonly=True, base_type=list)
# record_ids field is only for backward compatibility (e.g. used in related
# actions), can be removed (replaced by "records") in 14.0
record_ids = JobSerialized(compute="_compute_record_ids", base_type=list)
records = JobSerialized(
string="Record(s)", readonly=True, base_type=models.BaseModel,
)
args = JobSerialized(readonly=True, base_type=tuple)
kwargs = JobSerialized(readonly=True, base_type=dict)
func_string = fields.Char(
Expand Down Expand Up @@ -113,6 +126,25 @@ def init(self):
"'enqueued') AND identity_key IS NOT NULL;"
)

@api.depends("records")
def _compute_user_id(self):
for record in self:
record.user_id = record.records.env.uid

def _inverse_user_id(self):
for record in self.with_context(_job_edit_sentinel=self.EDIT_SENTINEL):
record.records = record.records.with_user(record.user_id.id)

@api.depends("records")
def _compute_model_name(self):
for record in self:
record.model_name = record.records._name

@api.depends("records")
def _compute_record_ids(self):
for record in self:
record.record_ids = record.records.ids

def _inverse_channel(self):
for record in self:
record.override_channel = record.channel
Expand All @@ -137,11 +169,10 @@ def _compute_job_function(self):
record.channel_method_name = channel_method_name
record.job_function_id = function

@api.depends("model_name", "method_name", "record_ids", "args", "kwargs")
@api.depends("model_name", "method_name", "records", "args", "kwargs")
def _compute_func_string(self):
for record in self:
record_ids = record.record_ids
model = repr(self.env[record.model_name].browse(record_ids))
model = repr(record.records)
args = [repr(arg) for arg in record.args]
kwargs = ["{}={!r}".format(key, val) for key, val in record.kwargs.items()]
all_args = ", ".join(args + kwargs)
Expand Down Expand Up @@ -327,8 +358,7 @@ def related_action_open_record(self):

"""
self.ensure_one()
model_name = self.model_name
records = self.env[model_name].browse(self.record_ids).exists()
records = self.records.exists()
if not records:
return None
action = {
Expand Down
2 changes: 1 addition & 1 deletion queue_job/readme/USAGE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Example of related action code:
def related_action_partner(self, name):
self.ensure_one()
model = self.model_name
partner = self.env[model].browse(self.record_ids)
partner = self.records
action = {
'name': name,
'type': 'ir.actions.act_window',
Expand Down
32 changes: 31 additions & 1 deletion queue_job/tests/test_json_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@

class TestJson(common.TransactionCase):
def test_encoder_recordset(self):
demo_user = self.env.ref("base.user_demo")
partner = self.env(user=demo_user).ref("base.main_partner")
value = partner
value_json = json.dumps(value, cls=JobEncoder)
expected = {
"uid": demo_user.id,
"_type": "odoo_recordset",
"model": "res.partner",
"ids": [partner.id],
"su": False,
}
self.assertEqual(json.loads(value_json), expected)

def test_encoder_recordset_list(self):
demo_user = self.env.ref("base.user_demo")
partner = self.env(user=demo_user).ref("base.main_partner")
value = ["a", 1, partner]
Expand All @@ -27,25 +41,41 @@ def test_encoder_recordset(self):
"_type": "odoo_recordset",
"model": "res.partner",
"ids": [partner.id],
"su": False,
},
]
self.assertEqual(json.loads(value_json), expected)

def test_decoder_recordset(self):
demo_user = self.env.ref("base.user_demo")
partner = self.env(user=demo_user).ref("base.main_partner")
value_json = (
'{"_type": "odoo_recordset",'
'"model": "res.partner",'
'"su": false,'
'"ids": [%s],"uid": %s}' % (partner.id, demo_user.id)
)
expected = partner
value = json.loads(value_json, cls=JobDecoder, env=self.env)
self.assertEqual(value, expected)
self.assertEqual(demo_user, expected.env.user)

def test_decoder_recordset_list(self):
demo_user = self.env.ref("base.user_demo")
partner = self.env(user=demo_user).ref("base.main_partner")
value_json = (
'["a", 1, '
'{"_type": "odoo_recordset",'
'"model": "res.partner",'
'"su": false,'
'"ids": [%s],"uid": %s}]' % (partner.id, demo_user.id)
)
expected = ["a", 1, partner]
value = json.loads(value_json, cls=JobDecoder, env=self.env)
self.assertEqual(value, expected)
self.assertEqual(demo_user, expected[2].env.user)

def test_decoder_recordset_without_user(self):
def test_decoder_recordset_list_without_user(self):
value_json = (
'["a", 1, {"_type": "odoo_recordset",' '"model": "res.users", "ids": [1]}]'
)
Expand Down
Loading