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/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml b/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml deleted file mode 100644 index fbe82eea7..000000000 --- a/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - fetchmail.attach.mail.manually - fetchmail.attach.mail.manually - -
- - - - - - - - - - - - -
-
-
-
-
-
-
diff --git a/fetchmail_attach_from_folder/README.rst b/fetchmail_attach_from_folder/README.rst new file mode 100644 index 000000000..f7f3dbaaf --- /dev/null +++ b/fetchmail_attach_from_folder/README.rst @@ -0,0 +1,83 @@ +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 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, +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 +======= + +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 97% 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..159e82bfb 100644 --- a/__unported__/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py +++ b/fetchmail_attach_from_folder/match_algorithm/openerp_standard.py @@ -20,14 +20,14 @@ # ############################################################################## -from base import base +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 = 'OpenERP standard' + name = 'Odoo standard' readonly_fields = [ 'model_field', 'mail_field', 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..e01ea7779 100644 --- a/__unported__/fetchmail_attach_from_folder/model/fetchmail_server.py +++ b/fetchmail_attach_from_folder/model/fetchmail_server.py @@ -19,33 +19,27 @@ # 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 +_logger = logging.getLogger(__name__) -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=False) _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 +69,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: + _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)) + _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)) + _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) + _logger.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)) + _logger.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') + _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 - 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 +188,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 +218,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 +235,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 +248,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 000000000..be54e22b8 Binary files /dev/null and b/fetchmail_attach_from_folder/static/description/icon.png differ 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) diff --git a/__unported__/fetchmail_attach_from_folder/view/fetchmail_server.xml b/fetchmail_attach_from_folder/view/fetchmail_server.xml similarity index 91% rename from __unported__/fetchmail_attach_from_folder/view/fetchmail_server.xml rename to fetchmail_attach_from_folder/view/fetchmail_server.xml index 49384b039..3d58421e7 100644 --- a/__unported__/fetchmail_attach_from_folder/view/fetchmail_server.xml +++ b/fetchmail_attach_from_folder/view/fetchmail_server.xml @@ -30,19 +30,19 @@ - + - + - + - - + + 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 64% 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..666de38bf 100644 --- a/__unported__/fetchmail_attach_from_folder/wizard/attach_mail_manually.py +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.py @@ -19,22 +19,18 @@ # 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__) +_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,16 +50,14 @@ 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' % ( - folder.path, folder.server_id.name)) + _logger.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' % ( - 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], @@ -74,8 +68,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': '%s,-1' % folder.model_id.model, + })) connection.close() return defaults @@ -87,8 +81,8 @@ 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' % ( - 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( @@ -104,26 +98,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 new file mode 100644 index 000000000..320ccaf4a --- /dev/null +++ b/fetchmail_attach_from_folder/wizard/attach_mail_manually.xml @@ -0,0 +1,28 @@ + + + + + fetchmail.attach.mail.manually + fetchmail.attach.mail.manually + +
+ + + + + + + + + + + +
+
+
+
+