[IMP] ir.cron: trigger on multiple moments
Often the business want to trigger the cron several time at different moments. The typical use case is the business holds a recordset and needs to trigger the cron for every record. The current solution is to loop on each record to call `_trigger` times. We now allow to invoke `_trigger` with an iterable of `datetime` objects, and isolate the implementation in method `_trigger_list`. We also removed the `delay` argument as it was not used. closes odoo/odoo#65459 Task: 2452697 Signed-off-by: Julien Castiaux <Julien00859@users.noreply.github.com>
This commit is contained in:
committed by
Raphael Collet
parent
c4b4eb699d
commit
a29bb54633
@@ -162,7 +162,10 @@ class TestEventNotifications(TransactionCase, MailCase):
|
|||||||
triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
|
triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
|
||||||
new_triggers = triggers_after - triggers_before
|
new_triggers = triggers_after - triggers_before
|
||||||
new_triggers.ensure_one()
|
new_triggers.ensure_one()
|
||||||
self.assertEqual(new_triggers.call_at, now - relativedelta(minutes=5))
|
self.assertEqual(
|
||||||
|
new_triggers.call_at,
|
||||||
|
now.replace(second=0) - relativedelta(minutes=5),
|
||||||
|
)
|
||||||
|
|
||||||
with patch.object(fields.Datetime, 'now', lambda: now):
|
with patch.object(fields.Datetime, 'now', lambda: now):
|
||||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
@@ -11,6 +10,7 @@ from dateutil.relativedelta import relativedelta
|
|||||||
import odoo
|
import odoo
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools import unique
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -393,36 +393,59 @@ class ir_cron(models.Model):
|
|||||||
return self.try_write({'active': active})
|
return self.try_write({'active': active})
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _trigger(self, delay=None, at=None):
|
def _trigger(self, at=None):
|
||||||
"""
|
"""
|
||||||
Schedule a cron job to be executed soon independently of its
|
Schedule a cron job to be executed soon independently of its
|
||||||
``nextcall`` field value.
|
``nextcall`` field value.
|
||||||
|
|
||||||
By default the cron is scheduled to be executed in the next
|
By default the cron is scheduled to be executed in the next batch but
|
||||||
batch but optional arguments may be given to delay the execution
|
the optional `at` argument may be given to delay the execution later
|
||||||
with a precision down to 1 minute.
|
with a precision down to 1 minute.
|
||||||
|
|
||||||
:param datetime.timedelta delay:
|
The method may be called with a datetime or an iterable of datetime.
|
||||||
Execute the cron later, after the delay expires.
|
The actual implementation is in :meth:`~._trigger_list`, which is the
|
||||||
|
recommended method for overrides.
|
||||||
|
|
||||||
:param datetime.datetime at:
|
:param Optional[Union[datetime.datetime, list[datetime.datetime]]] at:
|
||||||
Execute the cron later, at a precise moment in time.
|
When to execute the cron, at one or several moments in time instead
|
||||||
|
of as soon as possible.
|
||||||
"""
|
"""
|
||||||
|
if at is None:
|
||||||
|
at_list = [datetime.utcnow()]
|
||||||
|
elif isinstance(at, datetime):
|
||||||
|
at_list = [at]
|
||||||
|
else:
|
||||||
|
at_list = list(at)
|
||||||
|
assert all(isinstance(at, datetime) for at in at_list)
|
||||||
|
|
||||||
|
self._trigger_list(at_list)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _trigger_list(self, at_list):
|
||||||
|
"""
|
||||||
|
Implementation of :meth:`~._trigger`.
|
||||||
|
|
||||||
|
:param list[datetime.datetime] at_list:
|
||||||
|
Execute the cron later, at precise moments in time.
|
||||||
|
"""
|
||||||
|
if not at_list:
|
||||||
|
return
|
||||||
|
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
now = datetime.utcnow().replace(second=0, microsecond=0)
|
||||||
|
|
||||||
if delay and at:
|
# compress the list to insert at most one trigger per minute
|
||||||
raise ValueError("Set either a delay either a moment, not both.")
|
at_list = list(unique(at.replace(second=0, microsecond=0) for at in at_list))
|
||||||
|
|
||||||
now = datetime.utcnow()
|
self.env['ir.cron.trigger'].sudo().create([
|
||||||
call_at = at or now
|
{'cron_id': self.id, 'call_at': at}
|
||||||
if delay:
|
for at in at_list
|
||||||
call_at += delay
|
])
|
||||||
|
if _logger.isEnabledFor(logging.DEBUG):
|
||||||
|
ats = ', '.join(map(str, at_list))
|
||||||
|
_logger.debug("will execute '%s' at %s", self.sudo().name, ats)
|
||||||
|
|
||||||
self.env['ir.cron.trigger'].sudo().create({'cron_id': self.id, 'call_at': call_at})
|
if min(at_list) <= now:
|
||||||
_logger.debug("will execute '%s' at %s",
|
|
||||||
self.name, call_at if call_at > now else "soon as possible")
|
|
||||||
|
|
||||||
if call_at <= now:
|
|
||||||
self._cr.postcommit.add(self._notifydb)
|
self._cr.postcommit.add(self._notifydb)
|
||||||
|
|
||||||
def _notifydb(self):
|
def _notifydb(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user