[IMP] attachment_queue multiple improvements :
Refactore the conccurent attachment run, we lock the attachment now instead of using the concurent update concept. Move the attachment queue menu from settings to Queue app Fix failure email template Rename the default attachment channelpull/2560/head
parent
178f40731a
commit
e7754cd9e0
|
@ -17,5 +17,6 @@
|
|||
"data/mail_template.xml",
|
||||
"data/queue_job_channel.xml",
|
||||
],
|
||||
"demo": ["demo/attachment_queue.xml"],
|
||||
"installable": True,
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<odoo noupdate="1">
|
||||
|
||||
<record id="attachment_failure_notification" model="mail.template">
|
||||
<field name="email_to">${object.failure_emails}</field>
|
||||
<field name="email_to">{{object.failure_emails}}</field>
|
||||
<field name="name">Attachment Failure notification</field>
|
||||
<field name="subject">The attachment ${object.name} has failed</field>
|
||||
<field name="subject">The attachment {{object.name}} has failed</field>
|
||||
<field name="model_id" ref="attachment_queue.model_attachment_queue" />
|
||||
<field
|
||||
name="body_html"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo noupdate="1">
|
||||
<record id="attachment_queue_job_channel" model="queue.job.channel">
|
||||
<field name="name">Attachment queues</field>
|
||||
<field name="name">attachment_queue</field>
|
||||
<field name="parent_id" ref="queue_job.channel_root" />
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo noupdate="1">
|
||||
<record id="dummy_attachment_queue" model="attachment.queue">
|
||||
<field name="name">Dummy file Used for unitests</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models
|
||||
import psycopg2
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.addons.queue_job.exception import RetryableJobError
|
||||
|
@ -10,9 +12,7 @@ from odoo.addons.queue_job.exception import RetryableJobError
|
|||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_ETA_FOR_RETRY = 60 * 60
|
||||
STR_ERR_ATTACHMENT_RUNNING = (
|
||||
"The attachment is currently flagged as being in processing"
|
||||
)
|
||||
STR_ERR_ATTACHMENT_RUNNING = "The attachment is currently being in processing"
|
||||
STR_ERROR_DURING_PROCESSING = "Error during processing of attachment_queue id {}: \n"
|
||||
|
||||
|
||||
|
@ -46,19 +46,16 @@ class AttachmentQueue(models.Model):
|
|||
help="Comma-separated list of email addresses to be notified in case of"
|
||||
"failure",
|
||||
)
|
||||
running_lock = fields.Boolean()
|
||||
|
||||
@property
|
||||
def _eta_for_retry(self):
|
||||
return DEFAULT_ETA_FOR_RETRY
|
||||
|
||||
@property
|
||||
def _job_attrs(self):
|
||||
return {"channel": "Attachment queues"}
|
||||
# Override this method to have file type specific job attributes
|
||||
self.ensure_one()
|
||||
return {"channel": "attachment_queue"}
|
||||
|
||||
def _schedule_jobs(self):
|
||||
for el in self:
|
||||
el.with_delay(**self._job_attrs).run()
|
||||
kwargs = el._job_attrs()
|
||||
el.with_delay(**kwargs).run_as_job()
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
@ -67,15 +64,9 @@ class AttachmentQueue(models.Model):
|
|||
return res
|
||||
|
||||
def button_reschedule(self):
|
||||
self.state = "pending"
|
||||
self.state_message = ""
|
||||
self.write({"state": "pending", "state_message": ""})
|
||||
self._schedule_jobs()
|
||||
|
||||
def button_manual_run(self):
|
||||
if self.running_lock:
|
||||
raise UserError(STR_ERR_ATTACHMENT_RUNNING)
|
||||
self.run()
|
||||
|
||||
def _compute_failure_emails(self):
|
||||
for attach in self:
|
||||
attach.failure_emails = attach._get_failure_emails()
|
||||
|
@ -85,41 +76,70 @@ class AttachmentQueue(models.Model):
|
|||
self.ensure_one()
|
||||
return ""
|
||||
|
||||
def button_manual_run(self):
|
||||
"""
|
||||
Run the process for an individual attachment queue from a dedicated button
|
||||
"""
|
||||
try:
|
||||
self._cr.execute(
|
||||
"""
|
||||
SELECT id
|
||||
FROM attachment_queue
|
||||
WHERE id = %s
|
||||
FOR UPDATE NOWAIT
|
||||
""",
|
||||
(self.id,),
|
||||
)
|
||||
except psycopg2.OperationalError as exc:
|
||||
raise UserError(_(STR_ERR_ATTACHMENT_RUNNING)) from exc
|
||||
if self.state != "done":
|
||||
self.run()
|
||||
|
||||
def run_as_job(self):
|
||||
"""
|
||||
Run the process for an individual attachment queue from a async job
|
||||
"""
|
||||
try:
|
||||
self._cr.execute(
|
||||
"""
|
||||
SELECT id
|
||||
FROM attachment_queue
|
||||
WHERE id = %s
|
||||
FOR UPDATE NOWAIT
|
||||
""",
|
||||
(self.id,),
|
||||
)
|
||||
except psycopg2.OperationalError as exc:
|
||||
raise RetryableJobError(
|
||||
STR_ERR_ATTACHMENT_RUNNING,
|
||||
seconds=DEFAULT_ETA_FOR_RETRY,
|
||||
ignore_retry=True,
|
||||
) from exc
|
||||
if self.state == "pending":
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
self.run()
|
||||
except Exception as e:
|
||||
_logger.warning(STR_ERROR_DURING_PROCESSING.format(self.id) + str(e))
|
||||
self.write({"state": "failed", "state_message": str(e)})
|
||||
emails = self.failure_emails
|
||||
if emails:
|
||||
self.env.ref(
|
||||
"attachment_queue.attachment_failure_notification"
|
||||
).send_mail(self.id)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the process for an individual attachment queue
|
||||
"""
|
||||
if self.state != "pending":
|
||||
return
|
||||
if self.running_lock is True:
|
||||
raise RetryableJobError(
|
||||
STR_ERR_ATTACHMENT_RUNNING, seconds=self._eta_for_retry
|
||||
)
|
||||
self.running_lock = True
|
||||
self.flush_recordset()
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
self._run()
|
||||
except Exception as e:
|
||||
_logger.warning(STR_ERROR_DURING_PROCESSING.format(self.id) + str(e))
|
||||
self.write(
|
||||
{"state": "failed", "state_message": str(e), "running_lock": False}
|
||||
)
|
||||
emails = self.failure_emails
|
||||
if emails:
|
||||
self.env.ref(
|
||||
"attachment_queue.attachment_failure_notification"
|
||||
).send_mail(self.id)
|
||||
return False
|
||||
else:
|
||||
self.write(
|
||||
{
|
||||
"state": "done",
|
||||
"date_done": fields.Datetime.now(),
|
||||
"running_lock": False,
|
||||
}
|
||||
)
|
||||
return True
|
||||
self._run()
|
||||
self.write(
|
||||
{
|
||||
"state": "done",
|
||||
"date_done": fields.Datetime.now(),
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
def _run(self):
|
||||
self.ensure_one()
|
||||
|
|
|
@ -4,6 +4,7 @@ from unittest import mock
|
|||
|
||||
from odoo_test_helper import FakeModelLoader
|
||||
|
||||
from odoo import registry
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
@ -53,21 +54,41 @@ class TestAttachmentBaseQueue(TransactionCase):
|
|||
with trap_jobs() as trap:
|
||||
self._create_dummy_attachment()
|
||||
trap.assert_enqueued_job(
|
||||
self.env["attachment.queue"].run,
|
||||
self.env["attachment.queue"].run_as_job,
|
||||
)
|
||||
|
||||
def test_aq_locked_job(self):
|
||||
"""If an attachment is already running, and a job tries to run it, retry later"""
|
||||
with self.assertRaises(RetryableJobError):
|
||||
self._create_dummy_attachment({"running_lock": True}, no_job=True)
|
||||
attachment = self.env.ref("attachment_queue.dummy_attachment_queue")
|
||||
with registry(self.env.cr.dbname).cursor() as new_cr:
|
||||
new_cr.execute(
|
||||
"""
|
||||
SELECT id
|
||||
FROM attachment_queue
|
||||
WHERE id = %s
|
||||
FOR UPDATE NOWAIT
|
||||
""",
|
||||
(attachment.id,),
|
||||
)
|
||||
with self.assertRaises(RetryableJobError):
|
||||
attachment.run_as_job()
|
||||
|
||||
def test_aq_locked_button(self):
|
||||
"""If an attachment is already running, and a user tries to run it manually,
|
||||
raise error window"""
|
||||
attachment = self._create_dummy_attachment(no_job=True)
|
||||
attachment.running_lock = True
|
||||
with self.assertRaises(UserError):
|
||||
attachment.button_manual_run()
|
||||
attachment = self.env.ref("attachment_queue.dummy_attachment_queue")
|
||||
with registry(self.env.cr.dbname).cursor() as new_cr:
|
||||
new_cr.execute(
|
||||
"""
|
||||
SELECT id
|
||||
FROM attachment_queue
|
||||
WHERE id = %s
|
||||
FOR UPDATE NOWAIT
|
||||
""",
|
||||
(attachment.id,),
|
||||
)
|
||||
with self.assertRaises(UserError):
|
||||
attachment.button_manual_run()
|
||||
|
||||
def test_run_ok(self):
|
||||
"""Attachment queue should have correct state and result"""
|
||||
|
@ -102,6 +123,10 @@ class TestAttachmentBaseQueue(TransactionCase):
|
|||
self._create_dummy_attachment(no_job=True)
|
||||
partners_after = len(self.env["res.partner"].search([]))
|
||||
self.assertEqual(partners_after, partners_initial)
|
||||
failure_email = self.env["mail.mail"].search(
|
||||
[("subject", "ilike", "dummy_aq.doc")]
|
||||
)
|
||||
self.assertEqual(failure_email.email_to, "test@test.com")
|
||||
|
||||
def test_set_done(self):
|
||||
"""Test set_done manually"""
|
||||
|
|
|
@ -18,3 +18,6 @@ class AttachmentQueue(models.Model):
|
|||
def mock_run_create_partners_and_fail(self):
|
||||
self.mock_run_create_partners()
|
||||
raise UserError(_("boom"))
|
||||
|
||||
def _get_failure_emails(self):
|
||||
return "test@test.com"
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<field name="model">attachment.queue</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree default_order='create_date desc'>
|
||||
<field name="create_date" />
|
||||
<field name="name" />
|
||||
<field name="file_type" />
|
||||
<field name="type" />
|
||||
|
@ -152,7 +153,7 @@
|
|||
|
||||
<menuitem
|
||||
id="menu_attachment_queue"
|
||||
parent="base.next_id_9"
|
||||
parent="queue_job.menu_queue_job_root"
|
||||
sequence="20"
|
||||
action="action_attachment_queue"
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue