[IMP] fetchmail_attach_from_folder: pre-commit stuff

pull/3071/head
Ronald Portier 2024-01-08 11:13:15 +01:00 committed by Jose Zambudio
parent 95ae813886
commit be09001e33
16 changed files with 408 additions and 378 deletions

View File

@ -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

View File

@ -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,
} }

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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),
)

View File

@ -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

View File

@ -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

View File

@ -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])],
}
)

View File

@ -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

View File

@ -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']) )

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record model="ir.ui.view" id="view_email_server_form"> <record model="ir.ui.view" id="view_email_server_form">
@ -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 &amp; Confirm" string="Test &amp; 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>

View File

@ -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

View File

@ -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')

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record model="ir.ui.view" id="view_attach_mail_manually"> <record model="ir.ui.view" id="view_attach_mail_manually">
@ -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>