Browse Source

[MIG] fetchmail_attach_from_folder. Migration to 10.0.

pull/1317/merge
Ronald Portier 7 years ago
committed by Holger Brunn
parent
commit
06ab3ef577
  1. 14
      fetchmail_attach_from_folder/README.rst
  2. 27
      fetchmail_attach_from_folder/__init__.py
  3. 42
      fetchmail_attach_from_folder/__manifest__.py
  4. 25
      fetchmail_attach_from_folder/match_algorithm/__init__.py
  5. 48
      fetchmail_attach_from_folder/match_algorithm/base.py
  6. 54
      fetchmail_attach_from_folder/match_algorithm/email_domain.py
  7. 57
      fetchmail_attach_from_folder/match_algorithm/email_exact.py
  8. 33
      fetchmail_attach_from_folder/match_algorithm/odoo_standard.py
  9. 58
      fetchmail_attach_from_folder/match_algorithm/openerp_standard.py
  10. 24
      fetchmail_attach_from_folder/model/__init__.py
  11. 266
      fetchmail_attach_from_folder/model/fetchmail_server.py
  12. 118
      fetchmail_attach_from_folder/model/fetchmail_server_folder.py
  13. 5
      fetchmail_attach_from_folder/models/__init__.py
  14. 105
      fetchmail_attach_from_folder/models/fetchmail_server.py
  15. 220
      fetchmail_attach_from_folder/models/fetchmail_server_folder.py
  16. 21
      fetchmail_attach_from_folder/tests/__init__.py
  17. 147
      fetchmail_attach_from_folder/tests/test_match_algorithms.py
  18. 58
      fetchmail_attach_from_folder/view/fetchmail_server.xml
  19. 80
      fetchmail_attach_from_folder/views/fetchmail_server.xml
  20. 25
      fetchmail_attach_from_folder/wizard/__init__.py
  21. 130
      fetchmail_attach_from_folder/wizard/attach_mail_manually.py
  22. 19
      fetchmail_attach_from_folder/wizard/attach_mail_manually.xml

14
fetchmail_attach_from_folder/README.rst

@ -60,11 +60,10 @@ grow indefinitely.
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20fetchmail_attach_from_folder%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits Credits
======= =======
@ -73,6 +72,7 @@ Contributors
------------ ------------
* Holger Brunn <hbrunn@therp.nl> * Holger Brunn <hbrunn@therp.nl>
* Ronald Portier <ronald@therp.nl>
Icon Icon
---- ----
@ -88,6 +88,8 @@ Maintainer
This module is maintained by the OCA. This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org. To contribute to this module, please visit http://odoo-community.org.

27
fetchmail_attach_from_folder/__init__.py

@ -1,25 +1,6 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import match_algorithm from . import match_algorithm
from . import model
from . import models
from . import wizard from . import wizard

42
fetchmail_attach_from_folder/__manifest__.py

@ -1,39 +1,25 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Email gateway - folders', 'name': 'Email gateway - folders',
'summary': 'Attach mails in an IMAP folder to existing objects', 'summary': 'Attach mails in an IMAP folder to existing objects',
'version': '8.0.1.0.1',
'author': "Therp BV,Odoo Community Association (OCA)",
'version': '10.0.1.0.0',
'author': 'Therp BV,Odoo Community Association (OCA)',
'website': 'http://www.therp.nl', 'website': 'http://www.therp.nl',
'license': 'AGPL-3', 'license': 'AGPL-3',
"category": "Tools",
"depends": ['fetchmail'],
'category': 'Tools',
'depends': ['fetchmail'],
'data': [ 'data': [
'view/fetchmail_server.xml',
'views/fetchmail_server.xml',
'wizard/attach_mail_manually.xml', 'wizard/attach_mail_manually.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
], ],
'installable': False,
'installable': True,
'auto_install': False, 'auto_install': False,
'external_dependencies': {
'python': [
'simplejson',
],
},
} }

25
fetchmail_attach_from_folder/match_algorithm/__init__.py

@ -1,26 +1,7 @@
# -*- encoding: utf-8 -*- # -*- 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/>.
#
##############################################################################
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import base from . import base
from . import email_exact from . import email_exact
from . import email_domain from . import email_domain
from . import openerp_standard
from . import odoo_standard

48
fetchmail_attach_from_folder/match_algorithm/base.py

@ -1,43 +1,25 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
class base(object):
name = None
'''Name shown to the user'''
class Base(object):
name = None # Name shown to the user
# Fields on fetchmail_server folder required for this algorithm
required_fields = [] required_fields = []
'''Fields on fetchmail_server folder required for this algorithm'''
# Fields on fetchmail_server folder readonly for this algorithm
readonly_fields = [] readonly_fields = []
'''Fields on fetchmail_server folder readonly for this algorithm'''
def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
'''Returns ids found for model with mail_message'''
def search_matches(self, folder, mail_message, mail_message_org):
"""Returns recordset found for model with mail_message."""
return [] return []
def handle_match( def handle_match(
self, cr, uid, connection, object_id, folder,
mail_message, mail_message_org, msgid, context=None):
'''Do whatever it takes to handle a match'''
return folder.server_id.attach_mail(connection, object_id, folder,
mail_message, msgid)
self, connection, match_object, folder,
mail_message, mail_message_org, msgid):
"""Do whatever it takes to handle a match"""
folder.attach_mail(match_object, mail_message)
if folder.delete_matching:
connection.store(msgid, '+FLAGS', '\\DELETED')

54
fetchmail_attach_from_folder/match_algorithm/email_domain.py

@ -1,45 +1,29 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .email_exact import EmailExact
from .email_exact import email_exact
class EmailDomain(EmailExact):
"""Search objects by domain name of email address.
class email_domain(email_exact):
'''Search objects by domain name of email address.
Beware of match_first here, this is most likely to get it wrong (gmail)'''
Beware of match_first here, this is most likely to get it wrong (gmail).
"""
name = 'Domain of email address' name = 'Domain of email address'
def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
ids = super(email_domain, self).search_matches(
cr, uid, conf, mail_message, mail_message_org)
if not ids:
def search_matches(self, folder, mail_message, mail_message_org):
"""Returns recordset of matching objects."""
matches = super(EmailDomain, self).search_matches(
folder, mail_message, mail_message_org)
if not matches:
object_model = folder.env[folder.model_id.model]
domains = [] domains = []
for addr in self._get_mailaddresses(conf, mail_message):
for addr in self._get_mailaddresses(folder, mail_message):
domains.append(addr.split('@')[-1]) domains.append(addr.split('@')[-1])
ids = conf.pool.get(conf.model_id.model).search(
cr, uid,
matches = object_model.search(
self._get_mailaddress_search_domain( self._get_mailaddress_search_domain(
conf, mail_message,
folder, mail_message,
operator='like', operator='like',
values=['%@' + domain for domain in set(domains)]), values=['%@' + domain for domain in set(domains)]),
order=conf.model_order)
return ids
order=folder.model_order)
return matches

