Merge pull request #121 from hbrunn/8.0-fetchmail_attach_from_folder
[ADD] port fetchmail_attach_from_folderpull/126/head
commit
f7e88259c8
|
@ -1,131 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
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',
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_attach_mail_manually">
|
||||
<field name="name">fetchmail.attach.mail.manually</field>
|
||||
<field name="model">fetchmail.attach.mail.manually</field>
|
||||
<field name="arch" type="xml">
|
||||
<form col="4" version="7.0" string="Attach mail manually">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="folder_id" />
|
||||
<field name="mail_ids" nolabel="1" colspan="4">
|
||||
<tree editable="top" create="0">
|
||||
<field name="subject" />
|
||||
<field name="date" />
|
||||
<field name="object_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button string="Save" type="object" name="attach_mails" icon="gtk-ok" />
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -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 <hbrunn@therp.nl>
|
||||
|
||||
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.
|
|
@ -20,5 +20,6 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import fetchmail_server
|
||||
import fetchmail_server_folder
|
||||
from . import match_algorithm
|
||||
from . import model
|
||||
from . import wizard
|
|
@ -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,
|
||||
}
|
|
@ -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
|
|
@ -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'''
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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',
|
|
@ -20,6 +20,5 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import match_algorithm
|
||||
import model
|
||||
import wizard
|
||||
from . import fetchmail_server
|
||||
from . import fetchmail_server_folder
|
|
@ -19,33 +19,27 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
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(
|
|
@ -0,0 +1,115 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
########################################################################
|
||||
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',
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# This module copyright (C) 2015 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import test_match_algorithms
|
|
@ -0,0 +1,103 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# This module copyright (C) 2015 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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)
|
|
@ -30,19 +30,19 @@
|
|||
</header>
|
||||
<group>
|
||||
<group>
|
||||
<field name="path" />
|
||||
<field name="path" placeholder="INBOX.subfolder1" />
|
||||
<field name="model_id" />
|
||||
<field name="model_field" />
|
||||
<field name="model_field" placeholder="email" />
|
||||
<field name="match_algorithm" />
|
||||
<field name="mail_field" />
|
||||
<field name="mail_field" placeholder="to,from" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="delete_matching" />
|
||||
<field name="flag_nonmatching" />
|
||||
<field name="match_first" />
|
||||
<field name="msg_state" />
|
||||
<field name="model_order" attrs="{'readonly': [('match_first','==',False)], 'required': [('match_first','==',True)]}" />
|
||||
<field name="domain" />
|
||||
<field name="model_order" attrs="{'readonly': [('match_first','==',False)], 'required': [('match_first','==',True)]}" placeholder="name asc,type desc" />
|
||||
<field name="domain" placeholder="[('state', '=', 'open')]" />
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
|
@ -20,4 +20,4 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import attach_mail_manually
|
||||
from . import attach_mail_manually
|
|
@ -19,22 +19,18 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
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)
|
||||
|
||||
class attach_mail_manually_mail(TransientModel):
|
||||
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(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')
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_attach_mail_manually">
|
||||
<field name="name">fetchmail.attach.mail.manually</field>
|
||||
<field name="model">fetchmail.attach.mail.manually</field>
|
||||
<field name="arch" type="xml">
|
||||
<form col="4" string="Attach mail manually">
|
||||
<group>
|
||||
<field name="folder_id" />
|
||||
<field name="mail_ids" nolabel="1" colspan="4">
|
||||
<tree editable="top" create="0">
|
||||
<field name="subject" />
|
||||
<field name="date" />
|
||||
<field name="object_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Save" type="object" name="attach_mails" class="oe_highlight" />
|
||||
or
|
||||
<button special="cancel" string="Cancel" class="oe_link" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
Loading…
Reference in New Issue