diff --git a/attachment_queue/__init__.py b/attachment_queue/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/attachment_queue/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/attachment_queue/__manifest__.py b/attachment_queue/__manifest__.py new file mode 100644 index 000000000..7581c49f5 --- /dev/null +++ b/attachment_queue/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2015 Florian DA COSTA @ Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Attachment Queue", + "version": "12.0.1.0.0", + "author": "Akretion,Odoo Community Association (OCA)", + "summary": "Base module adding the concept of queue for processing files", + "website": "https://github.com/OCA/server-tools", + "maintainers": ["florian-dacosta", "sebastienbeau"], + "license": "AGPL-3", + "category": "Generic Modules", + "depends": ["base", "mail"], + "data": [ + "views/attachment_queue_view.xml", + "security/ir.model.access.csv", + "data/cron.xml", + "data/ir_config_parameter.xml", + "data/mail_template.xml", + ], + "demo": ["demo/attachment_queue_demo.xml"], + "installable": True, +} diff --git a/attachment_queue/data/cron.xml b/attachment_queue/data/cron.xml new file mode 100644 index 000000000..ad4e6540a --- /dev/null +++ b/attachment_queue/data/cron.xml @@ -0,0 +1,16 @@ + + + + + Run Attachments Queue + 30 + minutes + -1 + False + + + code + model.run_attachment_queue_scheduler() + + + diff --git a/attachment_queue/data/ir_config_parameter.xml b/attachment_queue/data/ir_config_parameter.xml new file mode 100644 index 000000000..5b723255a --- /dev/null +++ b/attachment_queue/data/ir_config_parameter.xml @@ -0,0 +1,7 @@ + + + + attachment_queue_cron_batch_limit + 200 + + diff --git a/attachment_queue/data/mail_template.xml b/attachment_queue/data/mail_template.xml new file mode 100644 index 000000000..4eec88492 --- /dev/null +++ b/attachment_queue/data/mail_template.xml @@ -0,0 +1,16 @@ + + + + + ${object.failure_emails} + Attachment Failure notification + The attachment ${object.name} has failed + + Hello,

+

The attachment ${object.name} has failed with the following error message :
${object.state_message}

+

Regards,