57
fetchmail_attach_from_folder/match_algorithm/email_exact.py

@ -1,57 +1,40 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tools.safe_eval import safe_eval
from odoo.tools.mail import email_split
from .base import base
from openerp.tools.safe_eval import safe_eval
from openerp.tools.mail import email_split
from .base import Base
class email_exact(base):
'''Search for exactly the mailadress as noted in the email'''
class EmailExact(Base):
"""Search for exactly the mailadress as noted in the email"""
name = 'Exact mailadress' name = 'Exact mailadress'
required_fields = ['model_field', 'mail_field'] required_fields = ['model_field', 'mail_field']
def _get_mailaddresses(self, conf, mail_message):
def _get_mailaddresses(self, folder, mail_message):
mailaddresses = [] mailaddresses = []
fields = conf.mail_field.split(',')
fields = folder.mail_field.split(',')
for field in fields: for field in fields:
if field in mail_message: if field in mail_message:
mailaddresses += email_split(mail_message[field]) mailaddresses += email_split(mail_message[field])
return [addr.lower() for addr in mailaddresses] return [addr.lower() for addr in mailaddresses]
def _get_mailaddress_search_domain( def _get_mailaddress_search_domain(
self, conf, mail_message, operator='=', values=None):
self, folder, mail_message, operator='=', values=None):
mailaddresses = values or self._get_mailaddresses( mailaddresses = values or self._get_mailaddresses(
conf, mail_message)
folder, mail_message)
if not mailaddresses: if not mailaddresses:
return [(0, '=', 1)] return [(0, '=', 1)]
search_domain = ((['|'] * (len(mailaddresses) - 1)) + [ search_domain = ((['|'] * (len(mailaddresses) - 1)) + [
(conf.model_field, operator, addr) for addr in mailaddresses] +
safe_eval(conf.domain or '[]'))
(folder.model_field, operator, addr) for addr in mailaddresses] +
safe_eval(folder.domain or '[]'))
return search_domain return search_domain
def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
conf_model = conf.pool.get(conf.model_id.model)
search_domain = self._get_mailaddress_search_domain(conf, mail_message)
return conf_model.search(
cr, uid, search_domain, order=conf.model_order)
def search_matches(self, folder, mail_message, mail_message_org):
"""Returns recordset of matching objects."""
object_model = folder.env[folder.model_id.model]
search_domain = self._get_mailaddress_search_domain(
folder, mail_message)
return object_model.search(search_domain, order=folder.model_order)

33
fetchmail_attach_from_folder/match_algorithm/odoo_standard.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .base import Base
class OdooStandard(Base):
"""No search at all. Use Odoo's standard mechanism to attach mails to
mail.thread objects. Note that this algorithm always matches."""
name = 'Odoo standard'
readonly_fields = [
'model_field',
'mail_field',
'match_first',
'domain',
'model_order',
'flag_nonmatching',
]
def search_matches(self, folder, mail_message, mail_message_org):
"""Always match. Duplicates will be fished out by message_id"""
return [True]
def handle_match(
self, connection, match_object, folder,
mail_message, mail_message_org, msgid):
thread_model = folder.env['mail.thread']
thread_model.message_process(
folder.model_id.model, mail_message_org,
save_original=folder.server_id.original,
strip_attachments=(not folder.server_id.attach))
if folder.delete_matching:
connection.store(msgid, '+FLAGS', '\\DELETED')

58
fetchmail_attach_from_folder/match_algorithm/openerp_standard.py

@ -1,58 +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 .base import base
class openerp_standard(base):
'''No search at all. Use OpenERP's standard mechanism to attach mails to
mail.thread objects. Note that this algorithm always matches.'''
name = 'Odoo standard'
readonly_fields = [
'model_field',
'mail_field',
'match_first',
'domain',
'model_order',
'flag_nonmatching',
]
def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
'''Always match. Duplicates will be fished out by message_id'''
return [True]
def handle_match(
self, cr, uid, connection, object_id, folder,
mail_message, mail_message_org, msgid, context):
result = folder.pool.get('mail.thread').message_process(
cr, uid,
folder.model_id.model, mail_message_org,
save_original=folder.server_id.original,
strip_attachments=(not folder.server_id.attach),
context=context
)
if folder.delete_matching:
connection.store(msgid, '+FLAGS', '\\DELETED')
return [result]

24
fetchmail_attach_from_folder/model/__init__.py

@ -1,24 +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 . import fetchmail_server
from . import fetchmail_server_folder

266
fetchmail_attach_from_folder/model/fetchmail_server.py

@ -1,266 +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/>.
#
##############################################################################
import logging
import base64
import simplejson
from lxml import etree
from openerp import models, fields, api, exceptions
from openerp.tools.translate import _
from openerp.tools.safe_eval import safe_eval
from openerp.tools.misc import UnquoteEvalContext
_logger = logging.getLogger(__name__)
class fetchmail_server(models.Model):
_inherit = 'fetchmail.server'
folder_ids = fields.One2many(
'fetchmail.server.folder', 'server_id', 'Folders',
context={'active_test': False})
object_id = fields.Many2one(required=False)
_defaults = {
'type': 'imap',
}
def onchange_server_type(
self, cr, uid, ids, server_type=False, ssl=False,
object_id=False):
retval = super(
fetchmail_server, self).onchange_server_type(cr, uid,
ids, server_type, ssl,
object_id)
retval['value']['state'] = 'draft'
return retval
def fetch_mail(self, cr, uid, ids, context=None):
if context is None:
context = {}
check_original = []
for this in self.browse(cr, uid, ids, context):
if this.object_id:
check_original.append(this.id)
context.update(
{
'fetchmail_server_id': this.id,
'server_type': this.type
})
connection = this.connect()
for folder in this.folder_ids.filtered('active'):
this.handle_folder(connection, folder)
connection.close()
return super(fetchmail_server, self).fetch_mail(
cr, uid, check_original, context)
@api.multi
def handle_folder(self, connection, folder):
'''Return ids of objects matched'''
matched_object_ids = []
for this in self:
_logger.info(
'start checking for emails in %s server %s',
folder.path, this.name)
match_algorithm = folder.get_algorithm()
if connection.select(folder.path)[0] != 'OK':
_logger.error(
'Could not open mailbox %s on %s',
folder.path, this.server)
connection.select()
continue
result, msgids = this.get_msgids(connection)
if result != 'OK':
_logger.error(
'Could not search mailbox %s on %s',
folder.path, this.server)
continue
for msgid in msgids[0].split():
matched_object_ids += this.apply_matching(
connection, folder, msgid, match_algorithm)
_logger.info(
'finished checking for emails in %s server %s',
folder.path, this.name)
return matched_object_ids
@api.multi
def get_msgids(self, connection):
'''Return imap ids of messages to process'''
return connection.search(None, 'UNDELETED')
@api.multi
def apply_matching(self, connection, folder, msgid, match_algorithm):
'''Return ids of objects matched'''
matched_object_ids = []
for this in self:
result, msgdata = connection.fetch(msgid, '(RFC822)')
if result != 'OK':
_logger.error(
'Could not fetch %s in %s on %s',
msgid, folder.path, this.server)
continue
mail_message = self.env['mail.thread'].message_parse(
msgdata[0][1], save_original=this.original)
if self.env['mail.message'].search(
[('message_id', '=', mail_message['message_id'])]):
continue
found_ids = match_algorithm.search_matches(
self.env.cr, self.env.uid, folder, mail_message, msgdata[0][1])
if found_ids and (len(found_ids) == 1 or
folder.match_first):
try:
self.env.cr.execute('savepoint apply_matching')
match_algorithm.handle_match(
self.env.cr, self.env.uid, connection,
found_ids[0], folder, mail_message,
msgdata[0][1], msgid, self.env.context)
self.env.cr.execute('release savepoint apply_matching')
matched_object_ids += found_ids[:1]
except Exception:
self.env.cr.execute('rollback to savepoint apply_matching')
_logger.exception(
"Failed to fetch mail %s from %s", msgid, this.name)
elif folder.flag_nonmatching:
connection.store(msgid, '+FLAGS', '\\FLAGGED')
return matched_object_ids
@api.multi
def attach_mail(self, connection, object_id, folder, mail_message, msgid):
'''Return ids of messages created'''
mail_message_ids = []
for this in self:
partner_id = None
if folder.model_id.model == 'res.partner':
partner_id = object_id
if 'partner_id' in self.env[folder.model_id.model]._columns:
partner_id = self.env[folder.model_id.model].browse(object_id)\
.partner_id.id
attachments = []
if this.attach and mail_message.get('attachments'):
for attachment in mail_message['attachments']:
fname, fcontent = attachment
if isinstance(fcontent, unicode):
fcontent = fcontent.encode('utf-8')
data_attach = {
'name': fname,
'datas': base64.b64encode(str(fcontent)),
'datas_fname': fname,
'description': _('Mail attachment'),
'res_model': folder.model_id.model,
'res_id': object_id,
}
attachments.append(
self.env['ir.attachment'].create(data_attach))
mail_message_ids.append(
self.env['mail.message'].create({
'author_id': partner_id,
'model': folder.model_id.model,
'res_id': object_id,
'type': 'email',
'body': mail_message.get('body'),
'subject': mail_message.get('subject'),
'email_from': mail_message.get('from'),
'date': mail_message.get('date'),
'message_id': mail_message.get('message_id'),
'attachment_ids': [(6, 0, [a.id for a in attachments])],
}))
if folder.delete_matching:
connection.store(msgid, '+FLAGS', '\\DELETED')
return mail_message_ids
def button_confirm_login(self, cr, uid, ids, context=None):
retval = super(fetchmail_server, self).button_confirm_login(
cr, uid, ids, context)
for this in self.browse(cr, uid, ids, context):
this.write({'state': 'draft'})
connection = this.connect()
connection.select()
for folder in this.folder_ids.filtered('active'):
if connection.select(folder.path)[0] != 'OK':
raise exceptions.ValidationError(
_('Mailbox %s not found!') % folder.path)
connection.close()
this.write({'state': 'done'})
return retval
def fields_view_get(self, cr, user, view_id=None, view_type='form',
context=None, toolbar=False, submenu=False):
result = super(fetchmail_server, self).fields_view_get(
cr, user, view_id, view_type, context, toolbar, submenu)
if view_type == 'form':
view = etree.fromstring(
result['fields']['folder_ids']['views']['form']['arch'])
modifiers = {}
docstr = ''
for algorithm in self.pool['fetchmail.server.folder']\
._get_match_algorithms().itervalues():
for modifier in ['required', 'readonly']:
for field in getattr(algorithm, modifier + '_fields'):
modifiers.setdefault(field, {})
modifiers[field].setdefault(modifier, [])
if modifiers[field][modifier]:
modifiers[field][modifier].insert(0, '|')
modifiers[field][modifier].append(
("match_algorithm", "==", algorithm.__name__))
docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
'\n\n'
for field in view.xpath('//field'):
if field.tag == 'field' and field.get('name') in modifiers:
field.set('modifiers', simplejson.dumps(
dict(
safe_eval(field.attrib['modifiers'],
UnquoteEvalContext({})),
**modifiers[field.attrib['name']])))
if (field.tag == 'field' and
field.get('name') == 'match_algorithm'):
field.set('help', docstr)
result['fields']['folder_ids']['views']['form']['arch'] = \
etree.tostring(view)
return result

118
fetchmail_attach_from_folder/model/fetchmail_server_folder.py

@ -1,118 +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 import api, models, fields
from .. import match_algorithm
class fetchmail_server_folder(models.Model):
_name = 'fetchmail.server.folder'
_rec_name = 'path'
_order = 'sequence'
def _get_match_algorithms(self):
def get_all_subclasses(cls):
return (cls.__subclasses__() +
[subsub
for sub in cls.__subclasses__()
for subsub in get_all_subclasses(sub)])
return dict([(cls.__name__, cls)
for cls in get_all_subclasses(
match_algorithm.base.base)])
def _get_match_algorithms_sel(self):
algorithms = []
for cls in self._get_match_algorithms().itervalues():
algorithms.append((cls.__name__, cls.name))
algorithms.sort()
return algorithms
sequence = fields.Integer('Sequence')
path = fields.Char(
'Path',
help="The path to your mail folder. Typically would be something like "
"'INBOX.myfolder'", required=True)
model_id = fields.Many2one(
'ir.model', 'Model', required=True,
help='The model to attach emails to')
model_field = fields.Char(
'Field (model)',
help='The field in your model that contains the field to match '
'against.\n'
'Examples:\n'
"'email' if your model is res.partner, or "
"'partner_id.email' if you're matching sale orders")
model_order = fields.Char(
'Order (model)',
help='Field(s) to order by, this mostly useful in conjunction '
"with 'Use 1st match'")
match_algorithm = fields.Selection(
_get_match_algorithms_sel,
'Match algorithm', required=True,
help='The algorithm used to determine which object an email matches.')
mail_field = fields.Char(
'Field (email)',
help='The field in the email used for matching. Typically '
"this is 'to' or 'from'")
server_id = fields.Many2one('fetchmail.server', 'Server')
delete_matching = fields.Boolean(
'Delete matches',
help='Delete matched emails from server')
flag_nonmatching = fields.Boolean(
'Flag nonmatching',
help="Flag emails in the server that don't match any object in Odoo")
match_first = fields.Boolean(
'Use 1st match',
help='If there are multiple matches, use the first one. If '
'not checked, multiple matches count as no match at all')
domain = fields.Char(
'Domain',
help='Fill in a search filter to narrow down objects to match')
msg_state = fields.Selection(
[
('sent', 'Sent'),
('received', 'Received'),
],
'Message state',
help='The state messages fetched from this folder should be '
'assigned in Odoo')
active = fields.Boolean('Active')
_defaults = {
'flag_nonmatching': True,
'msg_state': 'received',
'active': True,
}
@api.multi
def get_algorithm(self):
return self._get_match_algorithms()[self.match_algorithm]()
@api.multi
def button_attach_mail_manually(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'fetchmail.attach.mail.manually',
'target': 'new',
'context': dict(self.env.context, default_folder_id=self.id),
'view_type': 'form',
'view_mode': 'form',
}

5
fetchmail_attach_from_folder/models/__init__.py

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import fetchmail_server
from . import fetchmail_server_folder

105
fetchmail_attach_from_folder/models/fetchmail_server.py

