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
5 changes: 4 additions & 1 deletion .oca/oca-port/blacklist/queue_job.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"387": "Reverted later by https://github.com/OCA/queue/pull/466",
"413": "No reason to port repo template sync",
"440": "Already ported",
"466": "Reverts https://github.com/OCA/queue/pull/387"
"466": "Reverts https://github.com/OCA/queue/pull/387",
"511": "Icon already updated for v15",
"443": "Squashed w/ 453, commit rewritten",
"453": "Squashed w/ 443, commit rewritten"
}
}
2 changes: 1 addition & 1 deletion queue_job/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"website": "https://github.com/OCA/queue",
"license": "LGPL-3",
"category": "Generic Modules",
"depends": ["mail"],
"depends": ["mail", "base_sparse_field"],
"external_dependencies": {"python": ["requests"]},
"data": [
"security/security.xml",
Expand Down
1 change: 1 addition & 0 deletions queue_job/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def retry_postpone(job, message, seconds=None):
# traceback in the logs we should have the traceback when all
# retries are exhausted
env.cr.rollback()
return ""

except (FailedJobError, Exception) as orig_exception:
buff = StringIO()
Expand Down
4 changes: 4 additions & 0 deletions queue_job/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ def convert_to_record(self, value, record):
class JobEncoder(json.JSONEncoder):
"""Encode Odoo recordsets so that we can later recompose them"""

def _get_record_context(self, obj):
return obj._job_prepare_context_before_enqueue()

def default(self, obj):
if isinstance(obj, models.BaseModel):
return {
Expand All @@ -77,6 +80,7 @@ def default(self, obj):
"ids": obj.ids,
"uid": obj.env.uid,
"su": obj.env.su,
"context": self._get_record_context(obj),
}
elif isinstance(obj, datetime):
return {"_type": "datetime_isoformat", "value": obj.isoformat()}
Expand Down
8 changes: 7 additions & 1 deletion queue_job/jobrunner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
or ``False`` if unset.
- ``ODOO_QUEUE_JOB_JOBRUNNER_DB_PORT=5432``, default ``db_port``
or ``False`` if unset.
- ``ODOO_QUEUE_JOB_JOBRUNNER_DB_USER=userdb``, default ``db_user``
or ``False`` if unset.
- ``ODOO_QUEUE_JOB_JOBRUNNER_DB_PASSWORD=passdb``, default ``db_password``
or ``False`` if unset.

* Alternatively, configure the channels through the Odoo configuration
file, like:
Expand All @@ -50,6 +54,8 @@
http_auth_password = s3cr3t
jobrunner_db_host = master-db
jobrunner_db_port = 5432
jobrunner_db_user = userdb
jobrunner_db_password = passdb

* Or, if using ``anybox.recipe.odoo``, add this to your buildout configuration:

Expand Down Expand Up @@ -187,7 +193,7 @@ def _odoo_now():
def _connection_info_for(db_name):
db_or_uri, connection_info = odoo.sql_db.connection_info_for(db_name)

for p in ("host", "port"):
for p in ("host", "port", "user", "password"):
cfg = os.environ.get(
"ODOO_QUEUE_JOB_JOBRUNNER_DB_%s" % p.upper()
) or queue_job_config.get("jobrunner_db_" + p)
Expand Down
19 changes: 19 additions & 0 deletions queue_job/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,22 @@ def _job_store_values(self, job):
:return: dictionary for setting job values.
"""
return {}

@api.model
def _job_prepare_context_before_enqueue_keys(self):
"""Keys to keep in context of stored jobs
Empty by default for backward compatibility.
"""
# TODO: when migrating to 16.0, active the base context keys:
# return ("tz", "lang", "allowed_company_ids", "force_company", "active_test")
return ()

def _job_prepare_context_before_enqueue(self):
"""Return the context to store in the jobs
Can be used to keep only safe keys.
"""
return {
key: value
for key, value in self.env.context.items()
if key in self._job_prepare_context_before_enqueue_keys()
}
5 changes: 3 additions & 2 deletions queue_job/models/queue_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,9 @@ def open_graph_jobs(self):
self.ensure_one()
jobs = self.env["queue.job"].search([("graph_uuid", "=", self.graph_uuid)])

action_jobs = self.env.ref("queue_job.action_queue_job")
action = action_jobs.read()[0]
action = self.env["ir.actions.act_window"]._for_xml_id(
"queue_job.action_queue_job"
)
action.update(
{
"name": _("Jobs for graph %s") % (self.graph_uuid),
Expand Down
24 changes: 24 additions & 0 deletions queue_job/readme/USAGE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,30 @@ Based on this configuration, we can tell that:
* retries 10 to 15 postponed 30 seconds later
* all subsequent retries postponed 5 minutes later

**Job Context**

The context of the recordset of the job, or any recordset passed in arguments of
a job, is transferred to the job according to an allow-list.

The default allow-list is empty for backward compatibility. The allow-list can
be customized in ``Base._job_prepare_context_before_enqueue_keys``.

Example:

.. code-block:: python

class Base(models.AbstractModel):

_inherit = "base"

@api.model
def _job_prepare_context_before_enqueue_keys(self):
"""Keys to keep in context of stored jobs

Empty by default for backward compatibility.
"""
return ("tz", "lang", "allowed_company_ids", "force_company", "active_test")

**Bypass jobs on running Odoo**

When you are developing (ie: connector modules) you might want
Expand Down
2 changes: 1 addition & 1 deletion queue_job/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Job Queue</title>
<style type="text/css">

Expand Down
20 changes: 16 additions & 4 deletions queue_job/tests/test_json_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
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")
context = demo_user.context_get()
partner = self.env(user=demo_user, context=context).ref("base.main_partner")
value = partner
value_json = json.dumps(value, cls=JobEncoder)
expected = {
Expand All @@ -25,12 +26,15 @@ def test_encoder_recordset(self):
"model": "res.partner",
"ids": [partner.id],
"su": False,
# no allowed context by default, must be changed in 16.0
"context": {},
}
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")
context = demo_user.context_get()
partner = self.env(user=demo_user, context=context).ref("base.main_partner")
value = ["a", 1, partner]
value_json = json.dumps(value, cls=JobEncoder)
expected = [
Expand All @@ -42,18 +46,23 @@ def test_encoder_recordset_list(self):
"model": "res.partner",
"ids": [partner.id],
"su": False,
# no allowed context by default, must be changed in 16.0
"context": {},
},
]
self.assertEqual(json.loads(value_json), expected)

def test_decoder_recordset(self):
demo_user = self.env.ref("base.user_demo")
context = demo_user.context_get()
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)
'"ids": [%s],"uid": %s, '
'"context": {"tz": "%s", "lang": "%s"}}'
% (partner.id, demo_user.id, context["tz"], context["lang"])
)
expected = partner
value = json.loads(value_json, cls=JobDecoder, env=self.env)
Expand All @@ -62,13 +71,16 @@ def test_decoder_recordset(self):

def test_decoder_recordset_list(self):
demo_user = self.env.ref("base.user_demo")
context = demo_user.context_get()
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)
'"ids": [%s],"uid": %s, '
'"context": {"tz": "%s", "lang": "%s"}}]'
% (partner.id, demo_user.id, context["tz"], context["lang"])
)
expected = ["a", 1, partner]
value = json.loads(value_json, cls=JobDecoder, env=self.env)
Expand Down
7 changes: 6 additions & 1 deletion test_queue_job/models/test_models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2016 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

from odoo import fields, models
from odoo import api, fields, models

from odoo.addons.queue_job.delay import chain
from odoo.addons.queue_job.exception import RetryableJobError
Expand Down Expand Up @@ -37,6 +37,11 @@ class ModelTestQueueJob(models.Model):

name = fields.Char()

# to test the context is serialized/deserialized properly
@api.model
def _job_prepare_context_before_enqueue_keys(self):
return ("tz", "lang")

def testing_method(self, *args, **kwargs):
"""Method used for tests

Expand Down
32 changes: 32 additions & 0 deletions test_queue_job/tests/test_json_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# copyright 2022 Guewen Baconnier
# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

import json

from odoo.tests import common

# pylint: disable=odoo-addons-relative-import
# we are testing, we want to test as if we were an external consumer of the API
from odoo.addons.queue_job.fields import JobEncoder


class TestJsonField(common.TransactionCase):

# TODO: when migrating to 16.0, adapt the checks in queue_job/tests/test_json_field.py
# to verify the context keys are encoded and remove these
def test_encoder_recordset_store_context(self):
demo_user = self.env.ref("base.user_demo")
user_context = {"lang": "en_US", "tz": "Europe/Brussels"}
test_model = self.env(user=demo_user, context=user_context)["test.queue.job"]
value_json = json.dumps(test_model, cls=JobEncoder)
self.assertEqual(json.loads(value_json)["context"], user_context)

def test_encoder_recordset_context_filter_keys(self):
demo_user = self.env.ref("base.user_demo")
user_context = {"lang": "en_US", "tz": "Europe/Brussels"}
tampered_context = dict(user_context, foo=object())
test_model = self.env(user=demo_user, context=tampered_context)[
"test.queue.job"
]
value_json = json.dumps(test_model, cls=JobEncoder)
self.assertEqual(json.loads(value_json)["context"], user_context)