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..ece97f5a2 --- /dev/null +++ b/attachment_queue/__manifest__.py @@ -0,0 +1,28 @@ +# 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 that add the concept of queue for processing file", + '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..72e057ced --- /dev/null +++ b/attachment_queue/models/attachment_queue.py @@ -0,0 +1,106 @@ +# 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="list of email (separated by comma) which should 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 + + @api.multi + def _run(self): + self.ensure_one() + _logger.info('Start to process attachment queue id %d', self.id) + + @api.multi + 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..a5e20daee --- /dev/null +++ b/attachment_queue/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module implement a queue for processing file. +File are stored in Odoo standard ir.attachment. +The attachments will be processed depending on their type. + +An example of the use of this module, can be found in the module `attachment_synchronize`. diff --git a/attachment_queue/readme/USAGE.rst b/attachment_queue/readme/USAGE.rst new file mode 100644 index 000000000..ec276d247 --- /dev/null +++ b/attachment_queue/readme/USAGE.rst @@ -0,0 +1,8 @@ +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 + 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/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/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..fc29f902f --- /dev/null +++ b/attachment_queue/tests/test_attachment_queue.py @@ -0,0 +1,50 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase +import odoo +from odoo import api + + +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..f8bc7c9d3 --- /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 + + + + + + +