@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import simplejson
from lxml import etree
from odoo import _, api, exceptions, fields, models
from odoo.tools.safe_eval import safe_eval
from odoo.tools.misc import UnquoteEvalContext
class FetchmailServer(models.Model):
_inherit = 'fetchmail.server'
folder_ids = fields.One2many(
comodel_name='fetchmail.server.folder',
inverse_name='server_id',
string='Folders',
context={'active_test': False})
object_id = fields.Many2one(required=False) # comodel_name='ir.model'
type = fields.Selection(default='imap')
folders_only = fields.Boolean(
string='Only folders, not inbox',
help="Check this field to leave imap inbox alone"
" and only retrieve mail from configured folders.")
@api.onchange('type', 'is_ssl', 'object_id')
def onchange_server_type(self):
super(FetchmailServer, self).onchange_server_type()
self.state = 'draft'
@api.onchange('folder_ids')
def onchange_folder_ids(self):
self.onchange_server_type()
@api.multi
def fetch_mail(self):
for this in self:
if not this.folders_only:
super(FetchmailServer, this).fetch_mail()
for folder in this.folder_ids.filtered('active'):
folder.retrieve_imap_folder()
return True
@api.multi
def button_confirm_login(self):
retval = super(FetchmailServer, self).button_confirm_login()
for this in self:
this.write({'state': 'draft'})
connection = this.connect()
connection.select()
for folder in this.folder_ids.filtered('active'):
if connection.select(folder.path)[0] != 'OK':
raise exceptions.ValidationError(
_('Mailbox %s not found!') % folder.path)
connection.close()
this.write({'state': 'done'})
return retval
def fields_view_get(
self, view_id=None, view_type='form',
toolbar=False, submenu=False):
"""Set modifiers for form fields in folder_ids depending on algorithm.
A field will be readonly and/or required if this is specified in the
algorithm.
"""
result = super(FetchmailServer, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type == 'form':
view = etree.fromstring(
result['fields']['folder_ids']['views']['form']['arch'])
modifiers = {}
docstr = ''
folder_model = self.env['fetchmail.server.folder']
match_algorithms = folder_model._get_match_algorithms()
for algorithm in match_algorithms.itervalues():
for modifier in ['required', 'readonly']:
for field in getattr(algorithm, modifier + '_fields'):
modifiers.setdefault(field, {})
modifiers[field].setdefault(modifier, [])
if modifiers[field][modifier]:
modifiers[field][modifier].insert(0, '|')
modifiers[field][modifier].append(
("match_algorithm", "==", algorithm.__name__))
docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
'\n\n'
for field in view.xpath('//field'):
if field.tag == 'field' and field.get('name') in modifiers:
patched_modifiers = field.attrib['modifiers'].replace(
'false', 'False').replace('true', 'True')
original_dict = safe_eval(
patched_modifiers,
UnquoteEvalContext({}),
nocopy=True)
modifier_dict = modifiers[field.attrib['name']]
combined_dict = dict(original_dict, **modifier_dict)
field.set('modifiers', simplejson.dumps(combined_dict))
if (field.tag == 'field' and
field.get('name') == 'match_algorithm'):
field.set('help', docstr)
result['fields']['folder_ids']['views']['form']['arch'] = \
etree.tostring(view)
return result

220
fetchmail_attach_from_folder/models/fetchmail_server_folder.py

@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
import logging
from odoo import _, api, models, fields
from .. import match_algorithm
_logger = logging.getLogger(__name__)
class FetchmailServerFolder(models.Model):
_name = 'fetchmail.server.folder'
_rec_name = 'path'
_order = 'sequence'
def _get_match_algorithms(self):
def get_all_subclasses(cls):
return (cls.__subclasses__() +
[subsub
for sub in cls.__subclasses__()
for subsub in get_all_subclasses(sub)])
return dict([(cls.__name__, cls)
for cls in get_all_subclasses(
match_algorithm.base.Base)])
def _get_match_algorithms_sel(self):
algorithms = []
for cls in self._get_match_algorithms().itervalues():
algorithms.append((cls.__name__, cls.name))
algorithms.sort()
return algorithms
sequence = fields.Integer('Sequence')
path = fields.Char(
'Path',
help="The path to your mail folder. Typically would be something like "
"'INBOX.myfolder'", required=True)
model_id = fields.Many2one(
'ir.model', 'Model', required=True,
help='The model to attach emails to')
model_field = fields.Char(
'Field (model)',
help='The field in your model that contains the field to match '
'against.\n'
'Examples:\n'
"'email' if your model is res.partner, or "
"'partner_id.email' if you're matching sale orders")
model_order = fields.Char(
'Order (model)',
help='Field(s) to order by, this mostly useful in conjunction '
"with 'Use 1st match'")
match_algorithm = fields.Selection(
_get_match_algorithms_sel,
'Match algorithm', required=True,
help='The algorithm used to determine which object an email matches.')
mail_field = fields.Char(
'Field (email)',
help='The field in the email used for matching. Typically '
"this is 'to' or 'from'")
server_id = fields.Many2one('fetchmail.server', 'Server')
delete_matching = fields.Boolean(
'Delete matches',
help='Delete matched emails from server')
flag_nonmatching = fields.Boolean(
'Flag nonmatching',
default=True,
help="Flag emails in the server that don't match any object in Odoo")
match_first = fields.Boolean(
'Use 1st match',
help='If there are multiple matches, use the first one. If '
'not checked, multiple matches count as no match at all')
domain = fields.Char(
'Domain',
help='Fill in a search filter to narrow down objects to match')
msg_state = fields.Selection(
selection=[('sent', 'Sent'), ('received', 'Received')],
string='Message state',
default='received',
help='The state messages fetched from this folder should be '
'assigned in Odoo')
active = fields.Boolean('Active', default=True)
@api.multi
def get_algorithm(self):
return self._get_match_algorithms()[self.match_algorithm]()
@api.multi
def button_attach_mail_manually(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'fetchmail.attach.mail.manually',
'target': 'new',
'context': dict(self.env.context, default_folder_id=self.id),
'view_type': 'form',
'view_mode': 'form'}
@api.multi
def get_msgids(self, connection):
"""Return imap ids of messages to process"""
return connection.search(None, 'UNDELETED')
@api.multi
def retrieve_imap_folder(self):
"""Retrieve all mails for one IMAP folder.
For each folder on each server we create a separate connection.
"""
for this in self:
server = this.server_id
try:
_logger.info(
'start checking for emails in folder %s on server %s',
this.path, server.name)
imap_server = server.connect()
if imap_server.select(this.path)[0] != 'OK':
_logger.error(
'Could not open mailbox %s on %s',
this.path, server.name)
continue
result, msgids = this.get_msgids(imap_server)
if result != 'OK':
_logger.error(
'Could not search mailbox %s on %s',
this.path, server.name)
continue
match_algorithm = this.get_algorithm()
for msgid in msgids[0].split():
this.apply_matching(
imap_server, msgid, match_algorithm)
_logger.info(
'finished checking for emails in %s server %s',
this.path, server.name)
except Exception:
_logger.info(_(
"General failure when trying to fetch mail from"
" %s server %s."),
server.type, server.name, exc_info=True)
finally:
if imap_server:
imap_server.close()
imap_server.logout()
@api.multi
def apply_matching(self, connection, msgid, match_algorithm):
"""Return ids of objects matched"""
self.ensure_one()
result, msgdata = connection.fetch(msgid, '(RFC822)')
if result != 'OK':
_logger.error(
'Could not fetch %s in %s on %s',
msgid, self.path, self.server_id.server)
return
mail_message = self.env['mail.thread'].message_parse(
msgdata[0][1], save_original=self.server_id.original)
if self.env['mail.message'].search(
[('message_id', '=', mail_message['message_id'])]):
# Ignore mails that have been handled already
return
matches = match_algorithm.search_matches(
self, mail_message, msgdata[0][1])
if matches and (len(matches) == 1 or self.match_first):
try:
self.env.cr.execute('savepoint apply_matching')
match_algorithm.handle_match(
connection,
matches[0], self, mail_message,
msgdata[0][1], msgid)
self.env.cr.execute('release savepoint apply_matching')
except Exception:
self.env.cr.execute('rollback to savepoint apply_matching')
_logger.exception(
"Failed to fetch mail %s from %s",
msgid, self.server_id.name)
elif self.flag_nonmatching:
connection.store(msgid, '+FLAGS', '\\FLAGGED')
@api.multi
def attach_mail(self, match_object, mail_message):
"""Attach mail to match_object."""
self.ensure_one()
partner = False
model_name = self.model_id.model
if model_name == 'res.partner':
partner = match_object
elif 'partner_id' in self.env[model_name]._fields:
partner = match_object.partner_id
attachments = []
if self.server_id.attach and mail_message.get('attachments'):
for attachment in mail_message['attachments']:
# Attachment should at least have filename and data, but
# might have some extra element(s)
if len(attachment) < 2:
continue
fname, fcontent = attachment[:2]
if isinstance(fcontent, unicode):
fcontent = fcontent.encode('utf-8')
data_attach = {
'name': fname,
'datas': base64.b64encode(str(fcontent)),
'datas_fname': fname,
'description': _('Mail attachment'),
'res_model': model_name,
'res_id': match_object.id}
attachments.append(
self.env['ir.attachment'].create(data_attach))
self.env['mail.message'].create({
'author_id': partner and partner.id or False,
'model': model_name,
'res_id': match_object.id,
'message_type': 'email',
'body': mail_message.get('body'),
'subject': mail_message.get('subject'),
'email_from': mail_message.get('from'),
'date': mail_message.get('date'),
'message_id': mail_message.get('message_id'),
'attachment_ids': [(6, 0, [a.id for a in attachments])]})

21
fetchmail_attach_from_folder/tests/__init__.py

@ -1,21 +1,4 @@
# -*- coding: utf-8 -*- # -*- 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/>.
#
##############################################################################
# Copyright - 2015-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_match_algorithms from . import test_match_algorithms

147
fetchmail_attach_from_folder/tests/test_match_algorithms.py

@ -1,41 +1,70 @@
# -*- coding: utf-8 -*- # -*- 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)
# Copyright - 2015-2018 Therp BV <https://acme.com>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models
from odoo.tests.common import TransactionCase
from ..match_algorithm import email_exact, email_domain, odoo_standard
MSG_BODY = [
('1 (RFC822 {1149}',
'Return-Path: <ronald@acme.com>\r\n'
'Delivered-To: demo@yourcompany.example.com\r\n'
'Received: from localhost (localhost [127.0.0.1])\r\n'
'\tby vanaheim.acme.com (Postfix) with ESMTP id 14A3183163\r\n'
'\tfor <demo@yourcompany.example.com>;'
' Mon, 26 Mar 2018 16:03:52 +0200 (CEST)\r\n'
'To: Test User <demo@yourcompany.example.com>\r\n'
'From: Ronald Portier <ronald@acme.com>\r\n'
'Subject: test\r\n'
'Message-ID: <485a8041-d560-a981-5afc-d31c1f136748@acme.com>\r\n'
'Date: Mon, 26 Mar 2018 16:03:51 +0200\r\n'
'User-Agent: Mock Test\r\n'
'MIME-Version: 1.0\r\n'
'Content-Type: text/plain; charset=utf-8\r\n'
'Content-Language: en-US\r\n'
'Content-Transfer-Encoding: 7bit\r\n\r\n'
'Hallo Wereld!\r\n'),
')']
class MockConnection():
def store(self, msgid, msg_item, value):
"""Mock store command."""
return 'OK'
def fetch(self, msgid, parts):
"""Return RFC822 formatted message."""
return ('OK', MSG_BODY)
class TestMatchAlgorithms(TransactionCase): class TestMatchAlgorithms(TransactionCase):
def do_matching(self, match_algorithm, expected_xmlid, conf, mail_message,
def _get_base_folder(self):
server_model = self.env['fetchmail.server']
folder_model = self.env['fetchmail.server.folder']
folder = folder_model.browse([models.NewId()])
folder.model_id = self.env.ref('base.model_res_partner').id
folder.model_field = 'email'
folder.match_algorithm = 'EmailExact'
folder.mail_field = 'to,from'
folder.server_id = server_model.browse([models.NewId()])
return folder
def do_matching(
self, match_algorithm, expected_xmlid, folder, mail_message,
mail_message_org=None): mail_message_org=None):
matcher = match_algorithm() matcher = match_algorithm()
matches = matcher.search_matches( matches = matcher.search_matches(
self.env.cr, self.env.uid, conf, mail_message, mail_message_org)
folder, mail_message, mail_message_org)
self.assertEqual(len(matches), 1) self.assertEqual(len(matches), 1)
self.assertEqual( self.assertEqual(
matches[0], self.env.ref(expected_xmlid).id)
matches[0], self.env.ref(expected_xmlid))
connection = MockConnection()
matcher.handle_match( matcher.handle_match(
self.env.cr, self.env.uid, None, matches[0], conf, mail_message,
mail_message_org, None)
connection, matches[0], folder, mail_message, mail_message_org,
None)
def test_email_exact(self): def test_email_exact(self):
mail_message = { mail_message = {
@ -43,15 +72,11 @@ class TestMatchAlgorithms(TransactionCase):
'to': 'demo@yourcompany.example.com', 'to': 'demo@yourcompany.example.com',
'from': 'someone@else.com', 'from': 'someone@else.com',
} }
conf = self.env['fetchmail.server.folder'].browse([models.NewId()])
conf.model_id = self.env.ref('base.model_res_partner').id
conf.model_field = 'email'
conf.match_algorithm = 'email_exact'
conf.mail_field = 'to,from'
conf.server_id = self.env['fetchmail.server'].browse([models.NewId()])
folder = self._get_base_folder()
folder.match_algorithm = 'EmailExact'
self.do_matching( self.do_matching(
email_exact.email_exact, 'base.user_demo_res_partner',
conf, mail_message)
email_exact.EmailExact, 'base.user_demo_res_partner',
folder, mail_message)
self.assertEqual( self.assertEqual(
self.env.ref('base.user_demo_res_partner').message_ids.subject, self.env.ref('base.user_demo_res_partner').message_ids.subject,
mail_message['subject']) mail_message['subject'])
@ -61,22 +86,18 @@ class TestMatchAlgorithms(TransactionCase):
'subject': 'Testsubject', 'subject': 'Testsubject',
'to': 'test@seagate.com', 'to': 'test@seagate.com',
'from': 'someone@else.com', 'from': 'someone@else.com',
}
conf = self.env['fetchmail.server.folder'].browse([models.NewId()])
conf.model_id = self.env.ref('base.model_res_partner').id
conf.model_field = 'email'
conf.match_algorithm = 'email_domain'
conf.mail_field = 'to,from'
conf.use_first_match = True
conf.server_id = self.env['fetchmail.server'].browse([models.NewId()])
'attachments': [('hello.txt', 'Hello World!')]}
folder = self._get_base_folder()
folder.match_algorithm = 'EmailDomain'
folder.use_first_match = True
self.do_matching( self.do_matching(
email_domain.email_domain, 'base.res_partner_address_31',
conf, mail_message)
email_domain.EmailDomain, 'base.res_partner_address_31',
folder, mail_message)
self.assertEqual( self.assertEqual(
self.env.ref('base.res_partner_address_31').message_ids.subject, self.env.ref('base.res_partner_address_31').message_ids.subject,
mail_message['subject']) mail_message['subject'])
def test_openerp_standard(self):
def test_odoo_standard(self):
mail_message_org = ( mail_message_org = (
"To: demo@yourcompany.example.com\n" "To: demo@yourcompany.example.com\n"
"From: someone@else.com\n" "From: someone@else.com\n"
@ -84,20 +105,32 @@ class TestMatchAlgorithms(TransactionCase):
"Message-Id: 42\n" "Message-Id: 42\n"
"Hello world" "Hello world"
) )
conf = self.env['fetchmail.server.folder'].browse([models.NewId()])
conf.model_id = self.env.ref('base.model_res_partner').id
conf.model_field = 'email'
conf.match_algorithm = 'openerp_standard'
conf.mail_field = 'to,from'
conf.server_id = self.env['fetchmail.server'].browse([models.NewId()])
matcher = openerp_standard.openerp_standard()
folder = self._get_base_folder()
folder.match_algorithm = 'OdooStandard'
matcher = odoo_standard.OdooStandard()
matches = matcher.search_matches( matches = matcher.search_matches(
self.env.cr, self.env.uid, conf, None, mail_message_org)
folder, None, mail_message_org)
self.assertEqual(len(matches), 1) self.assertEqual(len(matches), 1)
matcher.handle_match( matcher.handle_match(
self.env.cr, self.env.uid, None, matches[0], conf, None,
mail_message_org, None, None)
None, matches[0], folder, None, mail_message_org, None)
self.assertIn( self.assertIn(
'Hello world', 'Hello world',
self.env['mail.message'] self.env['mail.message']
.search([('subject', '=', 'testsubject')]).body) .search([('subject', '=', 'testsubject')]).body)
def test_apply_matching_exact(self):
folder = self._get_base_folder()
folder.match_algorithm = 'EmailExact'
connection = MockConnection()
msgid = "<485a8041-d560-a981-5afc-d31c1f136748@acme.com>"
matcher = email_exact.EmailExact()
folder.apply_matching(connection, msgid, matcher)
def test_field_view_get(self):
"""For the moment just check execution withouth errors."""
server_model = self.env['fetchmail.server']
view = server_model.fields_view_get()
self.assertTrue(view)
self.assertIn(
'match_algorithm',
view['fields']['folder_ids']['views']['form']['arch'])

58
fetchmail_attach_from_folder/view/fetchmail_server.xml

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_email_server_form">
<field name="name">fetchmail.server.form</field>
<field name="model">fetchmail.server</field>
<field name="inherit_id" ref="fetchmail.view_email_server_form" />
<field name="arch" type="xml">
<data>
<field name="object_id" position="attributes">
<attribute name="attrs">{'required': [('type', '!=', 'imap')]}</attribute>
</field>
<xpath expr="//page[@string='Server &amp; Login']/group/group[3]" position="after">
<group attrs="{'invisible': [('type','!=','imap')]}" string="Folders to monitor">
<field
name="folder_ids"
nolabel="1"
on_change="onchange_server_type(type, is_ssl, object_id)">
<tree colors="gray:active==False">
<field name="active" invisible="True" />
<field name="sequence" widget="handle" />
<field name="path" />
<field name="model_id" />
<field name="model_field" />
<field name="match_algorithm" />
<field name="mail_field" />
</tree>
<form version="7.0">
<header>
<button type="object" name="button_attach_mail_manually" string="Attach mail manually" icon="gtk-redo" />
</header>
<group>
<group>
<field name="path" placeholder="INBOX.subfolder1" />
<field name="model_id" />
<field name="model_field" placeholder="email" />
<field name="match_algorithm" />
<field name="mail_field" placeholder="to,from" />
</group>
<group>
<field name="active" />
<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)]}" placeholder="name asc,type desc" />
<field name="domain" placeholder="[('state', '=', 'open')]" />
</group>
</group>
</form>
</field>
</group>
</xpath>
</data>
</field>
</record>
</data>
</openerp>

80
fetchmail_attach_from_folder/views/fetchmail_server.xml

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="view_email_server_form">
<field name="name">fetchmail.server.form</field>
<field name="model">fetchmail.server</field>
<field name="inherit_id" ref="fetchmail.view_email_server_form" />
<field name="arch" type="xml">
<field name="object_id" position="attributes">
<attribute
name="attrs">{'required': [('type', '!=', 'imap')]}</attribute>
</field>
<field name="type" position="after">
<field name="folders_only" />
</field>
<xpath
expr="//notebook"
position="inside">
<page
string="Folders to monitor"
attrs="{'invisible': [('type','!=','imap')]}">
<group>
<field
name="folder_ids"
nolabel="1">
<tree colors="gray:active==False">
<field name="active" invisible="True" />
<field name="sequence" widget="handle" />
<field name="path" />
<field name="model_id" />
<field name="model_field" />
<field name="match_algorithm" />
<field name="mail_field" />
</tree>
<form>
<header>
<button
type="object"
name="button_attach_mail_manually"
string="Attach mail manually"
icon="gtk-redo" />
</header>
<group>
<group>
<field
name="path"
placeholder="INBOX.subfolder1" />
<field name="model_id" />
<field
name="model_field"
placeholder="email" />
<field name="match_algorithm" />
<field
name="mail_field"
placeholder="to,from" />
</group>
<group>
<field name="active" />
<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)]}"
placeholder="name asc,type desc" />
<field
name="domain"
placeholder="[('state', '=', 'open')]" />
</group>
</group>
</form>
</field>
</group>
</page>
</xpath>
</field>
</record>
</odoo>

25
fetchmail_attach_from_folder/wizard/__init__.py

@ -1,23 +1,4 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import attach_mail_manually from . import attach_mail_manually

130
fetchmail_attach_from_folder/wizard/attach_mail_manually.py

