Ronald Portier
7 years ago
committed by
Holger Brunn
22 changed files with 715 additions and 911 deletions
-
14fetchmail_attach_from_folder/README.rst
-
27fetchmail_attach_from_folder/__init__.py
-
42fetchmail_attach_from_folder/__manifest__.py
-
25fetchmail_attach_from_folder/match_algorithm/__init__.py
-
48fetchmail_attach_from_folder/match_algorithm/base.py
-
54fetchmail_attach_from_folder/match_algorithm/email_domain.py
-
57fetchmail_attach_from_folder/match_algorithm/email_exact.py
-
33fetchmail_attach_from_folder/match_algorithm/odoo_standard.py
-
58fetchmail_attach_from_folder/match_algorithm/openerp_standard.py
-
24fetchmail_attach_from_folder/model/__init__.py
-
266fetchmail_attach_from_folder/model/fetchmail_server.py
-
118fetchmail_attach_from_folder/model/fetchmail_server_folder.py
-
5fetchmail_attach_from_folder/models/__init__.py
-
105fetchmail_attach_from_folder/models/fetchmail_server.py
-
220fetchmail_attach_from_folder/models/fetchmail_server_folder.py
-
21fetchmail_attach_from_folder/tests/__init__.py
-
147fetchmail_attach_from_folder/tests/test_match_algorithms.py
-
58fetchmail_attach_from_folder/view/fetchmail_server.xml
-
80fetchmail_attach_from_folder/views/fetchmail_server.xml
-
25fetchmail_attach_from_folder/wizard/__init__.py
-
130fetchmail_attach_from_folder/wizard/attach_mail_manually.py
-
19fetchmail_attach_from_folder/wizard/attach_mail_manually.xml
@ -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 |
@ -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', |
||||
|
], |
||||
|
}, |
||||
} |
} |
@ -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 |
@ -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') |
@ -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 |
@ -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) |
@ -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') |
@ -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] |
|
@ -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 |
|
@ -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 |
|
@ -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', |
|
||||
} |
|
@ -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 |
@ -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 |
@ -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])]}) |
@ -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 |
@ -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 & 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> |
|
@ -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> |
@ -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 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue