diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index a202e87b46..1e3624d0dc 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -17,6 +17,7 @@
"views/queue_job_channel_views.xml",
"views/queue_job_function_views.xml",
"wizards/queue_jobs_to_done_views.xml",
+ "wizards/queue_jobs_to_cancelled_views.xml",
"wizards/queue_requeue_job_views.xml",
"views/queue_job_menus.xml",
"data/queue_data.xml",
diff --git a/queue_job/job.py b/queue_job/job.py
index a4dc224b85..8efa05c73e 100644
--- a/queue_job/job.py
+++ b/queue_job/job.py
@@ -16,6 +16,7 @@
PENDING = "pending"
ENQUEUED = "enqueued"
+CANCELLED = "cancelled"
DONE = "done"
STARTED = "started"
FAILED = "failed"
@@ -25,6 +26,7 @@
(ENQUEUED, "Enqueued"),
(STARTED, "Started"),
(DONE, "Done"),
+ (CANCELLED, "Cancelled"),
(FAILED, "Failed"),
]
@@ -301,6 +303,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
@@ -484,6 +489,7 @@ def __init__(
self.date_enqueued = None
self.date_started = None
self.date_done = None
+ self.date_cancelled = None
self.result = None
self.exc_name = None
@@ -558,6 +564,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,
@@ -571,6 +578,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:
@@ -719,6 +728,7 @@ def set_pending(self, result=None, reset_retry=True):
self.date_started = None
self.date_done = None
self.worker_pid = None
+ self.date_cancelled = None
if reset_retry:
self.retry = 0
if result is not None:
@@ -743,6 +753,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 2d8ef74538..b6f8b52a6e 100644
--- a/queue_job/models/queue_job.py
+++ b/queue_job/models/queue_job.py
@@ -8,7 +8,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__)
@@ -82,6 +82,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")
@@ -187,6 +188,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()
@@ -196,6 +199,11 @@ def button_done(self):
self._change_job_state(DONE, result=result)
return True
+ def button_cancelled(self):
+ result = _("Cancelled by %s") % self.env.user.name
+ self._change_job_state(CANCELLED, result=result)
+ return True
+
def requeue(self):
self._change_job_state(PENDING)
return True
@@ -255,7 +263,9 @@ def autovacuum(self):
while True:
jobs = self.search(
[
+ "|",
("date_done", "<=", deadline),
+ ("date_cancelled", "<=", deadline),
("channel", "=", channel.complete_name),
],
limit=1000,
diff --git a/queue_job/security/ir.model.access.csv b/queue_job/security/ir.model.access.csv
index 9242305158..634daf8ede 100644
--- a/queue_job/security/ir.model.access.csv
+++ b/queue_job/security/ir.model.access.csv
@@ -4,3 +4,4 @@ access_queue_job_function_manager,queue job functions manager,queue_job.model_qu
access_queue_job_channel_manager,queue job channel manager,queue_job.model_queue_job_channel,queue_job.group_queue_job_manager,1,1,1,1
access_queue_requeue_job,queue requeue job manager,queue_job.model_queue_requeue_job,queue_job.group_queue_job_manager,1,1,1,1
access_queue_jobs_to_done,queue jobs to done manager,queue_job.model_queue_jobs_to_done,queue_job.group_queue_job_manager,1,1,1,1
+access_queue_jobs_to_cancelled,queue jobs to cancelled manager,queue_job.model_queue_jobs_to_cancelled,queue_job.group_queue_job_manager,1,1,1,1
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..2ac162d313
--- /dev/null
+++ b/queue_job/tests/test_wizards.py
@@ -0,0 +1,48 @@
+# 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 cb7aeac28b..9121e1b188 100644
--- a/queue_job/views/queue_job_views.xml
+++ b/queue_job/views/queue_job_views.xml
@@ -23,12 +23,20 @@
type="object"
groups="queue_job.group_queue_job_manager"
/>
+
diff --git a/queue_job/wizards/__init__.py b/queue_job/wizards/__init__.py
index 0794047e75..06c0bd8572 100644
--- a/queue_job/wizards/__init__.py
+++ b/queue_job/wizards/__init__.py
@@ -1,2 +1,3 @@
from . import queue_requeue_job
from . import queue_jobs_to_done
+from . import queue_jobs_to_cancelled
diff --git a/queue_job/wizards/queue_jobs_to_cancelled.py b/queue_job/wizards/queue_jobs_to_cancelled.py
new file mode 100644
index 0000000000..9e73374ebd
--- /dev/null
+++ b/queue_job/wizards/queue_jobs_to_cancelled.py
@@ -0,0 +1,17 @@
+# Copyright 2013-2020 Camptocamp SA
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
+
+from odoo import models
+
+
+class SetJobsToCancelled(models.TransientModel):
+ _inherit = "queue.requeue.job"
+ _name = "queue.jobs.to.cancelled"
+ _description = "Cancel all selected jobs"
+
+ 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"}
diff --git a/queue_job/wizards/queue_jobs_to_cancelled_views.xml b/queue_job/wizards/queue_jobs_to_cancelled_views.xml
new file mode 100644
index 0000000000..abfb761bc5
--- /dev/null
+++ b/queue_job/wizards/queue_jobs_to_cancelled_views.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ Cancel Jobs
+ queue.jobs.to.cancelled
+
+
+
+
+
+
+ Cancel jobs
+ queue.jobs.to.cancelled
+ form
+
+ new
+
+
+
+