OCA/server-tools

View on GitHub
fetchmail_attach_from_folder/model/fetchmail_server.py

Summary

Maintainability
D
1 day
Test Coverage
# -*- encoding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    This module copyright (C) 2013 Therp BV (<http://therp.nl>)
#    All Rights Reserved
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
import base64
import simplejson
from lxml import etree
from openerp import models, fields, api, exceptions
from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)


class fetchmail_server(models.Model):
    _inherit = 'fetchmail.server'

    folder_ids = fields.One2many(
        'fetchmail.server.folder', 'server_id', 'Folders',
        context={'active_test': False})
    object_id = fields.Many2one(required=False)

    _defaults = {
        'type': 'imap',
    }

    def onchange_server_type(
            self, cr, uid, ids, server_type=False, ssl=False,
            object_id=False):
        retval = super(
            fetchmail_server, self).onchange_server_type(cr, uid,
                                                         ids, server_type, ssl,
                                                         object_id)
        retval['value']['state'] = 'draft'
        return retval

    def fetch_mail(self, cr, uid, ids, context=None):
        if context is None:
            context = {}

        check_original = []

        for this in self.browse(cr, uid, ids, context):
            if this.object_id:
                check_original.append(this.id)
            if not this.folder_ids.filtered('active'):
                continue

            context.update(
                {
                    'fetchmail_server_id': this.id,
                    'server_type': this.type
                })

            connection = this.connect()
            for folder in this.folder_ids.filtered('active'):
                this.with_context(safe_eval(folder.context or '{}'))\
                    .handle_folder(connection, folder)
            connection.close()

        return super(fetchmail_server, self).fetch_mail(
            cr, uid, check_original, context)

    @api.multi
    def handle_folder(self, connection, folder):
        '''Return ids of objects matched'''

        matched_object_ids = []

        for this in self:
            _logger.info(
                'start checking for emails in %s server %s',
                folder.path, this.name)

            match_algorithm = folder.get_algorithm()

            if connection.select(folder.path)[0] != 'OK':
                _logger.error(
                    'Could not open mailbox %s on %s',
                    folder.path, this.server)
                connection.select()
                continue
            result, msgids = this.get_msgids(connection)
            if result != 'OK':
                _logger.error(
                    'Could not search mailbox %s on %s',
                    folder.path, this.server)
                continue

            for msgid in msgids[0].split():
                matched_object_ids += this.apply_matching(
                    connection, folder, msgid, match_algorithm)

            _logger.info(
                'finished checking for emails in %s server %s',
                folder.path, this.name)

        return matched_object_ids

    @api.multi
    def get_msgids(self, connection):
        '''Return imap ids of messages to process'''
        return connection.search(None, 'UNDELETED')

    @api.multi
    def apply_matching(self, connection, folder, msgid, match_algorithm):
        '''Return ids of objects matched'''

        matched_object_ids = []

        for this in self:
            result, msgdata = connection.fetch(msgid, '(RFC822)')

            if result != 'OK':
                _logger.error(
                    'Could not fetch %s in %s on %s',
                    msgid, folder.path, this.server)
                continue

            mail_message = self.env['mail.thread'].message_parse(
                msgdata[0][1], save_original=this.original)

            if self.env['mail.message'].search(
                    [('message_id', '=', mail_message['message_id'])]):
                continue

            found_ids = match_algorithm.search_matches(
                self.env.cr, self.env.uid, folder, mail_message, msgdata[0][1])

            if found_ids and (len(found_ids) == 1 or
                              folder.match_first):
                try:
                    self.env.cr.execute('savepoint apply_matching')
                    match_algorithm.handle_match(
                        self.env.cr, self.env.uid, connection,
                        found_ids[0], folder, mail_message,
                        msgdata[0][1], msgid, self.env.context)
                    self.env.cr.execute('release savepoint apply_matching')
                    matched_object_ids += found_ids[:1]
                except Exception:
                    self.env.cr.execute('rollback to savepoint apply_matching')
                    _logger.exception(
                        "Failed to fetch mail %s from %s", msgid, this.name)
            elif folder.flag_nonmatching:
                connection.store(msgid, '+FLAGS', '\\FLAGGED')

        return matched_object_ids

    @api.multi
    def attach_mail(self, connection, object_id, folder, mail_message, msgid):
        '''Return ids of messages created'''

        mail_message_ids = []

        for this in self:
            partner_id = None
            if folder.model_id.model == 'res.partner':
                partner_id = object_id
            if 'partner_id' in self.env[folder.model_id.model]._columns:
                partner_id = self.env[folder.model_id.model].browse(object_id)\
                    .partner_id.id

            attachments = []
            if this.attach and mail_message.get('attachments'):
                for attachment in mail_message['attachments']:
                    fname, fcontent = attachment
                    if isinstance(fcontent, unicode):
                        fcontent = fcontent.encode('utf-8')
                    data_attach = {
                        'name': fname,
                        'datas': base64.b64encode(str(fcontent)),
                        'datas_fname': fname,
                        'description': _('Mail attachment'),
                        'res_model': folder.model_id.model,
                        'res_id': object_id,
                    }
                    attachments.append(
                        self.env['ir.attachment'].create(data_attach))

            mail_message_ids.append(
                self.env['mail.message'].create({
                    'author_id': partner_id,
                    'model': folder.model_id.model,
                    'res_id': object_id,
                    'type': 'email',
                    'body': mail_message.get('body'),
                    'subject': mail_message.get('subject'),
                    'email_from': mail_message.get('from'),
                    'date': mail_message.get('date'),
                    'message_id': mail_message.get('message_id'),
                    'attachment_ids': [(6, 0, [a.id for a in attachments])],
                }))

            if folder.delete_matching:
                connection.store(msgid, '+FLAGS', '\\DELETED')
        return mail_message_ids

    def button_confirm_login(self, cr, uid, ids, context=None):
        retval = super(fetchmail_server, self).button_confirm_login(
            cr, uid, ids, context)

        for this in self.browse(cr, uid, ids, context):
            this.write({'state': 'draft'})
            connection = this.connect()
            connection.select()
            for folder in this.folder_ids.filtered('active'):
                try:
                    folder_context = safe_eval(folder.context or '{}')
                except Exception, e:
                    raise exceptions.ValidationError(
                        _('Invalid context "%s": %s') % (folder.context, e))
                if not isinstance(folder_context, dict):
                    raise exceptions.ValidationError(
                        _('Context "%s" is not a dictionary.') %
                        folder.context)
                if connection.select(folder.path)[0] != 'OK':
                    raise exceptions.ValidationError(
                        _('Mailbox %s not found!') % folder.path)
            connection.close()
            this.write({'state': 'done'})

        return retval

    def fields_view_get(self, cr, user, view_id=None, view_type='form',
                        context=None, toolbar=False, submenu=False):
        result = super(fetchmail_server, self).fields_view_get(
            cr, user, view_id, view_type, context, toolbar, submenu)

        if view_type == 'form':
            view = etree.fromstring(
                result['fields']['folder_ids']['views']['form']['arch'])
            modifiers = {}
            docstr = ''
            for algorithm in self.pool['fetchmail.server.folder']\
                    ._get_match_algorithms().itervalues():
                for modifier in ['required', 'readonly']:
                    for field in getattr(algorithm, modifier + '_fields'):
                        modifiers.setdefault(field, {})
                        modifiers[field].setdefault(modifier, [])
                        if modifiers[field][modifier]:
                            modifiers[field][modifier].insert(0, '|')
                        modifiers[field][modifier].append(
                            ("match_algorithm", "==", algorithm.__name__))
                docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
                    '\n\n'

            for field in view.xpath('//field'):
                if field.tag == 'field' and field.get('name') in modifiers:
                    field.set(
                        'modifiers',
                        simplejson.dumps(
                            dict(
                                simplejson.loads(field.attrib['modifiers']),
                                **modifiers[field.attrib['name']]
                            )
                        ),
                    )
                if (field.tag == 'field' and
                        field.get('name') == 'match_algorithm'):
                    field.set('help', docstr)
            result['fields']['folder_ids']['views']['form']['arch'] = \
                etree.tostring(view)

        return result