diff --git a/fetchmail_attach_from_folder/README.rst b/fetchmail_attach_from_folder/README.rst index cedb493b4..6be026a71 100644 --- a/fetchmail_attach_from_folder/README.rst +++ b/fetchmail_attach_from_folder/README.rst @@ -60,11 +60,10 @@ grow indefinitely. Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed feedback -`here `_. - +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. Credits ======= @@ -73,6 +72,7 @@ Contributors ------------ * Holger Brunn +* Ronald Portier Icon ---- @@ -88,6 +88,8 @@ Maintainer This module is maintained by the OCA. -OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. To contribute to this module, please visit http://odoo-community.org. diff --git a/fetchmail_attach_from_folder/__init__.py b/fetchmail_attach_from_folder/__init__.py index 2567300b5..67e1e2b4c 100644 --- a/fetchmail_attach_from_folder/__init__.py +++ b/fetchmail_attach_from_folder/__init__.py @@ -1,25 +1,6 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import match_algorithm -from . import model +from . import models from . import wizard diff --git a/fetchmail_attach_from_folder/__manifest__.py b/fetchmail_attach_from_folder/__manifest__.py index d1fc175eb..49b5373f6 100644 --- a/fetchmail_attach_from_folder/__manifest__.py +++ b/fetchmail_attach_from_folder/__manifest__.py @@ -1,39 +1,25 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Email gateway - folders', 'summary': 'Attach mails in an IMAP folder to existing objects', - 'version': '8.0.1.0.1', - 'author': "Therp BV,Odoo Community Association (OCA)", + 'version': '10.0.1.0.0', + 'author': 'Therp BV,Odoo Community Association (OCA)', 'website': 'http://www.therp.nl', 'license': 'AGPL-3', - "category": "Tools", - "depends": ['fetchmail'], + 'category': 'Tools', + 'depends': ['fetchmail'], 'data': [ - 'view/fetchmail_server.xml', + 'views/fetchmail_server.xml', 'wizard/attach_mail_manually.xml', 'security/ir.model.access.csv', ], - 'installable': False, + 'installable': True, 'auto_install': False, + 'external_dependencies': { + 'python': [ + 'simplejson', + ], + }, } diff --git a/fetchmail_attach_from_folder/match_algorithm/__init__.py b/fetchmail_attach_from_folder/match_algorithm/__init__.py index baa099c37..0da5325ca 100644 --- a/fetchmail_attach_from_folder/match_algorithm/__init__.py +++ b/fetchmail_attach_from_folder/match_algorithm/__init__.py @@ -1,26 +1,7 @@ # -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import base from . import email_exact from . import email_domain -from . import openerp_standard +from . import odoo_standard diff --git a/fetchmail_attach_from_folder/match_algorithm/base.py b/fetchmail_attach_from_folder/match_algorithm/base.py index 34e7b3dbe..d40fc10be 100644 --- a/fetchmail_attach_from_folder/match_algorithm/base.py +++ b/fetchmail_attach_from_folder/match_algorithm/base.py @@ -1,43 +1,25 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -class base(object): - name = None - '''Name shown to the user''' +class Base(object): + name = None # Name shown to the user + # Fields on fetchmail_server folder required for this algorithm required_fields = [] - '''Fields on fetchmail_server folder required for this algorithm''' + # Fields on fetchmail_server folder readonly for this algorithm readonly_fields = [] - '''Fields on fetchmail_server folder readonly for this algorithm''' - def search_matches(self, cr, uid, conf, mail_message, mail_message_org): - '''Returns ids found for model with mail_message''' + def search_matches(self, folder, mail_message, mail_message_org): + """Returns recordset found for model with mail_message.""" return [] def handle_match( - self, cr, uid, connection, object_id, folder, - mail_message, mail_message_org, msgid, context=None): - '''Do whatever it takes to handle a match''' - return folder.server_id.attach_mail(connection, object_id, folder, - mail_message, msgid) + self, connection, match_object, folder, + mail_message, mail_message_org, msgid): + """Do whatever it takes to handle a match""" + folder.attach_mail(match_object, mail_message) + if folder.delete_matching: + connection.store(msgid, '+FLAGS', '\\DELETED') diff --git a/fetchmail_attach_from_folder/match_algorithm/email_domain.py b/fetchmail_attach_from_folder/match_algorithm/email_domain.py index 1f06b7e7c..6740ae724 100644 --- a/fetchmail_attach_from_folder/match_algorithm/email_domain.py +++ b/fetchmail_attach_from_folder/match_algorithm/email_domain.py @@ -1,45 +1,29 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from .email_exact import EmailExact -from .email_exact import email_exact +class EmailDomain(EmailExact): + """Search objects by domain name of email address. -class email_domain(email_exact): - '''Search objects by domain name of email address. - Beware of match_first here, this is most likely to get it wrong (gmail)''' + Beware of match_first here, this is most likely to get it wrong (gmail). + """ name = 'Domain of email address' - def search_matches(self, cr, uid, conf, mail_message, mail_message_org): - ids = super(email_domain, self).search_matches( - cr, uid, conf, mail_message, mail_message_org) - if not ids: + def search_matches(self, folder, mail_message, mail_message_org): + """Returns recordset of matching objects.""" + matches = super(EmailDomain, self).search_matches( + folder, mail_message, mail_message_org) + if not matches: + object_model = folder.env[folder.model_id.model] domains = [] - for addr in self._get_mailaddresses(conf, mail_message): + for addr in self._get_mailaddresses(folder, mail_message): domains.append(addr.split('@')[-1]) - ids = conf.pool.get(conf.model_id.model).search( - cr, uid, + matches = object_model.search( self._get_mailaddress_search_domain( - conf, mail_message, + folder, mail_message, operator='like', values=['%@' + domain for domain in set(domains)]), - order=conf.model_order) - return ids + order=folder.model_order) + return matches diff --git a/fetchmail_attach_from_folder/match_algorithm/email_exact.py b/fetchmail_attach_from_folder/match_algorithm/email_exact.py index a2225e083..20fa85db4 100644 --- a/fetchmail_attach_from_folder/match_algorithm/email_exact.py +++ b/fetchmail_attach_from_folder/match_algorithm/email_exact.py @@ -1,57 +1,40 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.tools.safe_eval import safe_eval +from odoo.tools.mail import email_split -from .base import base -from openerp.tools.safe_eval import safe_eval -from openerp.tools.mail import email_split +from .base import Base -class email_exact(base): - '''Search for exactly the mailadress as noted in the email''' +class EmailExact(Base): + """Search for exactly the mailadress as noted in the email""" name = 'Exact mailadress' required_fields = ['model_field', 'mail_field'] - def _get_mailaddresses(self, conf, mail_message): + def _get_mailaddresses(self, folder, mail_message): mailaddresses = [] - fields = conf.mail_field.split(',') + fields = folder.mail_field.split(',') for field in fields: if field in mail_message: mailaddresses += email_split(mail_message[field]) return [addr.lower() for addr in mailaddresses] def _get_mailaddress_search_domain( - self, conf, mail_message, operator='=', values=None): + self, folder, mail_message, operator='=', values=None): mailaddresses = values or self._get_mailaddresses( - conf, mail_message) + folder, mail_message) if not mailaddresses: return [(0, '=', 1)] search_domain = ((['|'] * (len(mailaddresses) - 1)) + [ - (conf.model_field, operator, addr) for addr in mailaddresses] + - safe_eval(conf.domain or '[]')) + (folder.model_field, operator, addr) for addr in mailaddresses] + + safe_eval(folder.domain or '[]')) return search_domain - def search_matches(self, cr, uid, conf, mail_message, mail_message_org): - conf_model = conf.pool.get(conf.model_id.model) - search_domain = self._get_mailaddress_search_domain(conf, mail_message) - return conf_model.search( - cr, uid, search_domain, order=conf.model_order) + def search_matches(self, folder, mail_message, mail_message_org): + """Returns recordset of matching objects.""" + object_model = folder.env[folder.model_id.model] + search_domain = self._get_mailaddress_search_domain( + folder, mail_message) + return object_model.search(search_domain, order=folder.model_order) diff --git a/fetchmail_attach_from_folder/match_algorithm/odoo_standard.py b/fetchmail_attach_from_folder/match_algorithm/odoo_standard.py new file mode 100644 index 000000000..a8a6983eb --- /dev/null +++ b/fetchmail_attach_from_folder/match_algorithm/odoo_standard.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from .base import Base + + +class OdooStandard(Base): + """No search at all. Use Odoo's standard mechanism to attach mails to + mail.thread objects. Note that this algorithm always matches.""" + name = 'Odoo standard' + readonly_fields = [ + 'model_field', + 'mail_field', + 'match_first', + 'domain', + 'model_order', + 'flag_nonmatching', + ] + + def search_matches(self, folder, mail_message, mail_message_org): + """Always match. Duplicates will be fished out by message_id""" + return [True] + + def handle_match( + self, connection, match_object, folder, + mail_message, mail_message_org, msgid): + thread_model = folder.env['mail.thread'] + thread_model.message_process( + folder.model_id.model, mail_message_org, + save_original=folder.server_id.original, + strip_attachments=(not folder.server_id.attach)) + if folder.delete_matching: + connection.store(msgid, '+FLAGS', '\\DELETED') diff --git a/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py b/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py deleted file mode 100644 index 159e82bfb..000000000 --- a/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from .base import base - - -class openerp_standard(base): - '''No search at all. Use OpenERP's standard mechanism to attach mails to - mail.thread objects. Note that this algorithm always matches.''' - - name = 'Odoo standard' - readonly_fields = [ - 'model_field', - 'mail_field', - 'match_first', - 'domain', - 'model_order', - 'flag_nonmatching', - ] - - def search_matches(self, cr, uid, conf, mail_message, mail_message_org): - '''Always match. Duplicates will be fished out by message_id''' - return [True] - - def handle_match( - self, cr, uid, connection, object_id, folder, - mail_message, mail_message_org, msgid, context): - result = folder.pool.get('mail.thread').message_process( - cr, uid, - folder.model_id.model, mail_message_org, - save_original=folder.server_id.original, - strip_attachments=(not folder.server_id.attach), - context=context - ) - - if folder.delete_matching: - connection.store(msgid, '+FLAGS', '\\DELETED') - - return [result] diff --git a/fetchmail_attach_from_folder/model/__init__.py b/fetchmail_attach_from_folder/model/__init__.py deleted file mode 100644 index 1073e5e38..000000000 --- a/fetchmail_attach_from_folder/model/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from . import fetchmail_server -from . import fetchmail_server_folder diff --git a/fetchmail_attach_from_folder/model/fetchmail_server.py b/fetchmail_attach_from_folder/model/fetchmail_server.py deleted file mode 100644 index c00c0d200..000000000 --- a/fetchmail_attach_from_folder/model/fetchmail_server.py +++ /dev/null @@ -1,266 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -import logging -import base64 -import simplejson -from lxml import etree -from openerp import models, fields, api, exceptions -from openerp.tools.translate import _ -from openerp.tools.safe_eval import safe_eval -from openerp.tools.misc import UnquoteEvalContext -_logger = logging.getLogger(__name__) - - -class fetchmail_server(models.Model): - _inherit = 'fetchmail.server' - - folder_ids = fields.One2many( - 'fetchmail.server.folder', 'server_id', 'Folders', - context={'active_test': False}) - object_id = fields.Many2one(required=False) - - _defaults = { - 'type': 'imap', - } - - def onchange_server_type( - self, cr, uid, ids, server_type=False, ssl=False, - object_id=False): - retval = super( - fetchmail_server, self).onchange_server_type(cr, uid, - ids, server_type, ssl, - object_id) - retval['value']['state'] = 'draft' - return retval - - def fetch_mail(self, cr, uid, ids, context=None): - if context is None: - context = {} - - check_original = [] - - for this in self.browse(cr, uid, ids, context): - if this.object_id: - check_original.append(this.id) - - context.update( - { - 'fetchmail_server_id': this.id, - 'server_type': this.type - }) - - connection = this.connect() - for folder in this.folder_ids.filtered('active'): - this.handle_folder(connection, folder) - connection.close() - - return super(fetchmail_server, self).fetch_mail( - cr, uid, check_original, context) - - @api.multi - def handle_folder(self, connection, folder): - '''Return ids of objects matched''' - - matched_object_ids = [] - - for this in self: - _logger.info( - 'start checking for emails in %s server %s', - folder.path, this.name) - - match_algorithm = folder.get_algorithm() - - if connection.select(folder.path)[0] != 'OK': - _logger.error( - 'Could not open mailbox %s on %s', - folder.path, this.server) - connection.select() - continue - result, msgids = this.get_msgids(connection) - if result != 'OK': - _logger.error( - 'Could not search mailbox %s on %s', - folder.path, this.server) - continue - - for msgid in msgids[0].split(): - matched_object_ids += this.apply_matching( - connection, folder, msgid, match_algorithm) - - _logger.info( - 'finished checking for emails in %s server %s', - folder.path, this.name) - - return matched_object_ids - - @api.multi - def get_msgids(self, connection): - '''Return imap ids of messages to process''' - return connection.search(None, 'UNDELETED') - - @api.multi - def apply_matching(self, connection, folder, msgid, match_algorithm): - '''Return ids of objects matched''' - - matched_object_ids = [] - - for this in self: - result, msgdata = connection.fetch(msgid, '(RFC822)') - - if result != 'OK': - _logger.error( - 'Could not fetch %s in %s on %s', - msgid, folder.path, this.server) - continue - - mail_message = self.env['mail.thread'].message_parse( - msgdata[0][1], save_original=this.original) - - if self.env['mail.message'].search( - [('message_id', '=', mail_message['message_id'])]): - continue - - found_ids = match_algorithm.search_matches( - self.env.cr, self.env.uid, folder, mail_message, msgdata[0][1]) - - if found_ids and (len(found_ids) == 1 or - folder.match_first): - try: - self.env.cr.execute('savepoint apply_matching') - match_algorithm.handle_match( - self.env.cr, self.env.uid, connection, - found_ids[0], folder, mail_message, - msgdata[0][1], msgid, self.env.context) - self.env.cr.execute('release savepoint apply_matching') - matched_object_ids += found_ids[:1] - except Exception: - self.env.cr.execute('rollback to savepoint apply_matching') - _logger.exception( - "Failed to fetch mail %s from %s", msgid, this.name) - elif folder.flag_nonmatching: - connection.store(msgid, '+FLAGS', '\\FLAGGED') - - return matched_object_ids - - @api.multi - def attach_mail(self, connection, object_id, folder, mail_message, msgid): - '''Return ids of messages created''' - - mail_message_ids = [] - - for this in self: - partner_id = None - if folder.model_id.model == 'res.partner': - partner_id = object_id - if 'partner_id' in self.env[folder.model_id.model]._columns: - partner_id = self.env[folder.model_id.model].browse(object_id)\ - .partner_id.id - - attachments = [] - if this.attach and mail_message.get('attachments'): - for attachment in mail_message['attachments']: - fname, fcontent = attachment - if isinstance(fcontent, unicode): - fcontent = fcontent.encode('utf-8') - data_attach = { - 'name': fname, - 'datas': base64.b64encode(str(fcontent)), - 'datas_fname': fname, - 'description': _('Mail attachment'), - 'res_model': folder.model_id.model, - 'res_id': object_id, - } - attachments.append( - self.env['ir.attachment'].create(data_attach)) - - mail_message_ids.append( - self.env['mail.message'].create({ - 'author_id': partner_id, - 'model': folder.model_id.model, - 'res_id': object_id, - 'type': 'email', - 'body': mail_message.get('body'), - 'subject': mail_message.get('subject'), - 'email_from': mail_message.get('from'), - 'date': mail_message.get('date'), - 'message_id': mail_message.get('message_id'), - 'attachment_ids': [(6, 0, [a.id for a in attachments])], - })) - - if folder.delete_matching: - connection.store(msgid, '+FLAGS', '\\DELETED') - return mail_message_ids - - def button_confirm_login(self, cr, uid, ids, context=None): - retval = super(fetchmail_server, self).button_confirm_login( - cr, uid, ids, context) - - for this in self.browse(cr, uid, ids, context): - this.write({'state': 'draft'}) - connection = this.connect() - connection.select() - for folder in this.folder_ids.filtered('active'): - if connection.select(folder.path)[0] != 'OK': - raise exceptions.ValidationError( - _('Mailbox %s not found!') % folder.path) - connection.close() - this.write({'state': 'done'}) - - return retval - - def fields_view_get(self, cr, user, view_id=None, view_type='form', - context=None, toolbar=False, submenu=False): - result = super(fetchmail_server, self).fields_view_get( - cr, user, view_id, view_type, context, toolbar, submenu) - - if view_type == 'form': - view = etree.fromstring( - result['fields']['folder_ids']['views']['form']['arch']) - modifiers = {} - docstr = '' - for algorithm in self.pool['fetchmail.server.folder']\ - ._get_match_algorithms().itervalues(): - for modifier in ['required', 'readonly']: - for field in getattr(algorithm, modifier + '_fields'): - modifiers.setdefault(field, {}) - modifiers[field].setdefault(modifier, []) - if modifiers[field][modifier]: - modifiers[field][modifier].insert(0, '|') - modifiers[field][modifier].append( - ("match_algorithm", "==", algorithm.__name__)) - docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \ - '\n\n' - - for field in view.xpath('//field'): - if field.tag == 'field' and field.get('name') in modifiers: - field.set('modifiers', simplejson.dumps( - dict( - safe_eval(field.attrib['modifiers'], - UnquoteEvalContext({})), - **modifiers[field.attrib['name']]))) - if (field.tag == 'field' and - field.get('name') == 'match_algorithm'): - field.set('help', docstr) - result['fields']['folder_ids']['views']['form']['arch'] = \ - etree.tostring(view) - - return result diff --git a/fetchmail_attach_from_folder/model/fetchmail_server_folder.py b/fetchmail_attach_from_folder/model/fetchmail_server_folder.py deleted file mode 100644 index 7ea7ef3bb..000000000 --- a/fetchmail_attach_from_folder/model/fetchmail_server_folder.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -######################################################################## -from openerp import api, models, fields -from .. import match_algorithm - - -class fetchmail_server_folder(models.Model): - _name = 'fetchmail.server.folder' - _rec_name = 'path' - _order = 'sequence' - - def _get_match_algorithms(self): - def get_all_subclasses(cls): - return (cls.__subclasses__() + - [subsub - for sub in cls.__subclasses__() - for subsub in get_all_subclasses(sub)]) - return dict([(cls.__name__, cls) - for cls in get_all_subclasses( - match_algorithm.base.base)]) - - def _get_match_algorithms_sel(self): - algorithms = [] - for cls in self._get_match_algorithms().itervalues(): - algorithms.append((cls.__name__, cls.name)) - algorithms.sort() - return algorithms - - sequence = fields.Integer('Sequence') - path = fields.Char( - 'Path', - help="The path to your mail folder. Typically would be something like " - "'INBOX.myfolder'", required=True) - model_id = fields.Many2one( - 'ir.model', 'Model', required=True, - help='The model to attach emails to') - model_field = fields.Char( - 'Field (model)', - help='The field in your model that contains the field to match ' - 'against.\n' - 'Examples:\n' - "'email' if your model is res.partner, or " - "'partner_id.email' if you're matching sale orders") - model_order = fields.Char( - 'Order (model)', - help='Field(s) to order by, this mostly useful in conjunction ' - "with 'Use 1st match'") - match_algorithm = fields.Selection( - _get_match_algorithms_sel, - 'Match algorithm', required=True, - help='The algorithm used to determine which object an email matches.') - mail_field = fields.Char( - 'Field (email)', - help='The field in the email used for matching. Typically ' - "this is 'to' or 'from'") - server_id = fields.Many2one('fetchmail.server', 'Server') - delete_matching = fields.Boolean( - 'Delete matches', - help='Delete matched emails from server') - flag_nonmatching = fields.Boolean( - 'Flag nonmatching', - help="Flag emails in the server that don't match any object in Odoo") - match_first = fields.Boolean( - 'Use 1st match', - help='If there are multiple matches, use the first one. If ' - 'not checked, multiple matches count as no match at all') - domain = fields.Char( - 'Domain', - help='Fill in a search filter to narrow down objects to match') - msg_state = fields.Selection( - [ - ('sent', 'Sent'), - ('received', 'Received'), - ], - 'Message state', - help='The state messages fetched from this folder should be ' - 'assigned in Odoo') - active = fields.Boolean('Active') - - _defaults = { - 'flag_nonmatching': True, - 'msg_state': 'received', - 'active': True, - } - - @api.multi - def get_algorithm(self): - return self._get_match_algorithms()[self.match_algorithm]() - - @api.multi - def button_attach_mail_manually(self): - return { - 'type': 'ir.actions.act_window', - 'res_model': 'fetchmail.attach.mail.manually', - 'target': 'new', - 'context': dict(self.env.context, default_folder_id=self.id), - 'view_type': 'form', - 'view_mode': 'form', - } diff --git a/fetchmail_attach_from_folder/models/__init__.py b/fetchmail_attach_from_folder/models/__init__.py new file mode 100644 index 000000000..a562a0898 --- /dev/null +++ b/fetchmail_attach_from_folder/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import fetchmail_server +from . import fetchmail_server_folder diff --git a/fetchmail_attach_from_folder/models/fetchmail_server.py b/fetchmail_attach_from_folder/models/fetchmail_server.py new file mode 100644 index 000000000..cb07cdc61 --- /dev/null +++ b/fetchmail_attach_from_folder/models/fetchmail_server.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import simplejson +from lxml import etree + +from odoo import _, api, exceptions, fields, models +from odoo.tools.safe_eval import safe_eval +from odoo.tools.misc import UnquoteEvalContext + + +class FetchmailServer(models.Model): + _inherit = 'fetchmail.server' + + folder_ids = fields.One2many( + comodel_name='fetchmail.server.folder', + inverse_name='server_id', + string='Folders', + context={'active_test': False}) + object_id = fields.Many2one(required=False) # comodel_name='ir.model' + type = fields.Selection(default='imap') + folders_only = fields.Boolean( + string='Only folders, not inbox', + help="Check this field to leave imap inbox alone" + " and only retrieve mail from configured folders.") + + @api.onchange('type', 'is_ssl', 'object_id') + def onchange_server_type(self): + super(FetchmailServer, self).onchange_server_type() + self.state = 'draft' + + @api.onchange('folder_ids') + def onchange_folder_ids(self): + self.onchange_server_type() + + @api.multi + def fetch_mail(self): + for this in self: + if not this.folders_only: + super(FetchmailServer, this).fetch_mail() + for folder in this.folder_ids.filtered('active'): + folder.retrieve_imap_folder() + return True + + @api.multi + def button_confirm_login(self): + retval = super(FetchmailServer, self).button_confirm_login() + for this in self: + this.write({'state': 'draft'}) + connection = this.connect() + connection.select() + for folder in this.folder_ids.filtered('active'): + if connection.select(folder.path)[0] != 'OK': + raise exceptions.ValidationError( + _('Mailbox %s not found!') % folder.path) + connection.close() + this.write({'state': 'done'}) + return retval + + def fields_view_get( + self, view_id=None, view_type='form', + toolbar=False, submenu=False): + """Set modifiers for form fields in folder_ids depending on algorithm. + + A field will be readonly and/or required if this is specified in the + algorithm. + """ + result = super(FetchmailServer, self).fields_view_get( + view_id=view_id, view_type=view_type, toolbar=toolbar, + submenu=submenu) + if view_type == 'form': + view = etree.fromstring( + result['fields']['folder_ids']['views']['form']['arch']) + modifiers = {} + docstr = '' + folder_model = self.env['fetchmail.server.folder'] + match_algorithms = folder_model._get_match_algorithms() + for algorithm in match_algorithms.itervalues(): + for modifier in ['required', 'readonly']: + for field in getattr(algorithm, modifier + '_fields'): + modifiers.setdefault(field, {}) + modifiers[field].setdefault(modifier, []) + if modifiers[field][modifier]: + modifiers[field][modifier].insert(0, '|') + modifiers[field][modifier].append( + ("match_algorithm", "==", algorithm.__name__)) + docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \ + '\n\n' + for field in view.xpath('//field'): + if field.tag == 'field' and field.get('name') in modifiers: + patched_modifiers = field.attrib['modifiers'].replace( + 'false', 'False').replace('true', 'True') + original_dict = safe_eval( + patched_modifiers, + UnquoteEvalContext({}), + nocopy=True) + modifier_dict = modifiers[field.attrib['name']] + combined_dict = dict(original_dict, **modifier_dict) + field.set('modifiers', simplejson.dumps(combined_dict)) + if (field.tag == 'field' and + field.get('name') == 'match_algorithm'): + field.set('help', docstr) + result['fields']['folder_ids']['views']['form']['arch'] = \ + etree.tostring(view) + return result diff --git a/fetchmail_attach_from_folder/models/fetchmail_server_folder.py b/fetchmail_attach_from_folder/models/fetchmail_server_folder.py new file mode 100644 index 000000000..1c1ee7378 --- /dev/null +++ b/fetchmail_attach_from_folder/models/fetchmail_server_folder.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import base64 +import logging + +from odoo import _, api, models, fields + +from .. import match_algorithm + + +_logger = logging.getLogger(__name__) + + +class FetchmailServerFolder(models.Model): + _name = 'fetchmail.server.folder' + _rec_name = 'path' + _order = 'sequence' + + def _get_match_algorithms(self): + def get_all_subclasses(cls): + return (cls.__subclasses__() + + [subsub + for sub in cls.__subclasses__() + for subsub in get_all_subclasses(sub)]) + return dict([(cls.__name__, cls) + for cls in get_all_subclasses( + match_algorithm.base.Base)]) + + def _get_match_algorithms_sel(self): + algorithms = [] + for cls in self._get_match_algorithms().itervalues(): + algorithms.append((cls.__name__, cls.name)) + algorithms.sort() + return algorithms + + sequence = fields.Integer('Sequence') + path = fields.Char( + 'Path', + help="The path to your mail folder. Typically would be something like " + "'INBOX.myfolder'", required=True) + model_id = fields.Many2one( + 'ir.model', 'Model', required=True, + help='The model to attach emails to') + model_field = fields.Char( + 'Field (model)', + help='The field in your model that contains the field to match ' + 'against.\n' + 'Examples:\n' + "'email' if your model is res.partner, or " + "'partner_id.email' if you're matching sale orders") + model_order = fields.Char( + 'Order (model)', + help='Field(s) to order by, this mostly useful in conjunction ' + "with 'Use 1st match'") + match_algorithm = fields.Selection( + _get_match_algorithms_sel, + 'Match algorithm', required=True, + help='The algorithm used to determine which object an email matches.') + mail_field = fields.Char( + 'Field (email)', + help='The field in the email used for matching. Typically ' + "this is 'to' or 'from'") + server_id = fields.Many2one('fetchmail.server', 'Server') + delete_matching = fields.Boolean( + 'Delete matches', + help='Delete matched emails from server') + flag_nonmatching = fields.Boolean( + 'Flag nonmatching', + default=True, + help="Flag emails in the server that don't match any object in Odoo") + match_first = fields.Boolean( + 'Use 1st match', + help='If there are multiple matches, use the first one. If ' + 'not checked, multiple matches count as no match at all') + domain = fields.Char( + 'Domain', + help='Fill in a search filter to narrow down objects to match') + msg_state = fields.Selection( + selection=[('sent', 'Sent'), ('received', 'Received')], + string='Message state', + default='received', + help='The state messages fetched from this folder should be ' + 'assigned in Odoo') + active = fields.Boolean('Active', default=True) + + @api.multi + def get_algorithm(self): + return self._get_match_algorithms()[self.match_algorithm]() + + @api.multi + def button_attach_mail_manually(self): + return { + 'type': 'ir.actions.act_window', + 'res_model': 'fetchmail.attach.mail.manually', + 'target': 'new', + 'context': dict(self.env.context, default_folder_id=self.id), + 'view_type': 'form', + 'view_mode': 'form'} + + @api.multi + def get_msgids(self, connection): + """Return imap ids of messages to process""" + return connection.search(None, 'UNDELETED') + + @api.multi + def retrieve_imap_folder(self): + """Retrieve all mails for one IMAP folder. + + For each folder on each server we create a separate connection. + """ + for this in self: + server = this.server_id + try: + _logger.info( + 'start checking for emails in folder %s on server %s', + this.path, server.name) + imap_server = server.connect() + if imap_server.select(this.path)[0] != 'OK': + _logger.error( + 'Could not open mailbox %s on %s', + this.path, server.name) + continue + result, msgids = this.get_msgids(imap_server) + if result != 'OK': + _logger.error( + 'Could not search mailbox %s on %s', + this.path, server.name) + continue + match_algorithm = this.get_algorithm() + for msgid in msgids[0].split(): + this.apply_matching( + imap_server, msgid, match_algorithm) + _logger.info( + 'finished checking for emails in %s server %s', + this.path, server.name) + except Exception: + _logger.info(_( + "General failure when trying to fetch mail from" + " %s server %s."), + server.type, server.name, exc_info=True) + finally: + if imap_server: + imap_server.close() + imap_server.logout() + + @api.multi + def apply_matching(self, connection, msgid, match_algorithm): + """Return ids of objects matched""" + self.ensure_one() + result, msgdata = connection.fetch(msgid, '(RFC822)') + if result != 'OK': + _logger.error( + 'Could not fetch %s in %s on %s', + msgid, self.path, self.server_id.server) + return + mail_message = self.env['mail.thread'].message_parse( + msgdata[0][1], save_original=self.server_id.original) + if self.env['mail.message'].search( + [('message_id', '=', mail_message['message_id'])]): + # Ignore mails that have been handled already + return + matches = match_algorithm.search_matches( + self, mail_message, msgdata[0][1]) + if matches and (len(matches) == 1 or self.match_first): + try: + self.env.cr.execute('savepoint apply_matching') + match_algorithm.handle_match( + connection, + matches[0], self, mail_message, + msgdata[0][1], msgid) + self.env.cr.execute('release savepoint apply_matching') + except Exception: + self.env.cr.execute('rollback to savepoint apply_matching') + _logger.exception( + "Failed to fetch mail %s from %s", + msgid, self.server_id.name) + elif self.flag_nonmatching: + connection.store(msgid, '+FLAGS', '\\FLAGGED') + + @api.multi + def attach_mail(self, match_object, mail_message): + """Attach mail to match_object.""" + self.ensure_one() + partner = False + model_name = self.model_id.model + if model_name == 'res.partner': + partner = match_object + elif 'partner_id' in self.env[model_name]._fields: + partner = match_object.partner_id + attachments = [] + if self.server_id.attach and mail_message.get('attachments'): + for attachment in mail_message['attachments']: + # Attachment should at least have filename and data, but + # might have some extra element(s) + if len(attachment) < 2: + continue + fname, fcontent = attachment[:2] + if isinstance(fcontent, unicode): + fcontent = fcontent.encode('utf-8') + data_attach = { + 'name': fname, + 'datas': base64.b64encode(str(fcontent)), + 'datas_fname': fname, + 'description': _('Mail attachment'), + 'res_model': model_name, + 'res_id': match_object.id} + attachments.append( + self.env['ir.attachment'].create(data_attach)) + self.env['mail.message'].create({ + 'author_id': partner and partner.id or False, + 'model': model_name, + 'res_id': match_object.id, + 'message_type': 'email', + 'body': mail_message.get('body'), + 'subject': mail_message.get('subject'), + 'email_from': mail_message.get('from'), + 'date': mail_message.get('date'), + 'message_id': mail_message.get('message_id'), + 'attachment_ids': [(6, 0, [a.id for a in attachments])]}) diff --git a/fetchmail_attach_from_folder/tests/__init__.py b/fetchmail_attach_from_folder/tests/__init__.py index 1da15b8a0..c2f51343b 100644 --- a/fetchmail_attach_from_folder/tests/__init__.py +++ b/fetchmail_attach_from_folder/tests/__init__.py @@ -1,21 +1,4 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 Therp BV (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# Copyright - 2015-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_match_algorithms diff --git a/fetchmail_attach_from_folder/tests/test_match_algorithms.py b/fetchmail_attach_from_folder/tests/test_match_algorithms.py index 3e4e8517a..fa2e820f3 100644 --- a/fetchmail_attach_from_folder/tests/test_match_algorithms.py +++ b/fetchmail_attach_from_folder/tests/test_match_algorithms.py @@ -1,41 +1,70 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 Therp BV (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp import models -from openerp.tests.common import TransactionCase -from openerp.addons.fetchmail_attach_from_folder.match_algorithm import ( - email_exact, email_domain, openerp_standard) +# Copyright - 2015-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import models +from odoo.tests.common import TransactionCase + +from ..match_algorithm import email_exact, email_domain, odoo_standard + +MSG_BODY = [ + ('1 (RFC822 {1149}', + 'Return-Path: \r\n' + 'Delivered-To: demo@yourcompany.example.com\r\n' + 'Received: from localhost (localhost [127.0.0.1])\r\n' + '\tby vanaheim.acme.com (Postfix) with ESMTP id 14A3183163\r\n' + '\tfor ;' + ' Mon, 26 Mar 2018 16:03:52 +0200 (CEST)\r\n' + 'To: Test User \r\n' + 'From: Ronald Portier \r\n' + 'Subject: test\r\n' + 'Message-ID: <485a8041-d560-a981-5afc-d31c1f136748@acme.com>\r\n' + 'Date: Mon, 26 Mar 2018 16:03:51 +0200\r\n' + 'User-Agent: Mock Test\r\n' + 'MIME-Version: 1.0\r\n' + 'Content-Type: text/plain; charset=utf-8\r\n' + 'Content-Language: en-US\r\n' + 'Content-Transfer-Encoding: 7bit\r\n\r\n' + 'Hallo Wereld!\r\n'), + ')'] + + +class MockConnection(): + + def store(self, msgid, msg_item, value): + """Mock store command.""" + return 'OK' + + def fetch(self, msgid, parts): + """Return RFC822 formatted message.""" + return ('OK', MSG_BODY) class TestMatchAlgorithms(TransactionCase): - def do_matching(self, match_algorithm, expected_xmlid, conf, mail_message, - mail_message_org=None): + + def _get_base_folder(self): + server_model = self.env['fetchmail.server'] + folder_model = self.env['fetchmail.server.folder'] + folder = folder_model.browse([models.NewId()]) + folder.model_id = self.env.ref('base.model_res_partner').id + folder.model_field = 'email' + folder.match_algorithm = 'EmailExact' + folder.mail_field = 'to,from' + folder.server_id = server_model.browse([models.NewId()]) + return folder + + def do_matching( + self, match_algorithm, expected_xmlid, folder, mail_message, + mail_message_org=None): matcher = match_algorithm() matches = matcher.search_matches( - self.env.cr, self.env.uid, conf, mail_message, mail_message_org) + folder, mail_message, mail_message_org) self.assertEqual(len(matches), 1) self.assertEqual( - matches[0], self.env.ref(expected_xmlid).id) + matches[0], self.env.ref(expected_xmlid)) + connection = MockConnection() matcher.handle_match( - self.env.cr, self.env.uid, None, matches[0], conf, mail_message, - mail_message_org, None) + connection, matches[0], folder, mail_message, mail_message_org, + None) def test_email_exact(self): mail_message = { @@ -43,15 +72,11 @@ class TestMatchAlgorithms(TransactionCase): 'to': 'demo@yourcompany.example.com', 'from': 'someone@else.com', } - conf = self.env['fetchmail.server.folder'].browse([models.NewId()]) - conf.model_id = self.env.ref('base.model_res_partner').id - conf.model_field = 'email' - conf.match_algorithm = 'email_exact' - conf.mail_field = 'to,from' - conf.server_id = self.env['fetchmail.server'].browse([models.NewId()]) + folder = self._get_base_folder() + folder.match_algorithm = 'EmailExact' self.do_matching( - email_exact.email_exact, 'base.user_demo_res_partner', - conf, mail_message) + email_exact.EmailExact, 'base.user_demo_res_partner', + folder, mail_message) self.assertEqual( self.env.ref('base.user_demo_res_partner').message_ids.subject, mail_message['subject']) @@ -61,22 +86,18 @@ class TestMatchAlgorithms(TransactionCase): 'subject': 'Testsubject', 'to': 'test@seagate.com', 'from': 'someone@else.com', - } - conf = self.env['fetchmail.server.folder'].browse([models.NewId()]) - conf.model_id = self.env.ref('base.model_res_partner').id - conf.model_field = 'email' - conf.match_algorithm = 'email_domain' - conf.mail_field = 'to,from' - conf.use_first_match = True - conf.server_id = self.env['fetchmail.server'].browse([models.NewId()]) + 'attachments': [('hello.txt', 'Hello World!')]} + folder = self._get_base_folder() + folder.match_algorithm = 'EmailDomain' + folder.use_first_match = True self.do_matching( - email_domain.email_domain, 'base.res_partner_address_31', - conf, mail_message) + email_domain.EmailDomain, 'base.res_partner_address_31', + folder, mail_message) self.assertEqual( self.env.ref('base.res_partner_address_31').message_ids.subject, mail_message['subject']) - def test_openerp_standard(self): + def test_odoo_standard(self): mail_message_org = ( "To: demo@yourcompany.example.com\n" "From: someone@else.com\n" @@ -84,20 +105,32 @@ class TestMatchAlgorithms(TransactionCase): "Message-Id: 42\n" "Hello world" ) - conf = self.env['fetchmail.server.folder'].browse([models.NewId()]) - conf.model_id = self.env.ref('base.model_res_partner').id - conf.model_field = 'email' - conf.match_algorithm = 'openerp_standard' - conf.mail_field = 'to,from' - conf.server_id = self.env['fetchmail.server'].browse([models.NewId()]) - matcher = openerp_standard.openerp_standard() + folder = self._get_base_folder() + folder.match_algorithm = 'OdooStandard' + matcher = odoo_standard.OdooStandard() matches = matcher.search_matches( - self.env.cr, self.env.uid, conf, None, mail_message_org) + folder, None, mail_message_org) self.assertEqual(len(matches), 1) matcher.handle_match( - self.env.cr, self.env.uid, None, matches[0], conf, None, - mail_message_org, None, None) + None, matches[0], folder, None, mail_message_org, None) self.assertIn( 'Hello world', self.env['mail.message'] .search([('subject', '=', 'testsubject')]).body) + + def test_apply_matching_exact(self): + folder = self._get_base_folder() + folder.match_algorithm = 'EmailExact' + connection = MockConnection() + msgid = "<485a8041-d560-a981-5afc-d31c1f136748@acme.com>" + matcher = email_exact.EmailExact() + folder.apply_matching(connection, msgid, matcher) + + def test_field_view_get(self): + """For the moment just check execution withouth errors.""" + server_model = self.env['fetchmail.server'] + view = server_model.fields_view_get() + self.assertTrue(view) + self.assertIn( + 'match_algorithm', + view['fields']['folder_ids']['views']['form']['arch']) diff --git a/fetchmail_attach_from_folder/view/fetchmail_server.xml b/fetchmail_attach_from_folder/view/fetchmail_server.xml deleted file mode 100644 index e5160cc8a..000000000 --- a/fetchmail_attach_from_folder/view/fetchmail_server.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - fetchmail.server.form - fetchmail.server - - - - - {'required': [('type', '!=', 'imap')]} - - - - - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
diff --git a/fetchmail_attach_from_folder/views/fetchmail_server.xml b/fetchmail_attach_from_folder/views/fetchmail_server.xml new file mode 100644 index 000000000..3a1b2e010 --- /dev/null +++ b/fetchmail_attach_from_folder/views/fetchmail_server.xml @@ -0,0 +1,80 @@ + + + + + fetchmail.server.form + fetchmail.server + + + + {'required': [('type', '!=', 'imap')]} + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
diff --git a/fetchmail_attach_from_folder/wizard/__init__.py b/fetchmail_attach_from_folder/wizard/__init__.py index 1f98c5a26..f7b0d7875 100644 --- a/fetchmail_attach_from_folder/wizard/__init__.py +++ b/fetchmail_attach_from_folder/wizard/__init__.py @@ -1,23 +1,4 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import attach_mail_manually diff --git a/fetchmail_attach_from_folder/wizard/attach_mail_manually.py b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py index 666de38bf..23b16ebcd 100644 --- a/fetchmail_attach_from_folder/wizard/attach_mail_manually.py +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py @@ -1,30 +1,15 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013 Therp BV () -# All Rights Reserved -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp import fields, models +# -*- coding: utf-8 -*- +# Copyright - 2013-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging + +from odoo import api, fields, models + + _logger = logging.getLogger(__name__) -class attach_mail_manually(models.TransientModel): +class AttachMailManually(models.TransientModel): _name = 'fetchmail.attach.mail.manually' folder_id = fields.Many2one( @@ -32,17 +17,13 @@ class attach_mail_manually(models.TransientModel): mail_ids = fields.One2many( 'fetchmail.attach.mail.manually.mail', 'wizard_id', 'Emails') - def default_get(self, cr, uid, fields_list, context=None): - if context is None: - context = {} - - defaults = super(attach_mail_manually, self).default_get( - cr, uid, fields_list, context - ) - - for folder in self.pool.get('fetchmail.server.folder').browse( - cr, uid, - [context.get('default_folder_id')], context): + @api.model + def default_get(self, fields_list): + folder_model = self.env['fetchmail.server.folder'] + thread_model = self.env['mail.thread'] + defaults = super(AttachMailManually, self).default_get(fields_list) + default_folder_id = self.env.context.get('default_folder_id') + for folder in folder_model.browse([default_folder_id]): defaults['mail_ids'] = [] connection = folder.server_id.connect() connection.select(folder.path) @@ -50,70 +31,72 @@ class attach_mail_manually(models.TransientModel): None, 'FLAGGED' if folder.flag_nonmatching else 'UNDELETED') if result != 'OK': - _logger.error('Could not search mailbox %s on %s', - folder.path, folder.server_id.name) + _logger.error( + 'Could not search mailbox %s on %s', + folder.path, folder.server_id.name) continue for msgid in msgids[0].split(): result, msgdata = connection.fetch(msgid, '(RFC822)') if result != 'OK': - _logger.error('Could not fetch %s in %s on %s', - msgid, folder.path, folder.server_id.name) + _logger.error( + 'Could not fetch %s in %s on %s', + msgid, folder.path, folder.server_id.name) continue - mail_message = self.pool.get('mail.thread').message_parse( - cr, uid, msgdata[0][1], - save_original=folder.server_id.original, - context=context - ) + mail_message = thread_model.message_parse( + msgdata[0][1], + save_original=folder.server_id.original) defaults['mail_ids'].append((0, 0, { 'msgid': msgid, 'subject': mail_message.get('subject', ''), 'date': mail_message.get('date', ''), - 'object_id': '%s,-1' % folder.model_id.model, - })) + 'object_id': '%s,-1' % folder.model_id.model})) connection.close() - return defaults - def attach_mails(self, cr, uid, ids, context=None): - for this in self.browse(cr, uid, ids, context): + @api.multi + def attach_mails(self): + thread_model = self.env['mail.thread'] + for this in self: + folder = this.folder_id + server = folder.server_id + connection = server.connect() + connection.select(folder.path) for mail in this.mail_ids: - connection = this.folder_id.server_id.connect() - connection.select(this.folder_id.path) + if not mail.object_id: + continue result, msgdata = connection.fetch(mail.msgid, '(RFC822)') if result != 'OK': - _logger.error('Could not fetch %s in %s on %s', - mail.msgid, this.folder_id.path, this.server) + _logger.error( + 'Could not fetch %s in %s on %s', + mail.msgid, folder.path, server) continue - - mail_message = self.pool.get('mail.thread').message_parse( - cr, uid, msgdata[0][1], - save_original=this.folder_id.server_id.original, - context=context) - - this.folder_id.server_id.attach_mail( - connection, - mail.object_id.id, this.folder_id, mail_message, - mail.msgid - ) - connection.close() + mail_message = thread_model.message_parse( + msgdata[0][1], save_original=server.original) + folder.attach_mail(mail.object_id, mail_message) + if folder.delete_matching: + connection.store(mail.msgid, '+FLAGS', '\\DELETED') + elif folder.flag_nonmatching: + connection.store(mail.msgid, '-FLAGS', '\\FLAGGED') + connection.close() return {'type': 'ir.actions.act_window_close'} - def fields_view_get(self, cr, user, view_id=None, view_type='form', - context=None, toolbar=False, submenu=False): - result = super(attach_mail_manually, self).fields_view_get( - cr, user, view_id, view_type, context, toolbar, submenu) - + @api.model + def fields_view_get( + self, view_id=None, view_type='form', + toolbar=False, submenu=False): + result = super(AttachMailManually, self).fields_view_get( + view_id=view_id, view_type=view_type, toolbar=toolbar, + submenu=submenu) tree = result['fields']['mail_ids']['views']['tree'] - for folder in self.pool['fetchmail.server.folder'].browse( - cr, user, [context.get('default_folder_id')], context): + folder_model = self.env['fetchmail.server.folder'] + default_folder_id = self.env.context.get('default_folder_id') + for folder in folder_model.browse([default_folder_id]): tree['fields']['object_id']['selection'] = [ - (folder.model_id.model, folder.model_id.name) - ] - + (folder.model_id.model, folder.model_id.name)] return result -class attach_mail_manually_mail(models.TransientModel): +class AttachMailManuallyMail(models.TransientModel): _name = 'fetchmail.attach.mail.manually.mail' wizard_id = fields.Many2one( @@ -124,6 +107,5 @@ class attach_mail_manually_mail(models.TransientModel): object_id = fields.Reference( lambda self: [ (m.model, m.name) - for m in self.env['ir.model'].search([]) - ], + for m in self.env['ir.model'].search([])], string='Object') diff --git a/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml b/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml index 320ccaf4a..72e2eab32 100644 --- a/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml @@ -1,28 +1,35 @@ - - - - fetchmail.attach.mail.manually - fetchmail.attach.mail.manually - -
- - - - - - - - - - -
-
-
-
-
-
-
+ + + + fetchmail.attach.mail.manually + fetchmail.attach.mail.manually + +
+ + + + + + + + + + + +
+
+
+ +