@ -1,30 +1,15 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<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 fields, models
# -*- coding: utf-8 -*-
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class attach_mail_manually(models.TransientModel):
class AttachMailManually(models.TransientModel):
_name = 'fetchmail.attach.mail.manually' _name = 'fetchmail.attach.mail.manually'
folder_id = fields.Many2one( folder_id = fields.Many2one(
@ -32,17 +17,13 @@ class attach_mail_manually(models.TransientModel):
mail_ids = fields.One2many( mail_ids = fields.One2many(
'fetchmail.attach.mail.manually.mail', 'wizard_id', 'Emails') 'fetchmail.attach.mail.manually.mail', 'wizard_id', 'Emails')
def default_get(self, cr, uid, fields_list, context=None):
if context is None:
context = {}
defaults = super(attach_mail_manually, self).default_get(
cr, uid, fields_list, context
)
for folder in self.pool.get('fetchmail.server.folder').browse(
cr, uid,
[context.get('default_folder_id')], context):
@api.model
def default_get(self, fields_list):
folder_model = self.env['fetchmail.server.folder']
thread_model = self.env['mail.thread']
defaults = super(AttachMailManually, self).default_get(fields_list)
default_folder_id = self.env.context.get('default_folder_id')
for folder in folder_model.browse([default_folder_id]):
defaults['mail_ids'] = [] defaults['mail_ids'] = []
connection = folder.server_id.connect() connection = folder.server_id.connect()
connection.select(folder.path) connection.select(folder.path)
@ -50,70 +31,72 @@ class attach_mail_manually(models.TransientModel):
None, None,
'FLAGGED' if folder.flag_nonmatching else 'UNDELETED') 'FLAGGED' if folder.flag_nonmatching else 'UNDELETED')
if result != 'OK': if result != 'OK':
_logger.error('Could not search mailbox %s on %s',
_logger.error(
'Could not search mailbox %s on %s',
folder.path, folder.server_id.name) folder.path, folder.server_id.name)
continue continue
for msgid in msgids[0].split(): for msgid in msgids[0].split():
result, msgdata = connection.fetch(msgid, '(RFC822)') result, msgdata = connection.fetch(msgid, '(RFC822)')
if result != 'OK': if result != 'OK':
_logger.error('Could not fetch %s in %s on %s',
_logger.error(
'Could not fetch %s in %s on %s',
msgid, folder.path, folder.server_id.name) msgid, folder.path, folder.server_id.name)
continue continue
mail_message = self.pool.get('mail.thread').message_parse(
cr, uid, msgdata[0][1],
save_original=folder.server_id.original,
context=context
)
mail_message = thread_model.message_parse(
msgdata[0][1],
save_original=folder.server_id.original)
defaults['mail_ids'].append((0, 0, { defaults['mail_ids'].append((0, 0, {
'msgid': msgid, 'msgid': msgid,
'subject': mail_message.get('subject', ''), 'subject': mail_message.get('subject', ''),
'date': mail_message.get('date', ''), 'date': mail_message.get('date', ''),
'object_id': '%s,-1' % folder.model_id.model,
}))
'object_id': '%s,-1' % folder.model_id.model}))
connection.close() connection.close()
return defaults return defaults
def attach_mails(self, cr, uid, ids, context=None):
for this in self.browse(cr, uid, ids, context):
@api.multi
def attach_mails(self):
thread_model = self.env['mail.thread']
for this in self:
folder = this.folder_id
server = folder.server_id
connection = server.connect()
connection.select(folder.path)
for mail in this.mail_ids: for mail in this.mail_ids:
connection = this.folder_id.server_id.connect()
connection.select(this.folder_id.path)
if not mail.object_id:
continue
result, msgdata = connection.fetch(mail.msgid, '(RFC822)') result, msgdata = connection.fetch(mail.msgid, '(RFC822)')
if result != 'OK': if result != 'OK':
_logger.error('Could not fetch %s in %s on %s',
mail.msgid, this.folder_id.path, this.server)
_logger.error(
'Could not fetch %s in %s on %s',
mail.msgid, folder.path, server)
continue continue
mail_message = self.pool.get('mail.thread').message_parse(
cr, uid, msgdata[0][1],
save_original=this.folder_id.server_id.original,
context=context)
this.folder_id.server_id.attach_mail(
connection,
mail.object_id.id, this.folder_id, mail_message,
mail.msgid
)
mail_message = thread_model.message_parse(
msgdata[0][1], save_original=server.original)
folder.attach_mail(mail.object_id, mail_message)
if folder.delete_matching:
connection.store(mail.msgid, '+FLAGS', '\\DELETED')
elif folder.flag_nonmatching:
connection.store(mail.msgid, '-FLAGS', '\\FLAGGED')
connection.close() connection.close()
return {'type': 'ir.actions.act_window_close'} return {'type': 'ir.actions.act_window_close'}
def fields_view_get(self, cr, user, view_id=None, view_type='form',
context=None, toolbar=False, submenu=False):
result = super(attach_mail_manually, self).fields_view_get(
cr, user, view_id, view_type, context, toolbar, submenu)
@api.model
def fields_view_get(
self, view_id=None, view_type='form',
toolbar=False, submenu=False):
result = super(AttachMailManually, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
tree = result['fields']['mail_ids']['views']['tree'] tree = result['fields']['mail_ids']['views']['tree']
for folder in self.pool['fetchmail.server.folder'].browse(
cr, user, [context.get('default_folder_id')], context):
folder_model = self.env['fetchmail.server.folder']
default_folder_id = self.env.context.get('default_folder_id')
for folder in folder_model.browse([default_folder_id]):
tree['fields']['object_id']['selection'] = [ tree['fields']['object_id']['selection'] = [
(folder.model_id.model, folder.model_id.name)
]
(folder.model_id.model, folder.model_id.name)]
return result return result
class attach_mail_manually_mail(models.TransientModel):
class AttachMailManuallyMail(models.TransientModel):
_name = 'fetchmail.attach.mail.manually.mail' _name = 'fetchmail.attach.mail.manually.mail'
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
@ -124,6 +107,5 @@ class attach_mail_manually_mail(models.TransientModel):
object_id = fields.Reference( object_id = fields.Reference(
lambda self: [ lambda self: [
(m.model, m.name) (m.model, m.name)
for m in self.env['ir.model'].search([])
],
for m in self.env['ir.model'].search([])],
string='Object') string='Object')

19
fetchmail_attach_from_folder/wizard/attach_mail_manually.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record model="ir.ui.view" id="view_attach_mail_manually"> <record model="ir.ui.view" id="view_attach_mail_manually">
<field name="name">fetchmail.attach.mail.manually</field> <field name="name">fetchmail.attach.mail.manually</field>
<field name="model">fetchmail.attach.mail.manually</field> <field name="model">fetchmail.attach.mail.manually</field>
@ -17,12 +17,19 @@
</field> </field>
</group> </group>
<footer> <footer>
<button string="Save" type="object" name="attach_mails" class="oe_highlight" />
<button
string="Save"
type="object"
name="attach_mails"
class="oe_highlight" />
or or
<button special="cancel" string="Cancel" class="oe_link" />
<button
special="cancel"
string="Cancel"
class="oe_link" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
</data>
</openerp>
</odoo>
Loading…
Cancel
Save