[IMP] fetchmail_attach_from_folder: pre-commit stuff
parent
95ae813886
commit
be09001e33
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from . import match_algorithm
|
from . import match_algorithm
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# 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': '10.0.1.1.0',
|
"version": "10.0.1.1.0",
|
||||||
'author': 'Therp BV,Odoo Community Association (OCA)',
|
"author": "Therp BV,Odoo Community Association (OCA)",
|
||||||
'website': 'http://www.therp.nl',
|
"website": "https://github.com/OCA/server-tools",
|
||||||
'license': 'AGPL-3',
|
"license": "AGPL-3",
|
||||||
'category': 'Tools',
|
"category": "Tools",
|
||||||
'depends': ['fetchmail'],
|
"depends": ["fetchmail"],
|
||||||
'data': [
|
"data": [
|
||||||
'views/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': True,
|
"installable": True,
|
||||||
'auto_install': False,
|
"auto_install": False,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from . import base
|
from . import base
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
@ -17,7 +16,7 @@ class Base(object):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def handle_match(
|
def handle_match(
|
||||||
self, connection, match_object, folder,
|
self, connection, match_object, folder, mail_message, mail_message_org, msgid
|
||||||
mail_message, mail_message_org, msgid):
|
):
|
||||||
"""Do whatever it takes to handle a match"""
|
"""Do whatever it takes to handle a match"""
|
||||||
folder.attach_mail(match_object, mail_message)
|
folder.attach_mail(match_object, mail_message)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from .email_exact import EmailExact
|
from .email_exact import EmailExact
|
||||||
|
@ -9,7 +8,8 @@ class EmailDomain(EmailExact):
|
||||||
|
|
||||||
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, folder, mail_message):
|
def search_matches(self, folder, mail_message):
|
||||||
"""Returns recordset of matching objects."""
|
"""Returns recordset of matching objects."""
|
||||||
|
@ -18,11 +18,14 @@ class EmailDomain(EmailExact):
|
||||||
object_model = folder.env[folder.model_id.model]
|
object_model = folder.env[folder.model_id.model]
|
||||||
domains = []
|
domains = []
|
||||||
for addr in self._get_mailaddresses(folder, mail_message):
|
for addr in self._get_mailaddresses(folder, mail_message):
|
||||||
domains.append(addr.split('@')[-1])
|
domains.append(addr.split("@")[-1])
|
||||||
matches = object_model.search(
|
matches = object_model.search(
|
||||||
self._get_mailaddress_search_domain(
|
self._get_mailaddress_search_domain(
|
||||||
folder, mail_message,
|
folder,
|
||||||
operator='like',
|
mail_message,
|
||||||
values=['%@' + domain for domain in set(domains)]),
|
operator="like",
|
||||||
order=folder.model_order)
|
values=["%@" + domain for domain in set(domains)],
|
||||||
|
),
|
||||||
|
order=folder.model_order,
|
||||||
|
)
|
||||||
return matches
|
return matches
|
||||||
|
|
|
@ -1,39 +1,40 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# 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 odoo.tools.mail import email_split
|
||||||
|
from odoo.tools.safe_eval import safe_eval
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base
|
||||||
|
|
||||||
|
|
||||||
class EmailExact(Base):
|
class EmailExact(Base):
|
||||||
"""Search for exactly the mailadress as noted in the email"""
|
"""Search for exactly the mailadress as noted in the email"""
|
||||||
name = 'Exact mailadress'
|
|
||||||
required_fields = ['model_field', 'mail_field']
|
name = "Exact mailadress"
|
||||||
|
required_fields = ["model_field", "mail_field"]
|
||||||
|
|
||||||
def _get_mailaddresses(self, folder, mail_message):
|
def _get_mailaddresses(self, folder, mail_message):
|
||||||
mailaddresses = []
|
mailaddresses = []
|
||||||
fields = folder.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, folder, mail_message, operator='=', values=None):
|
self, folder, mail_message, operator="=", values=None
|
||||||
mailaddresses = values or self._get_mailaddresses(
|
):
|
||||||
folder, mail_message)
|
mailaddresses = values or self._get_mailaddresses(folder, mail_message)
|
||||||
if not mailaddresses:
|
if not mailaddresses:
|
||||||
return [(0, '=', 1)]
|
return [(0, "=", 1)]
|
||||||
search_domain = ((['|'] * (len(mailaddresses) - 1)) + [
|
search_domain = (
|
||||||
(folder.model_field, operator, addr) for addr in mailaddresses] +
|
(["|"] * (len(mailaddresses) - 1))
|
||||||
safe_eval(folder.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, folder, mail_message):
|
def search_matches(self, folder, mail_message):
|
||||||
"""Returns recordset of matching objects."""
|
"""Returns recordset of matching objects."""
|
||||||
object_model = folder.env[folder.model_id.model]
|
object_model = folder.env[folder.model_id.model]
|
||||||
search_domain = self._get_mailaddress_search_domain(
|
search_domain = self._get_mailaddress_search_domain(folder, mail_message)
|
||||||
folder, mail_message)
|
|
||||||
return object_model.search(search_domain, order=folder.model_order)
|
return object_model.search(search_domain, order=folder.model_order)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from .base import Base
|
from .base import Base
|
||||||
|
@ -7,14 +6,15 @@ from .base import Base
|
||||||
class OdooStandard(Base):
|
class OdooStandard(Base):
|
||||||
"""No search at all. Use Odoo's standard mechanism to attach mails to
|
"""No search at all. Use Odoo's standard mechanism to attach mails to
|
||||||
mail.thread objects. Note that this algorithm always matches."""
|
mail.thread objects. Note that this algorithm always matches."""
|
||||||
name = 'Odoo standard'
|
|
||||||
|
name = "Odoo standard"
|
||||||
readonly_fields = [
|
readonly_fields = [
|
||||||
'model_field',
|
"model_field",
|
||||||
'mail_field',
|
"mail_field",
|
||||||
'match_first',
|
"match_first",
|
||||||
'domain',
|
"domain",
|
||||||
'model_order',
|
"model_order",
|
||||||
'flag_nonmatching',
|
"flag_nonmatching",
|
||||||
]
|
]
|
||||||
|
|
||||||
def search_matches(self, folder, mail_message):
|
def search_matches(self, folder, mail_message):
|
||||||
|
@ -22,10 +22,12 @@ class OdooStandard(Base):
|
||||||
return [True]
|
return [True]
|
||||||
|
|
||||||
def handle_match(
|
def handle_match(
|
||||||
self, connection, match_object, folder,
|
self, connection, match_object, folder, mail_message, mail_message_org, msgid
|
||||||
mail_message, mail_message_org, msgid):
|
):
|
||||||
thread_model = folder.env['mail.thread']
|
thread_model = folder.env["mail.thread"]
|
||||||
thread_model.message_process(
|
thread_model.message_process(
|
||||||
folder.model_id.model, mail_message_org,
|
folder.model_id.model,
|
||||||
|
mail_message_org,
|
||||||
save_original=folder.server_id.original,
|
save_original=folder.server_id.original,
|
||||||
strip_attachments=(not folder.server_id.attach))
|
strip_attachments=(not folder.server_id.attach),
|
||||||
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from . import fetchmail_server
|
from . import fetchmail_server
|
||||||
|
|
|
@ -1,69 +1,70 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.tools.safe_eval import safe_eval
|
|
||||||
from odoo.tools.misc import UnquoteEvalContext
|
from odoo.tools.misc import UnquoteEvalContext
|
||||||
|
from odoo.tools.safe_eval import safe_eval
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
list_response_pattern = re.compile(
|
list_response_pattern = re.compile(
|
||||||
r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
|
r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FetchmailServer(models.Model):
|
class FetchmailServer(models.Model):
|
||||||
_inherit = 'fetchmail.server'
|
_inherit = "fetchmail.server"
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_folders_available(self):
|
def _compute_folders_available(self):
|
||||||
"""Retrieve available folders from IMAP server."""
|
"""Retrieve available folders from IMAP server."""
|
||||||
|
|
||||||
def parse_list_response(line):
|
def parse_list_response(line):
|
||||||
flags, delimiter, mailbox_name = \
|
flags, delimiter, mailbox_name = list_response_pattern.match(line).groups()
|
||||||
list_response_pattern.match(line).groups()
|
|
||||||
mailbox_name = mailbox_name.strip('"')
|
mailbox_name = mailbox_name.strip('"')
|
||||||
return (flags, delimiter, mailbox_name)
|
return (flags, delimiter, mailbox_name)
|
||||||
|
|
||||||
for this in self:
|
for this in self:
|
||||||
if this.state != 'done':
|
if this.state != "done":
|
||||||
this.folders_available = _('Confirm connection first.')
|
this.folders_available = _("Confirm connection first.")
|
||||||
continue
|
continue
|
||||||
connection = this.connect()
|
connection = this.connect()
|
||||||
list_result = connection.list()
|
list_result = connection.list()
|
||||||
if list_result[0] != 'OK':
|
if list_result[0] != "OK":
|
||||||
this.folders_available = _('Unable to retrieve folders.')
|
this.folders_available = _("Unable to retrieve folders.")
|
||||||
continue
|
continue
|
||||||
folders_available = []
|
folders_available = []
|
||||||
for folder_entry in list_result[1]:
|
for folder_entry in list_result[1]:
|
||||||
folders_available.append(parse_list_response(folder_entry)[2])
|
folders_available.append(parse_list_response(folder_entry)[2])
|
||||||
this.folders_available = '\n'.join(folders_available)
|
this.folders_available = "\n".join(folders_available)
|
||||||
connection.logout()
|
connection.logout()
|
||||||
|
|
||||||
folders_available = fields.Text(
|
folders_available = fields.Text(
|
||||||
string='Available folders',
|
string="Available folders", compute="_compute_folders_available", readonly=True
|
||||||
compute='_compute_folders_available',
|
)
|
||||||
readonly=True)
|
|
||||||
folder_ids = fields.One2many(
|
folder_ids = fields.One2many(
|
||||||
comodel_name='fetchmail.server.folder',
|
comodel_name="fetchmail.server.folder",
|
||||||
inverse_name='server_id',
|
inverse_name="server_id",
|
||||||
string='Folders',
|
string="Folders",
|
||||||
context={'active_test': False})
|
context={"active_test": False},
|
||||||
|
)
|
||||||
object_id = fields.Many2one(required=False) # comodel_name='ir.model'
|
object_id = fields.Many2one(required=False) # comodel_name='ir.model'
|
||||||
type = fields.Selection(default='imap')
|
type = fields.Selection(default="imap")
|
||||||
folders_only = fields.Boolean(
|
folders_only = fields.Boolean(
|
||||||
string='Only folders, not inbox',
|
string="Only folders, not inbox",
|
||||||
help="Check this field to leave imap inbox alone"
|
help="Check this field to leave imap inbox alone"
|
||||||
" and only retrieve mail from configured folders.")
|
" and only retrieve mail from configured folders.",
|
||||||
|
)
|
||||||
|
|
||||||
@api.onchange('type', 'is_ssl', 'object_id')
|
@api.onchange("type", "is_ssl", "object_id")
|
||||||
def onchange_server_type(self):
|
def onchange_server_type(self):
|
||||||
super(FetchmailServer, self).onchange_server_type()
|
super(FetchmailServer, self).onchange_server_type()
|
||||||
self.state = 'draft'
|
self.state = "draft"
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def fetch_mail(self):
|
def fetch_mail(self):
|
||||||
|
@ -73,48 +74,51 @@ class FetchmailServer(models.Model):
|
||||||
this.folder_ids.fetch_mail()
|
this.folder_ids.fetch_mail()
|
||||||
|
|
||||||
def fields_view_get(
|
def fields_view_get(
|
||||||
self, view_id=None, view_type='form',
|
self, view_id=None, view_type="form", toolbar=False, submenu=False
|
||||||
toolbar=False, submenu=False):
|
):
|
||||||
"""Set modifiers for form fields in folder_ids depending on algorithm.
|
"""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
|
A field will be readonly and/or required if this is specified in the
|
||||||
algorithm.
|
algorithm.
|
||||||
"""
|
"""
|
||||||
result = super(FetchmailServer, self).fields_view_get(
|
result = super(FetchmailServer, self).fields_view_get(
|
||||||
view_id=view_id, view_type=view_type, toolbar=toolbar,
|
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
|
||||||
submenu=submenu)
|
)
|
||||||
if view_type == 'form':
|
if view_type == "form":
|
||||||
view = etree.fromstring(
|
view = etree.fromstring(
|
||||||
result['fields']['folder_ids']['views']['form']['arch'])
|
result["fields"]["folder_ids"]["views"]["form"]["arch"]
|
||||||
|
)
|
||||||
modifiers = {}
|
modifiers = {}
|
||||||
docstr = ''
|
docstr = ""
|
||||||
folder_model = self.env['fetchmail.server.folder']
|
folder_model = self.env["fetchmail.server.folder"]
|
||||||
match_algorithms = folder_model._get_match_algorithms()
|
match_algorithms = folder_model._get_match_algorithms()
|
||||||
for algorithm in match_algorithms.itervalues():
|
for algorithm in match_algorithms.itervalues():
|
||||||
for modifier in ['required', 'readonly']:
|
for modifier in ["required", "readonly"]:
|
||||||
for field in getattr(algorithm, modifier + '_fields'):
|
for field in getattr(algorithm, modifier + "_fields"):
|
||||||
modifiers.setdefault(field, {})
|
modifiers.setdefault(field, {})
|
||||||
modifiers[field].setdefault(modifier, [])
|
modifiers[field].setdefault(modifier, [])
|
||||||
if modifiers[field][modifier]:
|
if modifiers[field][modifier]:
|
||||||
modifiers[field][modifier].insert(0, '|')
|
modifiers[field][modifier].insert(0, "|")
|
||||||
modifiers[field][modifier].append(
|
modifiers[field][modifier].append(
|
||||||
("match_algorithm", "==", algorithm.__name__))
|
("match_algorithm", "==", algorithm.__name__)
|
||||||
docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
|
)
|
||||||
'\n\n'
|
docstr += _(algorithm.name) + "\n" + _(algorithm.__doc__) + "\n\n"
|
||||||
for field in view.xpath('//field'):
|
for field in view.xpath("//field"):
|
||||||
if field.tag == 'field' and field.get('name') in modifiers:
|
if field.tag == "field" and field.get("name") in modifiers:
|
||||||
patched_modifiers = field.attrib['modifiers'].replace(
|
patched_modifiers = (
|
||||||
'false', 'False').replace('true', 'True')
|
field.attrib["modifiers"]
|
||||||
|
.replace("false", "False")
|
||||||
|
.replace("true", "True")
|
||||||
|
)
|
||||||
original_dict = safe_eval(
|
original_dict = safe_eval(
|
||||||
patched_modifiers,
|
patched_modifiers, UnquoteEvalContext({}), nocopy=True
|
||||||
UnquoteEvalContext({}),
|
)
|
||||||
nocopy=True)
|
modifier_dict = modifiers[field.attrib["name"]]
|
||||||
modifier_dict = modifiers[field.attrib['name']]
|
|
||||||
combined_dict = dict(original_dict, **modifier_dict)
|
combined_dict = dict(original_dict, **modifier_dict)
|
||||||
field.set('modifiers', json.dumps(combined_dict))
|
field.set("modifiers", json.dumps(combined_dict))
|
||||||
if (field.tag == 'field' and
|
if field.tag == "field" and field.get("name") == "match_algorithm":
|
||||||
field.get('name') == 'match_algorithm'):
|
field.set("help", docstr)
|
||||||
field.set('help', docstr)
|
result["fields"]["folder_ids"]["views"]["form"]["arch"] = etree.tostring(
|
||||||
result['fields']['folder_ids']['views']['form']['arch'] = \
|
view
|
||||||
etree.tostring(view)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -1,32 +1,33 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import _, api, models, fields
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
from .. import match_algorithm
|
from .. import match_algorithm
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FetchmailServerFolder(models.Model):
|
class FetchmailServerFolder(models.Model):
|
||||||
_name = 'fetchmail.server.folder'
|
_name = "fetchmail.server.folder"
|
||||||
_rec_name = 'path'
|
_rec_name = "path"
|
||||||
_order = 'sequence'
|
_order = "sequence"
|
||||||
|
|
||||||
def _get_match_algorithms(self):
|
def _get_match_algorithms(self):
|
||||||
def get_all_subclasses(cls):
|
def get_all_subclasses(cls):
|
||||||
return (cls.__subclasses__() +
|
return cls.__subclasses__() + [
|
||||||
[subsub
|
subsub
|
||||||
for sub in cls.__subclasses__()
|
for sub in cls.__subclasses__()
|
||||||
for subsub in get_all_subclasses(sub)])
|
for subsub in get_all_subclasses(sub)
|
||||||
return dict([(cls.__name__, cls)
|
]
|
||||||
for cls in get_all_subclasses(
|
|
||||||
match_algorithm.base.Base)])
|
return {
|
||||||
|
cls.__name__: cls
|
||||||
|
for cls in get_all_subclasses(match_algorithm.base.Base)
|
||||||
|
}
|
||||||
|
|
||||||
def _get_match_algorithms_sel(self):
|
def _get_match_algorithms_sel(self):
|
||||||
algorithms = []
|
algorithms = []
|
||||||
|
@ -35,64 +36,73 @@ class FetchmailServerFolder(models.Model):
|
||||||
algorithms.sort()
|
algorithms.sort()
|
||||||
return algorithms
|
return algorithms
|
||||||
|
|
||||||
server_id = fields.Many2one('fetchmail.server', 'Server')
|
server_id = fields.Many2one("fetchmail.server", "Server")
|
||||||
sequence = fields.Integer('Sequence')
|
sequence = fields.Integer("Sequence")
|
||||||
state = fields.Selection([
|
state = fields.Selection(
|
||||||
('draft', 'Not Confirmed'),
|
[("draft", "Not Confirmed"), ("done", "Confirmed")],
|
||||||
('done', 'Confirmed')],
|
string="Status",
|
||||||
string='Status',
|
|
||||||
readonly=True,
|
readonly=True,
|
||||||
required=True,
|
required=True,
|
||||||
copy=False,
|
copy=False,
|
||||||
default='draft')
|
default="draft",
|
||||||
|
)
|
||||||
path = fields.Char(
|
path = fields.Char(
|
||||||
'Path',
|
"Path",
|
||||||
required=True,
|
required=True,
|
||||||
help="The path to your mail folder."
|
help="The path to your mail folder."
|
||||||
" Typically would be something like 'INBOX.myfolder'")
|
" Typically would be something like 'INBOX.myfolder'",
|
||||||
|
)
|
||||||
model_id = fields.Many2one(
|
model_id = fields.Many2one(
|
||||||
'ir.model', 'Model', required=True,
|
"ir.model", "Model", required=True, help="The model to attach emails to"
|
||||||
help='The model to attach emails to')
|
)
|
||||||
model_field = fields.Char(
|
model_field = fields.Char(
|
||||||
'Field (model)',
|
"Field (model)",
|
||||||
help='The field in your model that contains the field to match '
|
help="The field in your model that contains the field to match "
|
||||||
'against.\n'
|
"against.\n"
|
||||||
'Examples:\n'
|
"Examples:\n"
|
||||||
"'email' if your model is res.partner, or "
|
"'email' if your model is res.partner, or "
|
||||||
"'partner_id.email' if you're matching sale orders")
|
"'partner_id.email' if you're matching sale orders",
|
||||||
|
)
|
||||||
model_order = fields.Char(
|
model_order = fields.Char(
|
||||||
'Order (model)',
|
"Order (model)",
|
||||||
help='Field(s) to order by, this mostly useful in conjunction '
|
help="Field(s) to order by, this mostly useful in conjunction "
|
||||||
"with 'Use 1st match'")
|
"with 'Use 1st match'",
|
||||||
|
)
|
||||||
match_algorithm = fields.Selection(
|
match_algorithm = fields.Selection(
|
||||||
_get_match_algorithms_sel,
|
_get_match_algorithms_sel,
|
||||||
'Match algorithm', required=True,
|
"Match algorithm",
|
||||||
help='The algorithm used to determine which object an email matches.')
|
required=True,
|
||||||
|
help="The algorithm used to determine which object an email matches.",
|
||||||
|
)
|
||||||
mail_field = fields.Char(
|
mail_field = fields.Char(
|
||||||
'Field (email)',
|
"Field (email)",
|
||||||
help='The field in the email used for matching. Typically '
|
help="The field in the email used for matching. Typically "
|
||||||
"this is 'to' or 'from'")
|
"this is 'to' or 'from'",
|
||||||
|
)
|
||||||
delete_matching = fields.Boolean(
|
delete_matching = fields.Boolean(
|
||||||
'Delete matches',
|
"Delete matches", help="Delete matched emails from server"
|
||||||
help='Delete matched emails from server')
|
)
|
||||||
flag_nonmatching = fields.Boolean(
|
flag_nonmatching = fields.Boolean(
|
||||||
'Flag nonmatching',
|
"Flag nonmatching",
|
||||||
default=True,
|
default=True,
|
||||||
help="Flag emails in the server that don't match any object in Odoo")
|
help="Flag emails in the server that don't match any object in Odoo",
|
||||||
|
)
|
||||||
match_first = fields.Boolean(
|
match_first = fields.Boolean(
|
||||||
'Use 1st match',
|
"Use 1st match",
|
||||||
help='If there are multiple matches, use the first one. If '
|
help="If there are multiple matches, use the first one. If "
|
||||||
'not checked, multiple matches count as no match at all')
|
"not checked, multiple matches count as no match at all",
|
||||||
|
)
|
||||||
domain = fields.Char(
|
domain = fields.Char(
|
||||||
'Domain',
|
"Domain", help="Fill in a search filter to narrow down objects to match"
|
||||||
help='Fill in a search filter to narrow down objects to match')
|
)
|
||||||
msg_state = fields.Selection(
|
msg_state = fields.Selection(
|
||||||
selection=[('sent', 'Sent'), ('received', 'Received')],
|
selection=[("sent", "Sent"), ("received", "Received")],
|
||||||
string='Message state',
|
string="Message state",
|
||||||
default='received',
|
default="received",
|
||||||
help='The state messages fetched from this folder should be '
|
help="The state messages fetched from this folder should be "
|
||||||
'assigned in Odoo')
|
"assigned in Odoo",
|
||||||
active = fields.Boolean('Active', default=True)
|
)
|
||||||
|
active = fields.Boolean("Active", default=True)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def get_algorithm(self):
|
def get_algorithm(self):
|
||||||
|
@ -101,31 +111,31 @@ class FetchmailServerFolder(models.Model):
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_confirm_folder(self):
|
def button_confirm_folder(self):
|
||||||
for this in self:
|
for this in self:
|
||||||
this.write({'state': 'draft'})
|
this.write({"state": "draft"})
|
||||||
if not this.active:
|
if not this.active:
|
||||||
continue
|
continue
|
||||||
connection = this.server_id.connect()
|
connection = this.server_id.connect()
|
||||||
connection.select()
|
connection.select()
|
||||||
if connection.select(this.path)[0] != 'OK':
|
if connection.select(this.path)[0] != "OK":
|
||||||
raise ValidationError(
|
raise ValidationError(_("Invalid folder %s!") % this.path)
|
||||||
_('Invalid folder %s!') % this.path)
|
|
||||||
connection.close()
|
connection.close()
|
||||||
this.write({'state': 'done'})
|
this.write({"state": "done"})
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_attach_mail_manually(self):
|
def button_attach_mail_manually(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window',
|
"type": "ir.actions.act_window",
|
||||||
'res_model': 'fetchmail.attach.mail.manually',
|
"res_model": "fetchmail.attach.mail.manually",
|
||||||
'target': 'new',
|
"target": "new",
|
||||||
'context': dict(self.env.context, folder_id=self.id),
|
"context": dict(self.env.context, folder_id=self.id),
|
||||||
'view_type': 'form',
|
"view_type": "form",
|
||||||
'view_mode': 'form'}
|
"view_mode": "form",
|
||||||
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def set_draft(self):
|
def set_draft(self):
|
||||||
self.write({'state': 'draft'})
|
self.write({"state": "draft"})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -134,20 +144,22 @@ class FetchmailServerFolder(models.Model):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
server = self.server_id
|
server = self.server_id
|
||||||
_logger.info(
|
_logger.info(
|
||||||
'start checking for emails in folder %s on server %s',
|
"start checking for emails in folder %s on server %s",
|
||||||
self.path, server.name)
|
self.path,
|
||||||
if connection.select(self.path)[0] != 'OK':
|
server.name,
|
||||||
raise UserError(_(
|
)
|
||||||
"Could not open mailbox %s on %s") %
|
if connection.select(self.path)[0] != "OK":
|
||||||
(self.path, server.name))
|
raise UserError(
|
||||||
|
_("Could not open mailbox %s on %s") % (self.path, server.name)
|
||||||
|
)
|
||||||
result, msgids = connection.search(None, criteria)
|
result, msgids = connection.search(None, criteria)
|
||||||
if result != 'OK':
|
if result != "OK":
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
"Could not search mailbox %s on %s") %
|
_("Could not search mailbox %s on %s") % (self.path, server.name)
|
||||||
(self.path, server.name))
|
)
|
||||||
_logger.info(
|
_logger.info(
|
||||||
'finished checking for emails in %s on server %s',
|
"finished checking for emails in %s on server %s", self.path, server.name
|
||||||
self.path, server.name)
|
)
|
||||||
return msgids
|
return msgids
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -155,33 +167,34 @@ class FetchmailServerFolder(models.Model):
|
||||||
"""Select a single message from a folder."""
|
"""Select a single message from a folder."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
server = self.server_id
|
server = self.server_id
|
||||||
result, msgdata = connection.fetch(msgid, '(RFC822)')
|
result, msgdata = connection.fetch(msgid, "(RFC822)")
|
||||||
if result != 'OK':
|
if result != "OK":
|
||||||
raise UserError(_(
|
raise UserError(
|
||||||
"Could not fetch %s in %s on %s") %
|
_("Could not fetch %s in %s on %s") % (msgid, self.path, server.server)
|
||||||
(msgid, self.path, server.server))
|
)
|
||||||
message_org = msgdata[0][1] # rfc822 message source
|
message_org = msgdata[0][1] # rfc822 message source
|
||||||
mail_message = self.env['mail.thread'].message_parse(
|
mail_message = self.env["mail.thread"].message_parse(
|
||||||
message_org, save_original=server.original)
|
message_org, save_original=server.original
|
||||||
|
)
|
||||||
return (mail_message, message_org)
|
return (mail_message, message_org)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def retrieve_imap_folder(self, connection):
|
def retrieve_imap_folder(self, connection):
|
||||||
"""Retrieve all mails for one IMAP folder."""
|
"""Retrieve all mails for one IMAP folder."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
msgids = self.get_msgids(connection, 'UNDELETED')
|
msgids = self.get_msgids(connection, "UNDELETED")
|
||||||
match_algorithm = self.get_algorithm()
|
match_algorithm = self.get_algorithm()
|
||||||
for msgid in msgids[0].split():
|
for msgid in msgids[0].split():
|
||||||
# We will accept exceptions for single messages
|
# We will accept exceptions for single messages
|
||||||
try:
|
try:
|
||||||
self.env.cr.execute('savepoint apply_matching')
|
self.env.cr.execute("savepoint apply_matching")
|
||||||
self.apply_matching(connection, msgid, match_algorithm)
|
self.apply_matching(connection, msgid, match_algorithm)
|
||||||
self.env.cr.execute('release savepoint apply_matching')
|
self.env.cr.execute("release savepoint apply_matching")
|
||||||
except Exception:
|
except Exception:
|
||||||
self.env.cr.execute('rollback to savepoint apply_matching')
|
self.env.cr.execute("rollback to savepoint apply_matching")
|
||||||
_logger.exception(
|
_logger.exception(
|
||||||
"Failed to fetch mail %s from %s",
|
"Failed to fetch mail %s from %s", msgid, self.server_id.name
|
||||||
msgid, self.server_id.name)
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def fetch_mail(self):
|
def fetch_mail(self):
|
||||||
|
@ -190,7 +203,7 @@ class FetchmailServerFolder(models.Model):
|
||||||
We will use a separate connection for each folder.
|
We will use a separate connection for each folder.
|
||||||
"""
|
"""
|
||||||
for this in self:
|
for this in self:
|
||||||
if not this.active or this.state != 'done':
|
if not this.active or this.state != "done":
|
||||||
continue
|
continue
|
||||||
connection = None
|
connection = None
|
||||||
try:
|
try:
|
||||||
|
@ -199,9 +212,12 @@ class FetchmailServerFolder(models.Model):
|
||||||
this.retrieve_imap_folder(connection)
|
this.retrieve_imap_folder(connection)
|
||||||
connection.close()
|
connection.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.error(_(
|
_logger.error(
|
||||||
"General failure when trying to connect to %s server %s."),
|
_("General failure when trying to connect to %s server %s."),
|
||||||
this.server_id.type, this.server_id.name, exc_info=True)
|
this.server_id.type,
|
||||||
|
this.server_id.name,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
if connection:
|
if connection:
|
||||||
connection.logout()
|
connection.logout()
|
||||||
|
@ -211,29 +227,29 @@ class FetchmailServerFolder(models.Model):
|
||||||
"""Update msg in imap folder depending on match and settings."""
|
"""Update msg in imap folder depending on match and settings."""
|
||||||
if matched:
|
if matched:
|
||||||
if self.delete_matching:
|
if self.delete_matching:
|
||||||
connection.store(msgid, '+FLAGS', '\\DELETED')
|
connection.store(msgid, "+FLAGS", "\\DELETED")
|
||||||
elif flagged and self.flag_nonmatching:
|
elif flagged and self.flag_nonmatching:
|
||||||
connection.store(msgid, '-FLAGS', '\\FLAGGED')
|
connection.store(msgid, "-FLAGS", "\\FLAGGED")
|
||||||
else:
|
else:
|
||||||
if self.flag_nonmatching:
|
if self.flag_nonmatching:
|
||||||
connection.store(msgid, '+FLAGS', '\\FLAGGED')
|
connection.store(msgid, "+FLAGS", "\\FLAGGED")
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def apply_matching(self, connection, msgid, match_algorithm):
|
def apply_matching(self, connection, msgid, match_algorithm):
|
||||||
"""Return ids of objects matched"""
|
"""Return ids of objects matched"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
mail_message, message_org = self.fetch_msg(connection, msgid)
|
mail_message, message_org = self.fetch_msg(connection, msgid)
|
||||||
if self.env['mail.message'].search(
|
if self.env["mail.message"].search(
|
||||||
[('message_id', '=', mail_message['message_id'])]):
|
[("message_id", "=", mail_message["message_id"])]
|
||||||
|
):
|
||||||
# Ignore mails that have been handled already
|
# Ignore mails that have been handled already
|
||||||
return
|
return
|
||||||
matches = match_algorithm.search_matches(self, mail_message)
|
matches = match_algorithm.search_matches(self, mail_message)
|
||||||
matched = matches and (len(matches) == 1 or self.match_first)
|
matched = matches and (len(matches) == 1 or self.match_first)
|
||||||
if matched:
|
if matched:
|
||||||
match_algorithm.handle_match(
|
match_algorithm.handle_match(
|
||||||
connection,
|
connection, matches[0], self, mail_message, message_org, msgid
|
||||||
matches[0], self, mail_message,
|
)
|
||||||
message_org, msgid)
|
|
||||||
self.update_msg(connection, msgid, matched=matched)
|
self.update_msg(connection, msgid, matched=matched)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -242,37 +258,40 @@ class FetchmailServerFolder(models.Model):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
partner = False
|
partner = False
|
||||||
model_name = self.model_id.model
|
model_name = self.model_id.model
|
||||||
if model_name == 'res.partner':
|
if model_name == "res.partner":
|
||||||
partner = match_object
|
partner = match_object
|
||||||
elif 'partner_id' in self.env[model_name]._fields:
|
elif "partner_id" in self.env[model_name]._fields:
|
||||||
partner = match_object.partner_id
|
partner = match_object.partner_id
|
||||||
attachments = []
|
attachments = []
|
||||||
if self.server_id.attach and mail_message.get('attachments'):
|
if self.server_id.attach and mail_message.get("attachments"):
|
||||||
for attachment in mail_message['attachments']:
|
for attachment in mail_message["attachments"]:
|
||||||
# Attachment should at least have filename and data, but
|
# Attachment should at least have filename and data, but
|
||||||
# might have some extra element(s)
|
# might have some extra element(s)
|
||||||
if len(attachment) < 2:
|
if len(attachment) < 2:
|
||||||
continue
|
continue
|
||||||
fname, fcontent = attachment[:2]
|
fname, fcontent = attachment[:2]
|
||||||
if isinstance(fcontent, unicode):
|
if isinstance(fcontent, unicode):
|
||||||
fcontent = fcontent.encode('utf-8')
|
fcontent = fcontent.encode("utf-8")
|
||||||
data_attach = {
|
data_attach = {
|
||||||
'name': fname,
|
"name": fname,
|
||||||
'datas': base64.b64encode(str(fcontent)),
|
"datas": base64.b64encode(str(fcontent)),
|
||||||
'datas_fname': fname,
|
"datas_fname": fname,
|
||||||
'description': _('Mail attachment'),
|
"description": _("Mail attachment"),
|
||||||
'res_model': model_name,
|
"res_model": model_name,
|
||||||
'res_id': match_object.id}
|
"res_id": match_object.id,
|
||||||
attachments.append(
|
}
|
||||||
self.env['ir.attachment'].create(data_attach))
|
attachments.append(self.env["ir.attachment"].create(data_attach))
|
||||||
self.env['mail.message'].create({
|
self.env["mail.message"].create(
|
||||||
'author_id': partner and partner.id or False,
|
{
|
||||||
'model': model_name,
|
"author_id": partner and partner.id or False,
|
||||||
'res_id': match_object.id,
|
"model": model_name,
|
||||||
'message_type': 'email',
|
"res_id": match_object.id,
|
||||||
'body': mail_message.get('body'),
|
"message_type": "email",
|
||||||
'subject': mail_message.get('subject'),
|
"body": mail_message.get("body"),
|
||||||
'email_from': mail_message.get('from'),
|
"subject": mail_message.get("subject"),
|
||||||
'date': mail_message.get('date'),
|
"email_from": mail_message.get("from"),
|
||||||
'message_id': mail_message.get('message_id'),
|
"date": mail_message.get("date"),
|
||||||
'attachment_ids': [(6, 0, [a.id for a in attachments])]})
|
"message_id": mail_message.get("message_id"),
|
||||||
|
"attachment_ids": [(6, 0, [a.id for a in attachments])],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2015-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2015-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from . import test_match_algorithms
|
from . import test_match_algorithms
|
||||||
|
|
|
@ -1,108 +1,118 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2015-2018 Therp BV <https://acme.com>.
|
# Copyright - 2015-2018 Therp BV <https://acme.com>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from odoo import models
|
from odoo import models
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
from ..match_algorithm import email_exact, email_domain, odoo_standard
|
from ..match_algorithm import email_domain, email_exact, odoo_standard
|
||||||
|
|
||||||
MSG_BODY = [
|
MSG_BODY = [
|
||||||
('1 (RFC822 {1149}',
|
(
|
||||||
'Return-Path: <ronald@acme.com>\r\n'
|
"1 (RFC822 {1149}",
|
||||||
'Delivered-To: demo@yourcompany.example.com\r\n'
|
"Return-Path: <ronald@acme.com>\r\n"
|
||||||
'Received: from localhost (localhost [127.0.0.1])\r\n'
|
"Delivered-To: demo@yourcompany.example.com\r\n"
|
||||||
'\tby vanaheim.acme.com (Postfix) with ESMTP id 14A3183163\r\n'
|
"Received: from localhost (localhost [127.0.0.1])\r\n"
|
||||||
'\tfor <demo@yourcompany.example.com>;'
|
"\tby vanaheim.acme.com (Postfix) with ESMTP id 14A3183163\r\n"
|
||||||
' Mon, 26 Mar 2018 16:03:52 +0200 (CEST)\r\n'
|
"\tfor <demo@yourcompany.example.com>;"
|
||||||
'To: Test User <demo@yourcompany.example.com>\r\n'
|
" Mon, 26 Mar 2018 16:03:52 +0200 (CEST)\r\n"
|
||||||
'From: Ronald Portier <ronald@acme.com>\r\n'
|
"To: Test User <demo@yourcompany.example.com>\r\n"
|
||||||
'Subject: test\r\n'
|
"From: Ronald Portier <ronald@acme.com>\r\n"
|
||||||
'Message-ID: <485a8041-d560-a981-5afc-d31c1f136748@acme.com>\r\n'
|
"Subject: test\r\n"
|
||||||
'Date: Mon, 26 Mar 2018 16:03:51 +0200\r\n'
|
"Message-ID: <485a8041-d560-a981-5afc-d31c1f136748@acme.com>\r\n"
|
||||||
'User-Agent: Mock Test\r\n'
|
"Date: Mon, 26 Mar 2018 16:03:51 +0200\r\n"
|
||||||
'MIME-Version: 1.0\r\n'
|
"User-Agent: Mock Test\r\n"
|
||||||
'Content-Type: text/plain; charset=utf-8\r\n'
|
"MIME-Version: 1.0\r\n"
|
||||||
'Content-Language: en-US\r\n'
|
"Content-Type: text/plain; charset=utf-8\r\n"
|
||||||
'Content-Transfer-Encoding: 7bit\r\n\r\n'
|
"Content-Language: en-US\r\n"
|
||||||
'Hallo Wereld!\r\n'),
|
"Content-Transfer-Encoding: 7bit\r\n\r\n"
|
||||||
')']
|
"Hallo Wereld!\r\n",
|
||||||
|
),
|
||||||
|
")",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class MockConnection():
|
class MockConnection:
|
||||||
|
|
||||||
def select(self, path):
|
def select(self, path):
|
||||||
"""Mock selecting a folder."""
|
"""Mock selecting a folder."""
|
||||||
return ('OK', )
|
return ("OK",)
|
||||||
|
|
||||||
def store(self, msgid, msg_item, value):
|
def store(self, msgid, msg_item, value):
|
||||||
"""Mock store command."""
|
"""Mock store command."""
|
||||||
return 'OK'
|
return "OK"
|
||||||
|
|
||||||
def fetch(self, msgid, parts):
|
def fetch(self, msgid, parts):
|
||||||
"""Return RFC822 formatted message."""
|
"""Return RFC822 formatted message."""
|
||||||
return ('OK', MSG_BODY)
|
return ("OK", MSG_BODY)
|
||||||
|
|
||||||
def search(self, charset, criteria):
|
def search(self, charset, criteria):
|
||||||
"""Return some msgid's."""
|
"""Return some msgid's."""
|
||||||
return ('OK', ['123 456'])
|
return ("OK", ["123 456"])
|
||||||
|
|
||||||
|
|
||||||
class TestMatchAlgorithms(TransactionCase):
|
class TestMatchAlgorithms(TransactionCase):
|
||||||
|
|
||||||
def _get_base_folder(self):
|
def _get_base_folder(self):
|
||||||
server_model = self.env['fetchmail.server']
|
server_model = self.env["fetchmail.server"]
|
||||||
folder_model = self.env['fetchmail.server.folder']
|
folder_model = self.env["fetchmail.server.folder"]
|
||||||
folder = folder_model.browse([models.NewId()])
|
folder = folder_model.browse([models.NewId()])
|
||||||
folder.model_id = self.env.ref('base.model_res_partner').id
|
folder.model_id = self.env.ref("base.model_res_partner").id
|
||||||
folder.model_field = 'email'
|
folder.model_field = "email"
|
||||||
folder.match_algorithm = 'EmailExact'
|
folder.match_algorithm = "EmailExact"
|
||||||
folder.mail_field = 'to,from'
|
folder.mail_field = "to,from"
|
||||||
folder.server_id = server_model.browse([models.NewId()])
|
folder.server_id = server_model.browse([models.NewId()])
|
||||||
return folder
|
return folder
|
||||||
|
|
||||||
def do_matching(
|
def do_matching(
|
||||||
self, match_algorithm, expected_xmlid, folder, mail_message,
|
self,
|
||||||
mail_message_org=None):
|
match_algorithm,
|
||||||
|
expected_xmlid,
|
||||||
|
folder,
|
||||||
|
mail_message,
|
||||||
|
mail_message_org=None,
|
||||||
|
):
|
||||||
matcher = match_algorithm()
|
matcher = match_algorithm()
|
||||||
matches = matcher.search_matches(folder, mail_message)
|
matches = matcher.search_matches(folder, mail_message)
|
||||||
self.assertEqual(len(matches), 1)
|
self.assertEqual(len(matches), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(matches[0], self.env.ref(expected_xmlid))
|
||||||
matches[0], self.env.ref(expected_xmlid))
|
|
||||||
connection = MockConnection()
|
connection = MockConnection()
|
||||||
matcher.handle_match(
|
matcher.handle_match(
|
||||||
connection, matches[0], folder, mail_message, mail_message_org,
|
connection, matches[0], folder, mail_message, mail_message_org, None
|
||||||
None)
|
)
|
||||||
|
|
||||||
def test_email_exact(self):
|
def test_email_exact(self):
|
||||||
mail_message = {
|
mail_message = {
|
||||||
'subject': 'Testsubject',
|
"subject": "Testsubject",
|
||||||
'to': 'demo@yourcompany.example.com',
|
"to": "demo@yourcompany.example.com",
|
||||||
'from': 'someone@else.com',
|
"from": "someone@else.com",
|
||||||
}
|
}
|
||||||
folder = self._get_base_folder()
|
folder = self._get_base_folder()
|
||||||
folder.match_algorithm = 'EmailExact'
|
folder.match_algorithm = "EmailExact"
|
||||||
self.do_matching(
|
self.do_matching(
|
||||||
email_exact.EmailExact, 'base.user_demo_res_partner',
|
email_exact.EmailExact, "base.user_demo_res_partner", folder, mail_message
|
||||||
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"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_email_domain(self):
|
def test_email_domain(self):
|
||||||
mail_message = {
|
mail_message = {
|
||||||
'subject': 'Testsubject',
|
"subject": "Testsubject",
|
||||||
'to': 'test@seagate.com',
|
"to": "test@seagate.com",
|
||||||
'from': 'someone@else.com',
|
"from": "someone@else.com",
|
||||||
'attachments': [('hello.txt', 'Hello World!')]}
|
"attachments": [("hello.txt", "Hello World!")],
|
||||||
|
}
|
||||||
folder = self._get_base_folder()
|
folder = self._get_base_folder()
|
||||||
folder.match_algorithm = 'EmailDomain'
|
folder.match_algorithm = "EmailDomain"
|
||||||
folder.use_first_match = True
|
folder.use_first_match = True
|
||||||
self.do_matching(
|
self.do_matching(
|
||||||
email_domain.EmailDomain, 'base.res_partner_address_31',
|
email_domain.EmailDomain,
|
||||||
folder, mail_message)
|
"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_odoo_standard(self):
|
def test_odoo_standard(self):
|
||||||
mail_message_org = (
|
mail_message_org = (
|
||||||
|
@ -113,20 +123,19 @@ class TestMatchAlgorithms(TransactionCase):
|
||||||
"Hello world"
|
"Hello world"
|
||||||
)
|
)
|
||||||
folder = self._get_base_folder()
|
folder = self._get_base_folder()
|
||||||
folder.match_algorithm = 'OdooStandard'
|
folder.match_algorithm = "OdooStandard"
|
||||||
matcher = odoo_standard.OdooStandard()
|
matcher = odoo_standard.OdooStandard()
|
||||||
matches = matcher.search_matches(folder, None)
|
matches = matcher.search_matches(folder, None)
|
||||||
self.assertEqual(len(matches), 1)
|
self.assertEqual(len(matches), 1)
|
||||||
matcher.handle_match(
|
matcher.handle_match(None, matches[0], folder, None, mail_message_org, 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):
|
def test_apply_matching_exact(self):
|
||||||
folder = self._get_base_folder()
|
folder = self._get_base_folder()
|
||||||
folder.match_algorithm = 'EmailExact'
|
folder.match_algorithm = "EmailExact"
|
||||||
connection = MockConnection()
|
connection = MockConnection()
|
||||||
msgid = "<485a8041-d560-a981-5afc-d31c1f136748@acme.com>"
|
msgid = "<485a8041-d560-a981-5afc-d31c1f136748@acme.com>"
|
||||||
matcher = email_exact.EmailExact()
|
matcher = email_exact.EmailExact()
|
||||||
|
@ -134,15 +143,15 @@ class TestMatchAlgorithms(TransactionCase):
|
||||||
|
|
||||||
def test_retrieve_imap_folder_domain(self):
|
def test_retrieve_imap_folder_domain(self):
|
||||||
folder = self._get_base_folder()
|
folder = self._get_base_folder()
|
||||||
folder.match_algorithm = 'EmailDomain'
|
folder.match_algorithm = "EmailDomain"
|
||||||
connection = MockConnection()
|
connection = MockConnection()
|
||||||
folder.retrieve_imap_folder(connection)
|
folder.retrieve_imap_folder(connection)
|
||||||
|
|
||||||
def test_field_view_get(self):
|
def test_field_view_get(self):
|
||||||
"""For the moment just check execution withouth errors."""
|
"""For the moment just check execution withouth errors."""
|
||||||
server_model = self.env['fetchmail.server']
|
server_model = self.env["fetchmail.server"]
|
||||||
view = server_model.fields_view_get()
|
view = server_model.fields_view_get()
|
||||||
self.assertTrue(view)
|
self.assertTrue(view)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'match_algorithm',
|
"match_algorithm", view["fields"]["folder_ids"]["views"]["form"]["arch"]
|
||||||
view['fields']['folder_ids']['views']['form']['arch'])
|
)
|
||||||
|
|
|
@ -8,21 +8,19 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="object_id" position="attributes">
|
<field name="object_id" position="attributes">
|
||||||
<attribute
|
<attribute
|
||||||
name="attrs">{'required': [('type', '!=', 'imap')]}</attribute>
|
name="attrs"
|
||||||
|
>{'required': [('type', '!=', 'imap')]}</attribute>
|
||||||
</field>
|
</field>
|
||||||
<field name="type" position="after">
|
<field name="type" position="after">
|
||||||
<field name="folders_only" />
|
<field name="folders_only" />
|
||||||
</field>
|
</field>
|
||||||
<xpath
|
<xpath expr="//notebook" position="inside">
|
||||||
expr="//notebook"
|
|
||||||
position="inside">
|
|
||||||
<page
|
<page
|
||||||
string="Folders to monitor"
|
string="Folders to monitor"
|
||||||
attrs="{'invisible': [('type','!=','imap')]}">
|
attrs="{'invisible': [('type','!=','imap')]}"
|
||||||
|
>
|
||||||
<group>
|
<group>
|
||||||
<field
|
<field name="folder_ids" nolabel="1">
|
||||||
name="folder_ids"
|
|
||||||
nolabel="1">
|
|
||||||
<tree colors="gray:active==False">
|
<tree colors="gray:active==False">
|
||||||
<field name="active" invisible="True" />
|
<field name="active" invisible="True" />
|
||||||
<field name="sequence" widget="handle" />
|
<field name="sequence" widget="handle" />
|
||||||
|
@ -40,36 +38,40 @@
|
||||||
type="object"
|
type="object"
|
||||||
name="button_confirm_folder"
|
name="button_confirm_folder"
|
||||||
string="Test & Confirm"
|
string="Test & Confirm"
|
||||||
states="draft"/>
|
states="draft"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
type="object"
|
type="object"
|
||||||
name="button_attach_mail_manually"
|
name="button_attach_mail_manually"
|
||||||
string="Attach mail manually"
|
string="Attach mail manually"
|
||||||
states="done"/>
|
states="done"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
type="object"
|
type="object"
|
||||||
name="fetch_mail"
|
name="fetch_mail"
|
||||||
string="Fetch folder now"
|
string="Fetch folder now"
|
||||||
states="done"/>
|
states="done"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
string="Reset Confirmation"
|
string="Reset Confirmation"
|
||||||
type="object"
|
type="object"
|
||||||
name="set_draft"
|
name="set_draft"
|
||||||
states="done"/>
|
states="done"
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field
|
<field
|
||||||
name="path"
|
name="path"
|
||||||
placeholder="INBOX.subfolder1" />
|
placeholder="INBOX.subfolder1"
|
||||||
|
/>
|
||||||
<field name="model_id" />
|
<field name="model_id" />
|
||||||
<field
|
<field name="model_field" placeholder="email" />
|
||||||
name="model_field"
|
|
||||||
placeholder="email" />
|
|
||||||
<field name="match_algorithm" />
|
<field name="match_algorithm" />
|
||||||
<field
|
<field
|
||||||
name="mail_field"
|
name="mail_field"
|
||||||
placeholder="to,from" />
|
placeholder="to,from"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="active" />
|
<field name="active" />
|
||||||
|
@ -80,18 +82,18 @@
|
||||||
<field
|
<field
|
||||||
name="model_order"
|
name="model_order"
|
||||||
attrs="{'readonly': [('match_first','==',False)], 'required': [('match_first','==',True)]}"
|
attrs="{'readonly': [('match_first','==',False)], 'required': [('match_first','==',True)]}"
|
||||||
placeholder="name asc,type desc" />
|
placeholder="name asc,type desc"
|
||||||
|
/>
|
||||||
<field
|
<field
|
||||||
name="domain"
|
name="domain"
|
||||||
placeholder="[('state', '=', 'open')]" />
|
placeholder="[('state', '=', 'open')]"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
<separator
|
<separator string="Folders available on server" colspan="6" />
|
||||||
string="Folders available on server"
|
|
||||||
colspan="6" />
|
|
||||||
<field name="folders_available" />
|
<field name="folders_available" />
|
||||||
</page>
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright - 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
from . import attach_mail_manually
|
from . import attach_mail_manually
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2013-2018 Therp BV <https://therp.nl>.
|
# Copyright 2013-2018 Therp BV <https://therp.nl>.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AttachMailManually(models.TransientModel):
|
class AttachMailManually(models.TransientModel):
|
||||||
_name = 'fetchmail.attach.mail.manually'
|
_name = "fetchmail.attach.mail.manually"
|
||||||
|
|
||||||
name = fields.Char()
|
name = fields.Char()
|
||||||
folder_id = fields.Many2one(
|
folder_id = fields.Many2one("fetchmail.server.folder", "Folder", readonly=True)
|
||||||
'fetchmail.server.folder', 'Folder', readonly=True)
|
|
||||||
mail_ids = fields.One2many(
|
mail_ids = fields.One2many(
|
||||||
'fetchmail.attach.mail.manually.mail', 'wizard_id', 'Emails')
|
"fetchmail.attach.mail.manually.mail", "wizard_id", "Emails"
|
||||||
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _prepare_mail(self, folder, msgid, mail_message):
|
def _prepare_mail(self, folder, msgid, mail_message):
|
||||||
return {
|
return {
|
||||||
'msgid': msgid,
|
"msgid": msgid,
|
||||||
'subject': mail_message.get('subject', ''),
|
"subject": mail_message.get("subject", ""),
|
||||||
'date': mail_message.get('date', ''),
|
"date": mail_message.get("date", ""),
|
||||||
'body': mail_message.get('body', ''),
|
"body": mail_message.get("body", ""),
|
||||||
'email_from': mail_message.get('from', ''),
|
"email_from": mail_message.get("from", ""),
|
||||||
'object_id': '%s,-1' % folder.model_id.model}
|
"object_id": "%s,-1" % folder.model_id.model,
|
||||||
|
}
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def default_get(self, fields_list):
|
def default_get(self, fields_list):
|
||||||
defaults = super(AttachMailManually, self).default_get(fields_list)
|
defaults = super(AttachMailManually, self).default_get(fields_list)
|
||||||
if not fields_list or 'name' in fields_list:
|
if not fields_list or "name" in fields_list:
|
||||||
defaults['name'] = _('Attach emails manually')
|
defaults["name"] = _("Attach emails manually")
|
||||||
defaults['mail_ids'] = []
|
defaults["mail_ids"] = []
|
||||||
folder_model = self.env['fetchmail.server.folder']
|
folder_model = self.env["fetchmail.server.folder"]
|
||||||
folder_id = self.env.context.get('folder_id')
|
folder_id = self.env.context.get("folder_id")
|
||||||
defaults['folder_id'] = folder_id
|
defaults["folder_id"] = folder_id
|
||||||
folder = folder_model.browse([folder_id])
|
folder = folder_model.browse([folder_id])
|
||||||
connection = folder.server_id.connect()
|
connection = folder.server_id.connect()
|
||||||
connection.select(folder.path)
|
connection.select(folder.path)
|
||||||
criteria = 'FLAGGED' if folder.flag_nonmatching else 'UNDELETED'
|
criteria = "FLAGGED" if folder.flag_nonmatching else "UNDELETED"
|
||||||
msgids = folder.get_msgids(connection, criteria)
|
msgids = folder.get_msgids(connection, criteria)
|
||||||
for msgid in msgids[0].split():
|
for msgid in msgids[0].split():
|
||||||
mail_message, message_org = folder.fetch_msg(connection, msgid)
|
mail_message, message_org = folder.fetch_msg(connection, msgid)
|
||||||
defaults['mail_ids'].append(
|
defaults["mail_ids"].append(
|
||||||
(0, 0, self._prepare_mail(folder, msgid, mail_message)))
|
(0, 0, self._prepare_mail(folder, msgid, mail_message))
|
||||||
|
)
|
||||||
connection.close()
|
connection.close()
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
|
@ -63,41 +63,40 @@ class AttachMailManually(models.TransientModel):
|
||||||
mail_message, message_org = folder.fetch_msg(connection, msgid)
|
mail_message, message_org = folder.fetch_msg(connection, msgid)
|
||||||
folder.attach_mail(mail.object_id, mail_message)
|
folder.attach_mail(mail.object_id, mail_message)
|
||||||
folder.update_msg(
|
folder.update_msg(
|
||||||
connection, msgid, matched=True,
|
connection, msgid, matched=True, flagged=folder.flag_nonmatching
|
||||||
flagged=folder.flag_nonmatching)
|
)
|
||||||
connection.close()
|
connection.close()
|
||||||
return {'type': 'ir.actions.act_window_close'}
|
return {"type": "ir.actions.act_window_close"}
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def fields_view_get(
|
def fields_view_get(
|
||||||
self, view_id=None, view_type='form',
|
self, view_id=None, view_type="form", toolbar=False, submenu=False
|
||||||
toolbar=False, submenu=False):
|
):
|
||||||
result = super(AttachMailManually, self).fields_view_get(
|
result = super(AttachMailManually, self).fields_view_get(
|
||||||
view_id=view_id, view_type=view_type, toolbar=toolbar,
|
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
|
||||||
submenu=submenu)
|
)
|
||||||
if view_type != 'form':
|
if view_type != "form":
|
||||||
return result
|
return result
|
||||||
folder_model = self.env['fetchmail.server.folder']
|
folder_model = self.env["fetchmail.server.folder"]
|
||||||
folder_id = self.env.context.get('folder_id')
|
folder_id = self.env.context.get("folder_id")
|
||||||
folder = folder_model.browse([folder_id])
|
folder = folder_model.browse([folder_id])
|
||||||
form = result['fields']['mail_ids']['views']['form']
|
form = result["fields"]["mail_ids"]["views"]["form"]
|
||||||
form['fields']['object_id']['selection'] = [
|
form["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 AttachMailManuallyMail(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("fetchmail.attach.mail.manually", readonly=True)
|
||||||
'fetchmail.attach.mail.manually', readonly=True)
|
msgid = fields.Char("Message id", readonly=True)
|
||||||
msgid = fields.Char('Message id', readonly=True)
|
subject = fields.Char("Subject", readonly=True)
|
||||||
subject = fields.Char('Subject', readonly=True)
|
date = fields.Datetime("Date", readonly=True)
|
||||||
date = fields.Datetime('Date', readonly=True)
|
email_from = fields.Char("From", readonly=True)
|
||||||
email_from = fields.Char('From', readonly=True)
|
body = fields.Html("Body", readonly=True)
|
||||||
body = fields.Html('Body', readonly=True)
|
|
||||||
object_id = fields.Reference(
|
object_id = fields.Reference(
|
||||||
lambda self: [
|
lambda self: [(m.model, m.name) for m in self.env["ir.model"].search([])],
|
||||||
(m.model, m.name)
|
string="Object",
|
||||||
for m in self.env['ir.model'].search([])],
|
)
|
||||||
string='Object')
|
|
||||||
|
|
|
@ -31,12 +31,10 @@
|
||||||
string="Save"
|
string="Save"
|
||||||
type="object"
|
type="object"
|
||||||
name="attach_mails"
|
name="attach_mails"
|
||||||
class="oe_highlight" />
|
class="oe_highlight"
|
||||||
|
/>
|
||||||
or
|
or
|
||||||
<button
|
<button special="cancel" string="Cancel" class="oe_link" />
|
||||||
special="cancel"
|
|
||||||
string="Cancel"
|
|
||||||
class="oe_link" />
|
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
Loading…
Reference in New Issue