OCA-git-bot
5 years ago
18 changed files with 360 additions and 0 deletions
-
1attachment_queue/__init__.py
-
23attachment_queue/__manifest__.py
-
16attachment_queue/data/cron.xml
-
7attachment_queue/data/ir_config_parameter.xml
-
16attachment_queue/data/mail_template.xml
-
10attachment_queue/demo/attachment_queue_demo.xml
-
1attachment_queue/models/__init__.py
-
111attachment_queue/models/attachment_queue.py
-
4attachment_queue/readme/CONTRIBUTORS.rst
-
3attachment_queue/readme/DESCRIPTION.rst
-
17attachment_queue/readme/USAGE.rst
-
3attachment_queue/security/ir.model.access.csv
-
BINattachment_queue/static/description/form.png
-
BINattachment_queue/static/description/icon.png
-
BINattachment_queue/static/description/tree.png
-
3attachment_queue/tests/__init__.py
-
39attachment_queue/tests/test_attachment_queue.py
-
106attachment_queue/views/attachment_queue_view.xml
@ -0,0 +1 @@ |
|||||
|
from . import models |
@ -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, |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<record model="ir.cron" id="cronjob_run_attachment_queue"> |
||||
|
<field name='name'>Run Attachments Queue</field> |
||||
|
<field name='interval_number'>30</field> |
||||
|
<field name='interval_type'>minutes</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="active">False</field> |
||||
|
<field name="doall" eval="False" /> |
||||
|
<field name="model_id" ref="model_attachment_queue"/> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code">model.run_attachment_queue_scheduler()</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,7 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo noupdate="1"> |
||||
|
<record id="attachment_queue_cron_batch_limit" model="ir.config_parameter"> |
||||
|
<field name="key">attachment_queue_cron_batch_limit</field> |
||||
|
<field name="value">200</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<record id="attachment_failure_notification" model="mail.template"> |
||||
|
<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="model_id" ref="attachment_queue.model_attachment_queue"/> |
||||
|
<field name="body_html"><![CDATA[ |
||||
|
<p style="margin:0px 0px 10px 0px;font-size:13px;font-family:"Lucida Grande", Helvetica, Verdana, Arial, sans-serif;">Hello,<br><br></p> |
||||
|
<p style="margin:0px 0px 10px 0px;font-size:13px;font-family:"Lucida Grande", Helvetica, Verdana, Arial, sans-serif;">The attachment ${object.name} has failed with the following error message : <br>${object.state_message}<br></p><p style="margin:0px 0px 10px 0px;font-size:13px;font-family:"Lucida Grande", Helvetica, Verdana, Arial, sans-serif;"></p> |
||||
|
<p style="margin:0px 0px 10px 0px;font-size:13px;font-family:"Lucida Grande", Helvetica, Verdana, Arial, sans-serif;">Regards,<br></p> |
||||
|
]]></field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,10 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<record id="attachment_queue_demo" model="attachment.queue"> |
||||
|
<field name="datas">bWlncmF0aW9uIHRlc3Q=</field> |
||||
|
<field name="datas_fname">attachment_queue_demo.doc</field> |
||||
|
<field name="name">attachment_queue_demo.doc</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1 @@ |
|||||
|
from . import attachment_queue |
@ -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"}) |
@ -0,0 +1,4 @@ |
|||||
|
* Valentin CHEMIERE <valentin.chemiere@akretion.com> |
||||
|
* Florian da Costa <florian.dacosta@akretion.com> |
||||
|
* Angel Moya <http://angelmoya.es> |
||||
|
* Dan Kiplangat <dan@sunflowerweb.nl> |
@ -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. |
@ -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 |
||||
|
|
@ -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 |
After Width: 925 | Height: 780 | Size: 75 KiB |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
After Width: 1171 | Height: 270 | Size: 41 KiB |
@ -0,0 +1,3 @@ |
|||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from . import test_attachment_queue |
@ -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") |
@ -0,0 +1,106 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<record id="view_attachment_queue_form" model="ir.ui.view"> |
||||
|
<field name="model">attachment.queue</field> |
||||
|
<field name="inherit_id" ref="base.view_attachment_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath expr="/form/*" position="before"> |
||||
|
<header> |
||||
|
<button name="run" states="pending,failed" |
||||
|
string="Run" type="object" class="oe_highlight"/> |
||||
|
<button name="set_done" states="pending,failed" |
||||
|
string="Set to Done" type="object"/> |
||||
|
</header> |
||||
|
</xpath> |
||||
|
<field name="url" position="after"> |
||||
|
<field name="date_done"/> |
||||
|
<field name="state"/> |
||||
|
<field name="file_type"/> |
||||
|
</field> |
||||
|
<group name="description_group"> |
||||
|
<group name="state_message" string="Error" colspan="4"> |
||||
|
<field name="state_message" nolabel="1"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_attachment_queue_tree" model="ir.ui.view"> |
||||
|
<field name="model">attachment.queue</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree default_order='create_date desc'> |
||||
|
<field name="name"/> |
||||
|
<field name="datas_fname"/> |
||||
|
<field name="file_type"/> |
||||
|
<field name="type"/> |
||||
|
<field name="create_date"/> |
||||
|
<field name="state"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_attachment_queue_search" model="ir.ui.view"> |
||||
|
<field name="model">attachment.queue</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<search string="Attachments"> |
||||
|
<field name="name" filter_domain="['|', ('name','ilike',self), ('datas_fname','ilike',self)]" string="Attachment"/> |
||||
|
<field name="create_date"/> |
||||
|
<filter name="url" |
||||
|
string="URL" |
||||
|
domain="[('type','=','url')]"/> |
||||
|
<filter name="binary" |
||||
|
string="Binary" |
||||
|
domain="[('type','=','binary')]"/> |
||||
|
<separator/> |
||||
|
<filter name="my_documents_filter" |
||||
|
string="My Document(s)" |
||||
|
domain="[('create_uid','=',uid)]" |
||||
|
help="Filter on my documents"/> |
||||
|
<field name="create_uid"/> |
||||
|
<field name="type"/> |
||||
|
<filter string="Pending" name="pending" domain="[('state', '=', 'pending')]"/> |
||||
|
<filter string="Failed" name="failed" domain="[('state', '=', 'failed')]"/> |
||||
|
<filter string="Done" name="done" domain="[('state', '=', 'done')]"/> |
||||
|
<group expand="0" string="Group By"> |
||||
|
<filter string="Owner" name="owner" domain="[]" context="{'group_by':'create_uid'}"/> |
||||
|
<filter string="Type" name="type" domain="[]" context="{'group_by':'type'}" groups="base.group_no_one"/> |
||||
|
<filter string="Company" name="company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/> |
||||
|
<filter string="Creation Month" name="creation_month" domain="[]" context="{'group_by':'create_date'}"/> |
||||
|
<filter string="State" name="state" domain="[]" context="{'group_by': 'state'}"/> |
||||
|
<filter string="File type" name="file_type" domain="[]" context="{'group_by': 'file_type'}"/> |
||||
|
</group> |
||||
|
</search> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_attachment_queue" model="ir.actions.act_window"> |
||||
|
<field name="name">Attachments Queue</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">attachment.queue</field> |
||||
|
<field name="view_type">form</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
<field name="view_id" eval="False"/> |
||||
|
<field name="search_view_id" ref="view_attachment_queue_search"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="act_open_attachment_que_view_tree" model="ir.actions.act_window.view"> |
||||
|
<field eval="10" name="sequence"/> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="view_id" ref="view_attachment_queue_tree"/> |
||||
|
<field name="act_window_id" ref="action_attachment_queue"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="act_open_attachment_que_view_form" model="ir.actions.act_window.view"> |
||||
|
<field eval="10" name="sequence"/> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="view_id" ref="view_attachment_queue_form"/> |
||||
|
<field name="act_window_id" ref="action_attachment_queue"/> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem id="menu_attachment_queue" |
||||
|
parent="base.next_id_9" |
||||
|
sequence="20" |
||||
|
action="action_attachment_queue"/> |
||||
|
|
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue