From 862d5a6bb896b02c0c0487d85d12508c94c00302 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 27 Jan 2015 14:57:38 +0100 Subject: [PATCH 1/8] [REN] preliminary migration of fetchmail_attach_from_folder --- .../model/fetchmail_server_folder.py | 131 ---------------- fetchmail_attach_from_folder/README.rst | 46 ++++++ .../__init__.py | 5 +- .../__openerp__.py | 18 +-- .../match_algorithm/__init__.py | 8 +- .../match_algorithm/base.py | 4 +- .../match_algorithm/email_domain.py | 4 +- .../match_algorithm/email_exact.py | 2 +- .../match_algorithm/openerp_standard.py | 2 +- .../model}/__init__.py | 5 +- .../model/fetchmail_server.py | 147 ++++++++---------- .../model/fetchmail_server_folder.py | 115 ++++++++++++++ .../security/ir.model.access.csv | 0 .../static/description/icon.png | Bin 0 -> 12585 bytes .../view/fetchmail_server.xml | 0 .../wizard/__init__.py | 2 +- .../wizard/attach_mail_manually.py | 4 +- .../wizard/attach_mail_manually.xml | 0 18 files changed, 250 insertions(+), 243 deletions(-) delete mode 100644 __unported__/fetchmail_attach_from_folder/model/fetchmail_server_folder.py create mode 100644 fetchmail_attach_from_folder/README.rst rename {__unported__/fetchmail_attach_from_folder/model => fetchmail_attach_from_folder}/__init__.py (93%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/__openerp__.py (74%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/match_algorithm/__init__.py (90%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/match_algorithm/base.py (90%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/match_algorithm/email_domain.py (94%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/match_algorithm/email_exact.py (99%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/match_algorithm/openerp_standard.py (98%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder/model}/__init__.py (93%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/model/fetchmail_server.py (64%) create mode 100644 fetchmail_attach_from_folder/model/fetchmail_server_folder.py rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/security/ir.model.access.csv (100%) create mode 100644 fetchmail_attach_from_folder/static/description/icon.png rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/view/fetchmail_server.xml (100%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/wizard/__init__.py (96%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/wizard/attach_mail_manually.py (98%) rename {__unported__/fetchmail_attach_from_folder => fetchmail_attach_from_folder}/wizard/attach_mail_manually.xml (100%) diff --git a/__unported__/fetchmail_attach_from_folder/model/fetchmail_server_folder.py b/__unported__/fetchmail_attach_from_folder/model/fetchmail_server_folder.py deleted file mode 100644 index 8021332e2..000000000 --- a/__unported__/fetchmail_attach_from_folder/model/fetchmail_server_folder.py +++ /dev/null @@ -1,131 +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.osv import fields -from openerp.osv.orm import Model -from .. import match_algorithm - - -class fetchmail_server_folder(Model): - _name = 'fetchmail.server.folder' - _rec_name = 'path' - - 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, cr, uid, context=None): - algorithms = [] - for cls in self._get_match_algorithms().itervalues(): - algorithms.append((cls.__name__, cls.name)) - algorithms.sort() - return algorithms - - _columns = { - 'sequence': fields.integer('Sequence'), - 'path': fields.char( - 'Path', size=256, 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)', size=128, - 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)', size=128, - help='Fields to order by, this mostly useful in conjunction ' - "with 'Use 1st match'" - ), - 'match_algorithm': fields.selection( - _get_match_algorithms_sel, - 'Match algorithm', required=True, translate=True, - help='The algorithm used to determine which object an email ' - 'matches.' - ), - 'mail_field': fields.char( - 'Field (email)', size=128, - 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 OpenERP' - ), - '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', size=128, 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 OpenERP' - ), - } - - _defaults = { - 'flag_nonmatching': True, - 'msg_state': 'received', - } - - def get_algorithm(self, cr, uid, ids, context=None): - for this in self.browse(cr, uid, ids, context): - return self._get_match_algorithms()[this.match_algorithm]() - - def button_attach_mail_manually(self, cr, uid, ids, context=None): - for this in self.browse(cr, uid, ids, context): - context.update({'default_folder_id': this.id}) - return { - 'type': 'ir.actions.act_window', - 'res_model': 'fetchmail.attach.mail.manually', - 'target': 'new', - 'context': context, - 'view_type': 'form', - 'view_mode': 'form', - } diff --git a/fetchmail_attach_from_folder/README.rst b/fetchmail_attach_from_folder/README.rst new file mode 100644 index 000000000..f7380d124 --- /dev/null +++ b/fetchmail_attach_from_folder/README.rst @@ -0,0 +1,46 @@ +Email gateway - folders +======================= + +Adds the possibility to attach emails from a certain IMAP folder to objects, +ie partners. Matching is done via several algorithms, ie email address, email +address's domain or the original Odoo algorithm. + +This gives a simple possibility to archive emails in Odoo without a mail +client integration. + +Configuration +============= + +In your fetchmail configuration, you'll find a new field `folders`. Add your +folders here in IMAP notation [TODO] + +Usage +===== + +A widespread configuration is to have a shared mailbox with several folders [TODO] + +Credits +======= + +Contributors +------------ + +* Holger Brunn + +Icon +---- + +http://commons.wikimedia.org/wiki/File:Crystal_Clear_filesystem_folder_favorites.png + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +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. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/__unported__/fetchmail_attach_from_folder/model/__init__.py b/fetchmail_attach_from_folder/__init__.py similarity index 93% rename from __unported__/fetchmail_attach_from_folder/model/__init__.py rename to fetchmail_attach_from_folder/__init__.py index d7e030949..2567300b5 100644 --- a/__unported__/fetchmail_attach_from_folder/model/__init__.py +++ b/fetchmail_attach_from_folder/__init__.py @@ -20,5 +20,6 @@ # ############################################################################## -import fetchmail_server -import fetchmail_server_folder +from . import match_algorithm +from . import model +from . import wizard diff --git a/__unported__/fetchmail_attach_from_folder/__openerp__.py b/fetchmail_attach_from_folder/__openerp__.py similarity index 74% rename from __unported__/fetchmail_attach_from_folder/__openerp__.py rename to fetchmail_attach_from_folder/__openerp__.py index cf2d427ab..93b0a022c 100644 --- a/__unported__/fetchmail_attach_from_folder/__openerp__.py +++ b/fetchmail_attach_from_folder/__openerp__.py @@ -21,15 +21,9 @@ ############################################################################## { - 'name': 'Attach mails in an IMAP folder to existing objects', + 'name': 'Email gateway - folders', + 'summary': 'Attach mails in an IMAP folder to existing objects', 'version': '1.0', - 'description': """ -Adds the possibility to attach emails from a certain IMAP folder to objects, -ie partners. Matching is done via several algorithms, ie email address. - -This gives a simple possibility to archive emails in OpenERP without a mail -client integration. - """, 'author': 'Therp BV', 'website': 'http://www.therp.nl', "category": "Tools", @@ -38,9 +32,7 @@ client integration. 'view/fetchmail_server.xml', 'wizard/attach_mail_manually.xml', 'security/ir.model.access.csv', - ], - 'js': [], - 'installable': False, - 'active': False, - 'certificate': '', + ], + 'installable': True, + 'active': True, } diff --git a/__unported__/fetchmail_attach_from_folder/match_algorithm/__init__.py b/fetchmail_attach_from_folder/match_algorithm/__init__.py similarity index 90% rename from __unported__/fetchmail_attach_from_folder/match_algorithm/__init__.py rename to fetchmail_attach_from_folder/match_algorithm/__init__.py index ff3610863..baa099c37 100644 --- a/__unported__/fetchmail_attach_from_folder/match_algorithm/__init__.py +++ b/fetchmail_attach_from_folder/match_algorithm/__init__.py @@ -20,7 +20,7 @@ # ############################################################################## -import base -import email_exact -import email_domain -import openerp_standard +from . import base +from . import email_exact +from . import email_domain +from . import openerp_standard diff --git a/__unported__/fetchmail_attach_from_folder/match_algorithm/base.py b/fetchmail_attach_from_folder/match_algorithm/base.py similarity index 90% rename from __unported__/fetchmail_attach_from_folder/match_algorithm/base.py rename to fetchmail_attach_from_folder/match_algorithm/base.py index a3d9ba6b8..34e7b3dbe 100644 --- a/__unported__/fetchmail_attach_from_folder/match_algorithm/base.py +++ b/fetchmail_attach_from_folder/match_algorithm/base.py @@ -26,10 +26,10 @@ class base(object): '''Name shown to the user''' required_fields = [] - '''Fields on fetchmail_server folder that are required for this algorithm''' + '''Fields on fetchmail_server folder required for this algorithm''' readonly_fields = [] - '''Fields on fetchmail_server folder that are readonly for this algorithm''' + '''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''' diff --git a/__unported__/fetchmail_attach_from_folder/match_algorithm/email_domain.py b/fetchmail_attach_from_folder/match_algorithm/email_domain.py similarity index 94% rename from __unported__/fetchmail_attach_from_folder/match_algorithm/email_domain.py rename to fetchmail_attach_from_folder/match_algorithm/email_domain.py index da2ec8ac8..1f06b7e7c 100644 --- a/__unported__/fetchmail_attach_from_folder/match_algorithm/email_domain.py +++ b/fetchmail_attach_from_folder/match_algorithm/email_domain.py @@ -20,7 +20,7 @@ # ############################################################################## -from email_exact import email_exact +from .email_exact import email_exact class email_domain(email_exact): @@ -40,6 +40,6 @@ class email_domain(email_exact): self._get_mailaddress_search_domain( conf, mail_message, operator='like', - values=['%@'+domain for domain in set(domains)]), + values=['%@' + domain for domain in set(domains)]), order=conf.model_order) return ids diff --git a/__unported__/fetchmail_attach_from_folder/match_algorithm/email_exact.py b/fetchmail_attach_from_folder/match_algorithm/email_exact.py similarity index 99% rename from __unported__/fetchmail_attach_from_folder/match_algorithm/email_exact.py rename to fetchmail_attach_from_folder/match_algorithm/email_exact.py index b5686a183..a2225e083 100644 --- a/__unported__/fetchmail_attach_from_folder/match_algorithm/email_exact.py +++ b/fetchmail_attach_from_folder/match_algorithm/email_exact.py @@ -20,7 +20,7 @@ # ############################################################################## -from base import base +from .base import base from openerp.tools.safe_eval import safe_eval from openerp.tools.mail import email_split diff --git a/__unported__/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py b/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py similarity index 98% rename from __unported__/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py rename to fetchmail_attach_from_folder/match_algorithm/openerp_standard.py index 2fee96c34..217699169 100644 --- a/__unported__/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py +++ b/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py @@ -20,7 +20,7 @@ # ############################################################################## -from base import base +from .base import base class openerp_standard(base): diff --git a/__unported__/fetchmail_attach_from_folder/__init__.py b/fetchmail_attach_from_folder/model/__init__.py similarity index 93% rename from __unported__/fetchmail_attach_from_folder/__init__.py rename to fetchmail_attach_from_folder/model/__init__.py index 1c91fe478..1073e5e38 100644 --- a/__unported__/fetchmail_attach_from_folder/__init__.py +++ b/fetchmail_attach_from_folder/model/__init__.py @@ -20,6 +20,5 @@ # ############################################################################## -import match_algorithm -import model -import wizard +from . import fetchmail_server +from . import fetchmail_server_folder diff --git a/__unported__/fetchmail_attach_from_folder/model/fetchmail_server.py b/fetchmail_attach_from_folder/model/fetchmail_server.py similarity index 64% rename from __unported__/fetchmail_attach_from_folder/model/fetchmail_server.py rename to fetchmail_attach_from_folder/model/fetchmail_server.py index c1673c382..60dbdce94 100644 --- a/__unported__/fetchmail_attach_from_folder/model/fetchmail_server.py +++ b/fetchmail_attach_from_folder/model/fetchmail_server.py @@ -19,33 +19,26 @@ # along with this program. If not, see . # ############################################################################## - +import logging import base64 import simplejson from lxml import etree -from openerp.osv.orm import Model, except_orm +from openerp import models, fields, api, exceptions from openerp.tools.translate import _ -from openerp.osv import fields -from openerp.addons.fetchmail.fetchmail import _logger as logger from openerp.tools.misc import UnquoteEvalContext -class fetchmail_server(Model): +class fetchmail_server(models.Model): _inherit = 'fetchmail.server' - _columns = { - 'folder_ids': fields.one2many( - 'fetchmail.server.folder', 'server_id', 'Folders'), - } + folder_ids = fields.One2many( + 'fetchmail.server.folder', 'server_id', 'Folders') + object_id = fields.Many2one(required=True) _defaults = { 'type': 'imap', } - def __init__(self, pool, cr): - self._columns['object_id'].required = False - return super(fetchmail_server, self).__init__(pool, cr) - def onchange_server_type( self, cr, uid, ids, server_type=False, ssl=False, object_id=False): @@ -75,111 +68,109 @@ class fetchmail_server(Model): connection = this.connect() for folder in this.folder_ids: this.handle_folder(connection, folder) - connection.close() return super(fetchmail_server, self).fetch_mail( cr, uid, check_original, context) - def handle_folder(self, cr, uid, ids, connection, folder, context=None): + @api.multi + def handle_folder(self, connection, folder): '''Return ids of objects matched''' matched_object_ids = [] - for this in self.browse(cr, uid, ids, context=context): - logger.info('start checking for emails in %s server %s', - folder.path, this.name) + for this in self: + logging.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)) + logging.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)) + logging.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) + logging.info( + 'finished checking for emails in %s server %s', + folder.path, this.name) return matched_object_ids - def get_msgids(self, cr, uid, ids, connection, context=None): + @api.multi + def get_msgids(self, connection): '''Return imap ids of messages to process''' return connection.search(None, 'UNDELETED') - def apply_matching(self, cr, uid, ids, connection, folder, msgid, - match_algorithm, context=None): + @api.multi + def apply_matching(self, connection, folder, msgid, match_algorithm): '''Return ids of objects matched''' matched_object_ids = [] - for this in self.browse(cr, uid, ids, context=context): + 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)) + logging.error( + 'Could not fetch %s in %s on %s', + msgid, folder.path, this.server) continue - mail_message = self.pool.get('mail.thread').message_parse( - cr, uid, msgdata[0][1], save_original=this.original, - context=context) + mail_message = self.env['mail.thread'].message_parse( + msgdata[0][1], save_original=this.original) - if self.pool.get('mail.message').search( - cr, uid, [ - ('message_id', '=', mail_message['message_id'])]): + if self.env['mail.message'].search( + [('message_id', '=', mail_message['message_id'])]): continue found_ids = match_algorithm.search_matches( - cr, uid, folder, - mail_message, msgdata[0][1]) + 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: - cr.execute('savepoint apply_matching') + self.env.cr.execute('savepoint apply_matching') match_algorithm.handle_match( - cr, uid, connection, + self.env.cr, self.env.uid, connection, found_ids[0], folder, mail_message, - msgdata[0][1], msgid, context) - cr.execute('release savepoint apply_matching') + msgdata[0][1], msgid, self.env.context) + self.env.cr.execute('release savepoint apply_matching') matched_object_ids += found_ids[:1] except Exception: - cr.execute('rollback to savepoint apply_matching') - logger.exception( - "Failed to fetch mail %s from %s", - msgid, this.name) + self.env.cr.execute('rollback to savepoint apply_matching') + logging.exception( + "Failed to fetch mail %s from %s", msgid, this.name) elif folder.flag_nonmatching: connection.store(msgid, '+FLAGS', '\\FLAGGED') return matched_object_ids - def attach_mail( - self, cr, uid, ids, connection, object_id, folder, - mail_message, msgid, context=None): + @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.browse(cr, uid, ids, context): + for this in self: partner_id = None if folder.model_id.model == 'res.partner': partner_id = object_id - if 'partner_id' in self.pool.get(folder.model_id.model)._columns: - partner_id = self.pool.get( - folder.model_id.model).browse( - cr, uid, object_id, context - ).partner_id.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'): @@ -196,34 +187,29 @@ class fetchmail_server(Model): 'res_id': object_id, } attachments.append( - self.pool.get('ir.attachment').create( - cr, uid, data_attach, context=context)) + self.env['ir.attachment'].create(data_attach)) mail_message_ids.append( - self.pool.get('mail.message').create( - cr, uid, - { - '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, attachments)], - }, - context)) + 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) + 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'}) @@ -231,9 +217,8 @@ class fetchmail_server(Model): connection.select() for folder in this.folder_ids: if connection.select(folder.path)[0] != 'OK': - raise except_orm( - _('Error'), _('Mailbox %s not found!') % - folder.path) + raise exceptions.ValidationError( + _('Mailbox %s not found!') % folder.path) connection.close() this.write({'state': 'done'}) @@ -249,7 +234,7 @@ class fetchmail_server(Model): result['fields']['folder_ids']['views']['form']['arch']) modifiers = {} docstr = '' - for algorithm in self.pool.get('fetchmail.server.folder')\ + for algorithm in self.pool['fetchmail.server.folder']\ ._get_match_algorithms().itervalues(): for modifier in ['required', 'readonly']: for field in getattr(algorithm, modifier + '_fields'): @@ -262,7 +247,7 @@ class fetchmail_server(Model): docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \ '\n\n' - for field in view: + for field in view.xpath('//field'): if field.tag == 'field' and field.get('name') in modifiers: field.set('modifiers', simplejson.dumps( dict( diff --git a/fetchmail_attach_from_folder/model/fetchmail_server_folder.py b/fetchmail_attach_from_folder/model/fetchmail_server_folder.py new file mode 100644 index 000000000..570245572 --- /dev/null +++ b/fetchmail_attach_from_folder/model/fetchmail_server_folder.py @@ -0,0 +1,115 @@ +# -*- 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' + + 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') + + _defaults = { + 'flag_nonmatching': True, + 'msg_state': 'received', + } + + @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/__unported__/fetchmail_attach_from_folder/security/ir.model.access.csv b/fetchmail_attach_from_folder/security/ir.model.access.csv similarity index 100% rename from __unported__/fetchmail_attach_from_folder/security/ir.model.access.csv rename to fetchmail_attach_from_folder/security/ir.model.access.csv diff --git a/fetchmail_attach_from_folder/static/description/icon.png b/fetchmail_attach_from_folder/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..be54e22b8851ded0aa8c0b0f1a016f2631b8f8a3 GIT binary patch literal 12585 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
    qqW8o`fjhYut8Px(1DfDuZqyuEthX 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 literal 0 HcmV?d00001 diff --git a/__unported__/fetchmail_attach_from_folder/view/fetchmail_server.xml b/fetchmail_attach_from_folder/view/fetchmail_server.xml similarity index 100% rename from __unported__/fetchmail_attach_from_folder/view/fetchmail_server.xml rename to fetchmail_attach_from_folder/view/fetchmail_server.xml diff --git a/__unported__/fetchmail_attach_from_folder/wizard/__init__.py b/fetchmail_attach_from_folder/wizard/__init__.py similarity index 96% rename from __unported__/fetchmail_attach_from_folder/wizard/__init__.py rename to fetchmail_attach_from_folder/wizard/__init__.py index 376a5b392..1f98c5a26 100644 --- a/__unported__/fetchmail_attach_from_folder/wizard/__init__.py +++ b/fetchmail_attach_from_folder/wizard/__init__.py @@ -20,4 +20,4 @@ # ############################################################################## -import attach_mail_manually +from . import attach_mail_manually diff --git a/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.py b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py similarity index 98% rename from __unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.py rename to fetchmail_attach_from_folder/wizard/attach_mail_manually.py index 18b851be2..0c22f0c86 100644 --- a/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.py +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py @@ -74,8 +74,8 @@ class attach_mail_manually(TransientModel): 'msgid': msgid, 'subject': mail_message.get('subject', ''), 'date': mail_message.get('date', ''), - 'object_id': folder.model_id.model+',False' - })) + 'object_id': folder.model_id.model + ',False' + })) connection.close() return defaults diff --git a/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml b/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml similarity index 100% rename from __unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml rename to fetchmail_attach_from_folder/wizard/attach_mail_manually.xml From 797b1e31f4cf37b9dbb12b130c882aebc13febc1 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 27 Jan 2015 15:27:54 +0100 Subject: [PATCH 2/8] [IMP] make manual attach wizard usable again --- .../wizard/attach_mail_manually.py | 73 +++++++++---------- .../wizard/attach_mail_manually.xml | 29 ++++---- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/fetchmail_attach_from_folder/wizard/attach_mail_manually.py b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py index 0c22f0c86..6e24a5234 100644 --- a/fetchmail_attach_from_folder/wizard/attach_mail_manually.py +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py @@ -19,22 +19,17 @@ # along with this program. If not, see . # ############################################################################## - -from openerp.osv import fields -from openerp.osv.orm import TransientModel +from openerp import fields, models import logging -logger = logging.getLogger(__name__) -class attach_mail_manually(TransientModel): +class attach_mail_manually(models.TransientModel): _name = 'fetchmail.attach.mail.manually' - _columns = { - 'folder_id': fields.many2one('fetchmail.server.folder', 'Folder', - readonly=True), - 'mail_ids': fields.one2many( - 'fetchmail.attach.mail.manually.mail', 'wizard_id', 'Emails'), - } + folder_id = fields.Many2one( + 'fetchmail.server.folder', 'Folder', readonly=True) + 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: @@ -54,15 +49,13 @@ class attach_mail_manually(TransientModel): None, 'FLAGGED' if folder.flag_nonmatching else 'UNDELETED') if result != 'OK': - logger.error('Could not search mailbox %s on %s' % ( + logging.error('Could not search mailbox %s on %s' % ( folder.path, folder.server_id.name)) continue - attach_mail_manually_mail._columns['object_id'].selection = [ - (folder.model_id.model, folder.model_id.name)] 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' % ( + logging.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( @@ -74,7 +67,7 @@ class attach_mail_manually(TransientModel): 'msgid': msgid, 'subject': mail_message.get('subject', ''), 'date': mail_message.get('date', ''), - 'object_id': folder.model_id.model + ',False' + 'object_id': '%s,-1' % folder.model_id.model, })) connection.close() @@ -87,7 +80,7 @@ class attach_mail_manually(TransientModel): connection.select(this.folder_id.path) result, msgdata = connection.fetch(mail.msgid, '(RFC822)') if result != 'OK': - logger.error('Could not fetch %s in %s on %s' % ( + logging.error('Could not fetch %s in %s on %s' % ( mail.msgid, this.folder_id.path, this.server)) continue @@ -104,26 +97,32 @@ class attach_mail_manually(TransientModel): 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) + + tree = result['fields']['mail_ids']['views']['tree'] + for folder in self.pool['fetchmail.server.folder'].browse( + cr, user, [context.get('default_folder_id')], context): + tree['fields']['object_id']['selection'] = [ + (folder.model_id.model, folder.model_id.name) + ] + + return result -class attach_mail_manually_mail(TransientModel): + +class attach_mail_manually_mail(models.TransientModel): _name = 'fetchmail.attach.mail.manually.mail' - _columns = { - 'wizard_id': fields.many2one('fetchmail.attach.mail.manually', - readonly=True), - 'msgid': fields.char('Message id', size=16, readonly=True), - 'subject': fields.char('Subject', size=128, readonly=True), - 'date': fields.datetime('Date', readonly=True), - 'object_id': fields.reference( - 'Object', - selection=lambda self, cr, uid, context: [ - (m.model, m.name) - for m in self.pool.get('ir.model').browse( - cr, uid, - self.pool.get('ir.model').search(cr, uid, []), - context - ) - ], - size=128, - ), - } + wizard_id = fields.Many2one( + 'fetchmail.attach.mail.manually', readonly=True) + msgid = fields.Char('Message id', readonly=True) + subject = fields.Char('Subject', readonly=True) + date = fields.Datetime('Date', readonly=True) + object_id = fields.Reference( + lambda self: [ + (m.model, m.name) + 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 fbe82eea7..320ccaf4a 100644 --- a/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml @@ -5,22 +5,21 @@ fetchmail.attach.mail.manually fetchmail.attach.mail.manually -
    - - - - - - - - - - - - + + + + + + + + + + +
    -
    From bfc8a407b9820fd56151179b48a54f0ea141de76 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 27 Jan 2015 15:33:28 +0100 Subject: [PATCH 3/8] [ADD] placeholders --- fetchmail_attach_from_folder/view/fetchmail_server.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fetchmail_attach_from_folder/view/fetchmail_server.xml b/fetchmail_attach_from_folder/view/fetchmail_server.xml index 49384b039..3d58421e7 100644 --- a/fetchmail_attach_from_folder/view/fetchmail_server.xml +++ b/fetchmail_attach_from_folder/view/fetchmail_server.xml @@ -30,19 +30,19 @@ - + - + - + - - + + From 69881bc9b642d3ad207ca5dc2d71a141dfc1bd95 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 27 Jan 2015 16:23:34 +0100 Subject: [PATCH 4/8] [FIX] rename OpenERP->Odoo --- .../match_algorithm/openerp_standard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py b/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py index 217699169..159e82bfb 100644 --- a/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py +++ b/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py @@ -27,7 +27,7 @@ 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 = 'OpenERP standard' + name = 'Odoo standard' readonly_fields = [ 'model_field', 'mail_field', From 1a6e457c8aa06f13738175bf7e7da8612355ef1b Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 27 Jan 2015 16:23:46 +0100 Subject: [PATCH 5/8] [IMP] readme --- fetchmail_attach_from_folder/README.rst | 43 +++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/fetchmail_attach_from_folder/README.rst b/fetchmail_attach_from_folder/README.rst index f7380d124..f7f3dbaaf 100644 --- a/fetchmail_attach_from_folder/README.rst +++ b/fetchmail_attach_from_folder/README.rst @@ -11,13 +11,50 @@ client integration. Configuration ============= -In your fetchmail configuration, you'll find a new field `folders`. Add your -folders here in IMAP notation [TODO] +In your fetchmail configuration, you'll find a new list field `Folders to +monitor`. Add your folders here in IMAP notation (usually something like +`INBOX.your_folder_name.your_subfolder_name`), choose a model to attach mails +to and a matching algorithm to use. + +Exact mailaddress +----------------- + +Fill in a field to search for the email address in `Field (model)`. For +partners, this would be `email`. Also fill in the header field from the email +to look at in `Field (email)`. If you want to match incoming mails from your +customers, this would be `from`. You can also list header fields, so to match +partners receiving this email, you might fill in `to,cc,bcc`. + +Domain of email addresses +------------------------- + +Match the domain of the email address(es) found in `Field (email)`. This would +attach a mail to `test1@example.com` to a record with `Field (model)` set to +`test2@example.com`. Given that this is a fuzzy match, you probably want to +check `Use 1st match`, because otherwise nothing happens if multiple possible +matches are found. + +Odoo standard +------------- + +This is stricly speaking no matching algorithm, but calls the model's standard +action on new incoming mail, which is usually creating a new record. Usage ===== -A widespread configuration is to have a shared mailbox with several folders [TODO] +A widespread configuration is to have a shared mailbox with several folders, +i.e. one where users drop mails they want to attach to partners. Let this +folder be called `From partners`. Then create a folder configuration for your +server with path `"INBOX.From partners"` (note the quotes because of the space, +this is server dependent). Choose model `Partners`, set `Field (model)` to +`email` and `Field (email)` to `from`. In `Domain`, you could fill in +`[('customer', '=', True)]` to be sure to only match customer records. + +Now when your users drop mails into this folder, they will be fetched by Odoo +and attached to the partner in question. After some testing, you might want to +check `Delete matches` in your folder configuration so that this folder doesn't +grow indefinitely. Credits ======= From a168ef93f53d746e880295241218b41f39bbcb3d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Sat, 31 Jan 2015 14:13:35 +0100 Subject: [PATCH 6/8] [IMP] use a dedicated logger --- .../model/fetchmail_server.py | 13 +++++++------ .../wizard/attach_mail_manually.py | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/fetchmail_attach_from_folder/model/fetchmail_server.py b/fetchmail_attach_from_folder/model/fetchmail_server.py index 60dbdce94..c0f1a2107 100644 --- a/fetchmail_attach_from_folder/model/fetchmail_server.py +++ b/fetchmail_attach_from_folder/model/fetchmail_server.py @@ -26,6 +26,7 @@ from lxml import etree from openerp import models, fields, api, exceptions from openerp.tools.translate import _ from openerp.tools.misc import UnquoteEvalContext +_logger = logging.getLogger(__name__) class fetchmail_server(models.Model): @@ -80,21 +81,21 @@ class fetchmail_server(models.Model): matched_object_ids = [] for this in self: - logging.info( + _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': - logging.error( + _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': - logging.error( + _logger.error( 'Could not search mailbox %s on %s', folder.path, this.server) continue @@ -103,7 +104,7 @@ class fetchmail_server(models.Model): matched_object_ids += this.apply_matching( connection, folder, msgid, match_algorithm) - logging.info( + _logger.info( 'finished checking for emails in %s server %s', folder.path, this.name) @@ -124,7 +125,7 @@ class fetchmail_server(models.Model): result, msgdata = connection.fetch(msgid, '(RFC822)') if result != 'OK': - logging.error( + _logger.error( 'Could not fetch %s in %s on %s', msgid, folder.path, this.server) continue @@ -151,7 +152,7 @@ class fetchmail_server(models.Model): matched_object_ids += found_ids[:1] except Exception: self.env.cr.execute('rollback to savepoint apply_matching') - logging.exception( + _logger.exception( "Failed to fetch mail %s from %s", msgid, this.name) elif folder.flag_nonmatching: connection.store(msgid, '+FLAGS', '\\FLAGGED') diff --git a/fetchmail_attach_from_folder/wizard/attach_mail_manually.py b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py index 6e24a5234..666de38bf 100644 --- a/fetchmail_attach_from_folder/wizard/attach_mail_manually.py +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py @@ -21,6 +21,7 @@ ############################################################################## from openerp import fields, models import logging +_logger = logging.getLogger(__name__) class attach_mail_manually(models.TransientModel): @@ -49,14 +50,14 @@ class attach_mail_manually(models.TransientModel): None, 'FLAGGED' if folder.flag_nonmatching else 'UNDELETED') if result != 'OK': - logging.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': - logging.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], @@ -80,8 +81,8 @@ class attach_mail_manually(models.TransientModel): connection.select(this.folder_id.path) result, msgdata = connection.fetch(mail.msgid, '(RFC822)') if result != 'OK': - logging.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, this.folder_id.path, this.server) continue mail_message = self.pool.get('mail.thread').message_parse( From 3ce245ac10648e68c137c31cee71996799ac63da Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Sat, 31 Jan 2015 14:13:55 +0100 Subject: [PATCH 7/8] [FIX] we want object_id not to be required --- fetchmail_attach_from_folder/model/fetchmail_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fetchmail_attach_from_folder/model/fetchmail_server.py b/fetchmail_attach_from_folder/model/fetchmail_server.py index c0f1a2107..e01ea7779 100644 --- a/fetchmail_attach_from_folder/model/fetchmail_server.py +++ b/fetchmail_attach_from_folder/model/fetchmail_server.py @@ -34,7 +34,7 @@ class fetchmail_server(models.Model): folder_ids = fields.One2many( 'fetchmail.server.folder', 'server_id', 'Folders') - object_id = fields.Many2one(required=True) + object_id = fields.Many2one(required=False) _defaults = { 'type': 'imap', From ad90c2478f95b9ba6109c82d04c72b2e26230555 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Sat, 31 Jan 2015 15:30:21 +0100 Subject: [PATCH 8/8] [ADD] test matching algorithms --- .../tests/__init__.py | 21 ++++ .../tests/test_match_algorithms.py | 103 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 fetchmail_attach_from_folder/tests/__init__.py create mode 100644 fetchmail_attach_from_folder/tests/test_match_algorithms.py diff --git a/fetchmail_attach_from_folder/tests/__init__.py b/fetchmail_attach_from_folder/tests/__init__.py new file mode 100644 index 000000000..1da15b8a0 --- /dev/null +++ b/fetchmail_attach_from_folder/tests/__init__.py @@ -0,0 +1,21 @@ +# -*- 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 . 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 new file mode 100644 index 000000000..3e4e8517a --- /dev/null +++ b/fetchmail_attach_from_folder/tests/test_match_algorithms.py @@ -0,0 +1,103 @@ +# -*- 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) + + +class TestMatchAlgorithms(TransactionCase): + def do_matching(self, match_algorithm, expected_xmlid, conf, 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) + self.assertEqual(len(matches), 1) + self.assertEqual( + matches[0], self.env.ref(expected_xmlid).id) + matcher.handle_match( + self.env.cr, self.env.uid, None, matches[0], conf, mail_message, + mail_message_org, None) + + def test_email_exact(self): + mail_message = { + 'subject': 'Testsubject', + '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()]) + self.do_matching( + email_exact.email_exact, 'base.user_demo_res_partner', + conf, mail_message) + self.assertEqual( + self.env.ref('base.user_demo_res_partner').message_ids.subject, + mail_message['subject']) + + def test_email_domain(self): + mail_message = { + '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()]) + self.do_matching( + email_domain.email_domain, 'base.res_partner_address_31', + conf, mail_message) + self.assertEqual( + self.env.ref('base.res_partner_address_31').message_ids.subject, + mail_message['subject']) + + def test_openerp_standard(self): + mail_message_org = ( + "To: demo@yourcompany.example.com\n" + "From: someone@else.com\n" + "Subject: testsubject\n" + "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() + matches = matcher.search_matches( + self.env.cr, self.env.uid, conf, 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) + self.assertIn( + 'Hello world', + self.env['mail.message'] + .search([('subject', '=', 'testsubject')]).body)