Skip to content
1 change: 1 addition & 0 deletions .pylintrc-mandatory
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enable=anomalous-backslash-in-string,
class-camelcase,
dangerous-default-value,
dangerous-view-replace-wo-priority,
development-status-allowed,
duplicate-id-csv,
duplicate-key,
duplicate-xml-fields,
Expand Down
4 changes: 2 additions & 2 deletions base_import_async/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"website": "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/OCA/queue",
"category": "Generic Modules",
"depends": ["base_import", "queue_job"],
"data": ["views/base_import_async.xml"],
"data": ["data/queue_job_function_data.xml", "views/base_import_async.xml"],
"qweb": ["static/src/xml/import.xml"],
"installable": True,
"development_status": "Stable",
"development_status": "Production/Stable",
}
21 changes: 21 additions & 0 deletions base_import_async/data/queue_job_function_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<odoo noupdate="1">
<record id="job_function_base_import_import_split_file" model="queue.job.function">
<field name="model_id" ref="base_import.model_base_import_import" />
<field name="method">_split_file</field>
<field
name="related_action"
eval='{"func_name": "_related_action_attachment"}'
/>
</record>
<record
id="job_function_base_import_import_import_one_chunk"
model="queue.job.function"
>
<field name="model_id" ref="base_import.model_base_import_import" />
<field name="method">_import_one_chunk</field>
<field
Comment thread
simahawk marked this conversation as resolved.
name="related_action"
eval='{"func_name": "_related_action_attachment"}'
/>
</record>
</odoo>
5 changes: 0 additions & 5 deletions base_import_async/models/base_import_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from odoo.models import fix_import_export_id_paths

from odoo.addons.queue_job.exception import FailedJobError
from odoo.addons.queue_job.job import job, related_action

# options defined in base_import/import.js
OPT_HAS_HEADER = "headers"
Expand Down Expand Up @@ -124,8 +123,6 @@ def _extract_chunks(model_obj, fields, data, chunk_size):
if row_from < len(data):
yield row_from, len(data) - 1

@job
@related_action("_related_action_attachment")
def _split_file(
self,
model_name,
Expand Down Expand Up @@ -172,8 +169,6 @@ def _split_file(
self._link_attachment_to_job(delayed_job, attachment)
priority += 1

@job
@related_action("_related_action_attachment")
def _import_one_chunk(self, model_name, attachment, options):
model_obj = self.env[model_name]
fields, data = self._read_csv_attachment(attachment, options)
Expand Down
3 changes: 2 additions & 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.1.7.0",
"version": "13.0.2.0.0",
"author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "/OCA/queue/queue_job",
"license": "LGPL-3",
Expand All @@ -15,6 +15,7 @@
"security/ir.model.access.csv",
"views/queue_job_views.xml",
"data/queue_data.xml",
"data/queue_job_function_data.xml",
],
"installable": True,
"development_status": "Mature",
Expand Down
6 changes: 6 additions & 0 deletions queue_job/data/queue_job_function_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<odoo noupdate="1">
<record id="job_function_queue_job__test_job" model="queue.job.function">
<field name="model_id" ref="queue_job.model_queue_job" />
<field name="method">_test_job</field>
</record>
</odoo>
100 changes: 84 additions & 16 deletions queue_job/job.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2013-2016 Camptocamp
# Copyright 2013-2020 Camptocamp
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

import functools
Expand Down Expand Up @@ -42,9 +42,6 @@ class DelayableRecordset(object):
delayable = DelayableRecordset(recordset, priority=20)
delayable.method(args, kwargs)

``method`` must be a method of the recordset's Model, decorated with
:func:`~odoo.addons.queue_job.job.job`.

The method call will be processed asynchronously in the job queue, with
the passed arguments.

Expand Down Expand Up @@ -78,12 +75,6 @@ def __getattr__(self, name):
)
)
recordset_method = getattr(self.recordset, name)
if not getattr(recordset_method, "delayable", None):
raise AttributeError(
"method %s on %s is not allowed to be delayed, "
"it should be decorated with odoo.addons.queue_job.job.job"
% (name, self.recordset)
)

