diff --git a/queue_job/job.py b/queue_job/job.py index c121e16eef..2045bf8362 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -19,6 +19,7 @@ PENDING = 'pending' ENQUEUED = 'enqueued' +CANCELLED = 'cancelled' DONE = 'done' STARTED = 'started' FAILED = 'failed' @@ -27,6 +28,7 @@ (ENQUEUED, 'Enqueued'), (STARTED, 'Started'), (DONE, 'Done'), + (CANCELLED, 'Cancelled'), (FAILED, 'Failed')] DEFAULT_PRIORITY = 10 # used by the PriorityQueue to sort the jobs @@ -283,6 +285,9 @@ def _load_from_db_record(cls, job_db_record): if stored.date_done: job_.date_done = stored.date_done + if stored.date_cancelled: + job_.date_cancelled = stored.date_cancelled + 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 @@ -435,6 +440,7 @@ def __init__(self, func, self.date_enqueued = None self.date_started = None self.date_done = None + self.date_cancelled = None self.result = None self.exc_name = None @@ -514,6 +520,7 @@ def _store_values(self, create=False): 'date_started': False, 'date_done': False, 'exec_time': False, + 'date_cancelled': False, 'eta': False, 'identity_key': False, "worker_pid": self.worker_pid, @@ -527,6 +534,8 @@ def _store_values(self, create=False): vals['date_done'] = self.date_done if self.exec_time: vals["exec_time"] = self.exec_time + if self.date_cancelled: + vals['date_cancelled'] = self.date_cancelled if self.eta: vals['eta'] = self.eta if self.identity_key: @@ -671,6 +680,7 @@ def set_pending(self, result=None, reset_retry=True): self.date_done = None self.worker_pid = None self.date_done = None + self.date_cancelled = None if reset_retry: self.retry = 0 if result is not None: @@ -695,6 +705,12 @@ def set_done(self, result=None): if result is not None: self.result = result + def set_cancelled(self, result=None): + self.state = CANCELLED + self.date_cancelled = datetime.now() + if result is not None: + self.result = result + def set_failed(self, **kw): self.state = FAILED for k, v in kw.items(): diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index d58744f387..3986688aa3 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -11,7 +11,7 @@ from odoo.osv import expression from ..fields import JobSerialized -from ..job import DONE, PENDING, STATES, Job +from ..job import CANCELLED, DONE, PENDING, STATES, Job _logger = logging.getLogger(__name__) @@ -91,6 +91,7 @@ class QueueJob(models.Model): group_operator="avg", help="Time required to execute this job in seconds. Average when grouped.", ) + date_cancelled = fields.Datetime(readonly=True) eta = fields.Datetime(string='Execute only after') retry = fields.Integer(string='Current try') @@ -200,6 +201,8 @@ def _change_job_state(self, state, result=None): job_.set_done(result=result) elif state == PENDING: job_.set_pending(result=result) + elif state == CANCELLED: + job_.set_cancelled(result=result) else: raise ValueError('State not supported: %s' % state) job_.store() @@ -210,6 +213,12 @@ def button_done(self): self._change_job_state(DONE, result=result) return True + @api.multi + def button_cancelled(self): + result = _('Cancelled by %s') % self.env.user.name + self._change_job_state(CANCELLED, result=result) + return True + @api.multi def requeue(self): self._change_job_state(PENDING) @@ -271,7 +280,9 @@ def autovacuum(self): deadline = datetime.now() - timedelta( days=int(channel.removal_interval)) jobs = self.search( - [('date_done', '<=', deadline), + ['|', + ('date_done', '<=', deadline), + ('date_cancelled', '<=', deadline), ('channel', '=', channel.complete_name)], ) if jobs: @@ -400,6 +411,20 @@ def set_done(self): return {'type': 'ir.actions.act_window_close'} +class SetJobsToCancelled(models.TransientModel): + _inherit = 'queue.requeue.job' + _name = 'queue.jobs.to.cancelled' + _description = 'Cancel all selected jobs' + + @api.multi + def set_cancelled(self): + jobs = self.job_ids.filtered( + lambda x: x.state in ('pending', 'failed', 'enqueued') + ) + jobs.button_cancelled() + return {'type': 'ir.actions.act_window_close'} + + class JobChannel(models.Model): _name = 'queue.job.channel' _description = 'Job Channels' diff --git a/queue_job/tests/__init__.py b/queue_job/tests/__init__.py index 10138c469e..a556f10d83 100644 --- a/queue_job/tests/__init__.py +++ b/queue_job/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_model_job_channel from . import test_model_job_function from . import test_queue_job_protected_write +from . import test_wizards diff --git a/queue_job/tests/test_wizards.py b/queue_job/tests/test_wizards.py new file mode 100644 index 0000000000..d03692ce61 --- /dev/null +++ b/queue_job/tests/test_wizards.py @@ -0,0 +1,39 @@ +# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +from odoo.tests import common + + +class TestWizards(common.TransactionCase): + def setUp(self): + super().setUp() + self.job = self.env['queue.job'].with_context( + _job_edit_sentinel=self.env['queue.job'].EDIT_SENTINEL, + ).create({ + 'uuid': 'test', + 'user_id': self.env.user.id, + 'state': 'failed', + 'model_name': + 'queue.job', + 'method_name': 'write', + 'args': (), + }) + + def _wizard(self, model_name): + return self.env[model_name].with_context( + active_model=self.job._name, + active_ids=self.job.ids, + ).create({}) + + def test_01_requeue(self): + wizard = self._wizard('queue.requeue.job') + wizard.requeue() + self.assertEqual(self.job.state, 'pending') + + def test_02_cancel(self): + wizard = self._wizard('queue.jobs.to.cancelled') + wizard.set_cancelled() + self.assertEqual(self.job.state, 'cancelled') + + def test_03_done(self): + wizard = self._wizard('queue.jobs.to.done') + wizard.set_done() + self.assertEqual(self.job.state, 'done') diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index b8e4d33fe5..c598c992eb 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -20,6 +20,12 @@ string="Set to 'Done'" type="object" groups="queue_job.group_queue_job_manager"/> +