+ ]]>
+
+ +
diff --git a/attachment_queue/demo/attachment_queue_demo.xml b/attachment_queue/demo/attachment_queue_demo.xml new file mode 100644 index 000000000..ef69be60a --- /dev/null +++ b/attachment_queue/demo/attachment_queue_demo.xml @@ -0,0 +1,10 @@ + + + + + bWlncmF0aW9uIHRlc3Q= + attachment_queue_demo.doc + attachment_queue_demo.doc + + + diff --git a/attachment_queue/models/__init__.py b/attachment_queue/models/__init__.py new file mode 100644 index 000000000..89333838a --- /dev/null +++ b/attachment_queue/models/__init__.py @@ -0,0 +1 @@ +from . import attachment_queue diff --git a/attachment_queue/models/attachment_queue.py b/attachment_queue/models/attachment_queue.py new file mode 100644 index 000000000..4c058dac3 --- /dev/null +++ b/attachment_queue/models/attachment_queue.py @@ -0,0 +1,111 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo import api, fields, models, registry + +_logger = logging.getLogger(__name__) + + +class AttachmentQueue(models.Model): + _name = "attachment.queue" + _inherits = {"ir.attachment": "attachment_id"} + _inherit = ["mail.thread"] + + attachment_id = fields.Many2one( + "ir.attachment", + required=True, + ondelete="cascade", + help="Link to ir.attachment model ", + ) + file_type = fields.Selection( + selection=[], + help="The file type determines an import method to be used " + "to parse and transform data before their import in ERP or an export", + ) + date_done = fields.Datetime() + state = fields.Selection( + [("pending", "Pending"), ("failed", "Failed"), ("done", "Done")], + readonly=False, + required=True, + default="pending", + ) + state_message = fields.Text() + failure_emails = fields.Char( + compute="_compute_failure_emails", + string="Failure Emails", + help="Comma-separated list of email addresses to be notified in case of" + "failure", + ) + + def _compute_failure_emails(self): + for attach in self: + attach.failure_emails = attach._get_failure_emails() + + def _get_failure_emails(self): + # to be overriden in submodules implementing the file_type + self.ensure_one() + return "" + + @api.model + def run_attachment_queue_scheduler(self, domain=None): + if domain is None: + domain = [("state", "=", "pending")] + batch_limit = self.env.ref( + "attachment_queue.attachment_queue_cron_batch_limit" + ).value + if batch_limit and batch_limit.isdigit(): + limit = int(batch_limit) + else: + limit = 200 + attachments = self.search(domain, limit=limit) + if attachments: + return attachments.run() + return True + + def run(self): + """ + Run the process for each attachment queue + """ + failure_tmpl = self.env.ref( + "attachment_queue.attachment_failure_notification" + ) + for attachment in self: + with api.Environment.manage(): + with registry(self.env.cr.dbname).cursor() as new_cr: + new_env = api.Environment( + new_cr, self.env.uid, self.env.context + ) + attach = attachment.with_env(new_env) + try: + attach._run() + # pylint: disable=broad-except + except Exception as e: + attach.env.cr.rollback() + _logger.exception(str(e)) + attach.write( + {"state": "failed", "state_message": str(e)} + ) + emails = attach.failure_emails + if emails: + failure_tmpl.send_mail(attach.id) + attach.env.cr.commit() + else: + vals = { + "state": "done", + "date_done": fields.Datetime.now(), + } + attach.write(vals) + attach.env.cr.commit() + return True + + def _run(self): + self.ensure_one() + _logger.info("Starting processing of attachment queue id %d", self.id) + + def set_done(self): + """ + Manually set to done + """ + message = "Manually set to done by %s" % self.env.user.name + self.write({"state_message": message, "state": "done"}) diff --git a/attachment_queue/readme/CONTRIBUTORS.rst b/attachment_queue/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..1394f35aa --- /dev/null +++ b/attachment_queue/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Valentin CHEMIERE +* Florian da Costa +* Angel Moya +* Dan Kiplangat diff --git a/attachment_queue/readme/DESCRIPTION.rst b/attachment_queue/readme/DESCRIPTION.rst new file mode 100644 index 000000000..57bac75fd --- /dev/null +++ b/attachment_queue/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module adds async processing capabilities to attachments by implementing a new model attachment.queue that wraps attachments and stores additional information so that it can be processed in an asynchronous way. + +A use case of this module can be found in the attachment_synchronize module. diff --git a/attachment_queue/readme/USAGE.rst b/attachment_queue/readme/USAGE.rst new file mode 100644 index 000000000..a97a271e5 --- /dev/null +++ b/attachment_queue/readme/USAGE.rst @@ -0,0 +1,17 @@ +Go the menu Settings > Technical > Database Structure > Attachments Queue + +You can create / see standard attachments with additional fields + +Configure the batch limit for attachments that can be sync by the cron task at a go: + +Settings > Technical > System parameters > attachment_queue_cron_batch_limit + + +image:: ../static/description/tree.png + + +This module can be used in combination with attachment_synchronize to control file processing workflow + + +image:: ../static/description/form.png + diff --git a/attachment_queue/security/ir.model.access.csv b/attachment_queue/security/ir.model.access.csv new file mode 100644 index 000000000..e1e0880a3 --- /dev/null +++ b/attachment_queue/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_attachment_queue_user,attachment.queue.user,model_attachment_queue,,1,0,0,0 +access_attachment_queue_manager,attachment.queue.manager,model_attachment_queue,base.group_no_one,1,1,1,1 diff --git a/attachment_queue/static/description/form.png b/attachment_queue/static/description/form.png new file mode 100644 index 000000000..fbc9f6aa6 Binary files /dev/null and b/attachment_queue/static/description/form.png differ diff --git a/attachment_queue/static/description/icon.png b/attachment_queue/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/attachment_queue/static/description/icon.png differ diff --git a/attachment_queue/static/description/tree.png b/attachment_queue/static/description/tree.png new file mode 100644 index 000000000..d4dcc92fe Binary files /dev/null and b/attachment_queue/static/description/tree.png differ diff --git a/attachment_queue/tests/__init__.py b/attachment_queue/tests/__init__.py new file mode 100644 index 000000000..7f25150f6 --- /dev/null +++ b/attachment_queue/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_attachment_queue diff --git a/attachment_queue/tests/test_attachment_queue.py b/attachment_queue/tests/test_attachment_queue.py new file mode 100644 index 000000000..74d368f2b --- /dev/null +++ b/attachment_queue/tests/test_attachment_queue.py @@ -0,0 +1,39 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import odoo +from odoo import api +from odoo.tests.common import TransactionCase + + +class TestAttachmentBaseQueue(TransactionCase): + def setUp(self): + super().setUp() + self.registry.enter_test_mode(self.env.cr) + self.env = api.Environment( + self.registry.test_cr, self.env.uid, self.env.context + ) + self.attachment = self.env.ref( + "attachment_queue.attachment_queue_demo" + ) + + def tearDown(self): + self.registry.leave_test_mode() + super().tearDown() + + def test_attachment_queue(self): + """Test run_attachment_queue_scheduler to ensure set state to done + """ + self.assertEqual(self.attachment.state, "pending") + self.env["attachment.queue"].run_attachment_queue_scheduler() + self.env.cache.invalidate() + with odoo.registry(self.env.cr.dbname).cursor() as new_cr: + new_env = api.Environment(new_cr, self.env.uid, self.env.context) + attach = self.attachment.with_env(new_env) + self.assertEqual(attach.state, "done") + + def test_set_done(self): + """Test set_done manually + """ + self.assertEqual(self.attachment.state, "pending") + self.attachment.set_done() + self.assertEqual(self.attachment.state, "done") diff --git a/attachment_queue/views/attachment_queue_view.xml b/attachment_queue/views/attachment_queue_view.xml new file mode 100644 index 000000000..f83f60a72 --- /dev/null +++ b/attachment_queue/views/attachment_queue_view.xml @@ -0,0 +1,106 @@ + + + + + attachment.queue + + + +
+
+
+ + + + + + + + + + + +
+ + + attachment.queue + + + + + + + + + + + + + + attachment.queue + + + + + + + + + + + + + + + + + + + + + + + + + + + Attachments Queue + ir.actions.act_window + attachment.queue + form + tree,form + + + + + + + tree + + + + + + + form + + + + + + +