def delay(*args, **kwargs):
return Job.enqueue(
Expand Down Expand Up @@ -453,6 +444,16 @@ def __init__(
self.job_model = self.env["queue.job"]
self.job_model_name = "queue.job"

self.job_config = (
self.env["queue.job.function"]
.sudo()
.job_config(
self.env["queue.job.function"].job_function_name(
self.model_name, self.method_name
)
)
)

self.state = PENDING

self.retry = 0
Expand Down Expand Up @@ -554,9 +555,14 @@ def store(self):
if self.identity_key:
vals["identity_key"] = self.identity_key

job_model = self.env["queue.job"]
# The sentinel is used to prevent edition sensitive fields (such as
# method_name) from RPC methods.
edit_sentinel = job_model.EDIT_SENTINEL

db_record = self.db_record()
if db_record:
db_record.write(vals)
db_record.with_context(_job_edit_sentinel=edit_sentinel).write(vals)
else:
date_created = self.date_created
# The following values must never be modified after the
Expand All @@ -578,7 +584,7 @@ def store(self):
if self.channel:
vals.update({"channel": self.channel})

self.env[self.job_model_name].sudo().create(vals)
job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create(vals)

def db_record(self):
return self.db_record_from_uuid(self.env, self.uuid)
Expand Down Expand Up @@ -672,7 +678,10 @@ def __repr__(self):
return "<Job %s, priority:%d>" % (self.uuid, self.priority)

def _get_retry_seconds(self, seconds=None):
retry_pattern = self.func.retry_pattern
retry_pattern = self.job_config.retry_pattern
if not retry_pattern:
# TODO deprecated by :job-no-decorator:
retry_pattern = getattr(self.func, "retry_pattern", None)
if not seconds and retry_pattern:
# ordered from higher to lower count of retries
patt = sorted(retry_pattern.items(), key=lambda t: t[0])
Expand Down Expand Up @@ -701,20 +710,29 @@ def postpone(self, result=None, seconds=None):

def related_action(self):
record = self.db_record()
if hasattr(self.func, "related_action"):
if not self.job_config.related_action_enable:
return None

funcname = self.job_config.related_action_func_name
if not funcname and hasattr(self.func, "related_action"):
# TODO deprecated by :job-no-decorator:
funcname = self.func.related_action
# decorator is set but empty: disable the default one
if not funcname:
return None
else:

if not funcname:
funcname = record._default_related_action
if not isinstance(funcname, str):
raise ValueError(
"related_action must be the name of the "
"method on queue.job as string"
)
action = getattr(record, funcname)
action_kwargs = getattr(self.func, "kwargs", {})
action_kwargs = self.job_config.related_action_kwargs
if not action_kwargs:
# TODO deprecated by :job-no-decorator:
action_kwargs = getattr(self.func, "kwargs", {})
return action(**action_kwargs)


Expand All @@ -724,9 +742,13 @@ def _is_model_method(func):
)


# TODO deprecated by :job-no-decorator:
def job(func=None, default_channel="root", retry_pattern=None):
"""Decorator for job methods.

Deprecated. Use ``queue.job.function`` XML records (details in
``readme/USAGE.rst``).

It enables the possibility to use a Model's method as a job function.

Optional argument:
Expand Down Expand Up @@ -810,6 +832,25 @@ def retryable_example():
job, default_channel=default_channel, retry_pattern=retry_pattern
)

xml_fields = [
' <field name="model_id" ref="[insert model xmlid]" />\n'
' <field name="method">_test_job</field>\n'
]
if default_channel:
xml_fields.append(' <field name="channel_id" ref="[insert channel xmlid]"/>')
if retry_pattern:
xml_fields.append(' <field name="retry_pattern">{retry_pattern}</field>')

xml_record = (
'<record id="job_function_[insert model]_{method}"'
' model="queue.job.function">\n' + "\n".join(xml_fields) + "\n</record>"
).format(**{"method": func.__name__, "retry_pattern": retry_pattern})
_logger.warning(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@guewen IMHO this is too much as it will break all runbot builds in repos that use queues. See https://runbot.odoo-community.org/runbot/build/3450323 for example.

Is it wanted ? Or maybe this can go into INFO.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You are right, I didn't realize runbot would not agree. We should move it to info, and move the XML snippet to debug because it is too verbose at the moment. I'll try to open a PR tomorrow.

"@job is deprecated and no longer needed, if you need custom options, "
"use an XML record:\n%s",
xml_record,
)

def delay_from_model(*args, **kwargs):
raise AttributeError(
"method.delay() can no longer be used, the general form is "
Expand All @@ -832,9 +873,13 @@ def delay_from_model(*args, **kwargs):
return func


# TODO deprecated by :job-no-decorator:
def related_action(action=None, **kwargs):
"""Attach a *Related Action* to a job (decorator)

Deprecated. Use ``queue.job.function`` XML records (details in
``readme/USAGE.rst``).

A *Related Action* will appear as a button on the Odoo view.
The button will execute the action, usually it will open the
form view of the record related to the job.
Expand Down Expand Up @@ -894,6 +939,29 @@ def export_product(self):
"""

def decorate(func):
related_action_dict = {
"func_name": action,
}
if kwargs:
related_action_dict["kwargs"] = kwargs

xml_fields = (
' <field name="model_id" ref="[insert model xmlid]" />\n'
' <field name="method">_test_job</field>\n'
' <field name="related_action">{related_action}</field>'
)

xml_record = (
'<record id="job_function_[insert model]_{method}"'
' model="queue.job.function">\n' + xml_fields + "\n</record>"
).format(**{"method": func.__name__, "related_action": action})
_logger.warning(
"@related_action is deprecated and no longer needed,"
" add these options in a 'queue.job.function'"
" XML record:\n%s",
xml_record,
)

func.related_action = action
func.kwargs = kwargs
return func
Expand Down
23 changes: 23 additions & 0 deletions queue_job/migrations/13.0.2.0.0/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

import logging

from odoo import SUPERUSER_ID, api, exceptions

_logger = logging.getLogger(__name__)


def migrate(cr, version):
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
for job_func in env["queue.job.function"].search([]):
try:
# trigger inverse field to set model_id and method
job_func.name = job_func.name
except exceptions.UserError:
# ignore invalid entries not to block migration
_logger.error(
"could not migrate job function '%s' (id: %s), invalid name",
job_func.name,
job_func.id,
)
20 changes: 15 additions & 5 deletions queue_job/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Base(models.AbstractModel):

_inherit = "base"

# TODO deprecated by :job-no-decorator:
def _register_hook(self):
"""Register marked jobs"""
super(Base, self)._register_hook()
Expand All @@ -45,15 +46,22 @@ def with_delay(
):
""" Return a ``DelayableRecordset``

The returned instance allow to enqueue any method of the recordset's
Model which is decorated by :func:`~odoo.addons.queue_job.job.job`.
The returned instance allows to enqueue any method of the recordset's
Model.

Usage::

self.env['res.users'].with_delay().write({'name': 'test'})

In the line above, in so far ``write`` is allowed to be delayed with
``@job``, the write will be executed in an asynchronous job.
``with_delay()`` accepts job properties which specify how the job will
be executed.

Usage with job properties::

delayable = env['a.model'].with_delay(priority=30, eta=60*60*5)
delayable.export_one_thing(the_thing_to_export)
# => the job will be executed with a low priority and not before a
# delay of 5 hours from now

:param priority: Priority of the job, 0 being the higher priority.
Default is 10.
Expand All @@ -69,7 +77,9 @@ def with_delay(
defined on the function
: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.
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`).
:return: instance of a DelayableRecordset
:rtype: :class:`odoo.addons.queue_job.job.DelayableRecordset`

Expand Down
Loading