Olivier Dony (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-server/emails-framework into lp:openobject-server.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/emails-framework/+merge/68500


-- 
https://code.launchpad.net/~openerp-dev/openobject-server/emails-framework/+merge/68500
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-server/emails-framework.
=== modified file 'openerp/addons/base/base_data.xml'
--- openerp/addons/base/base_data.xml	2011-05-25 07:48:40 +0000
+++ openerp/addons/base/base_data.xml	2011-07-20 08:33:46 +0000
@@ -1603,7 +1603,14 @@
            <field name="rate">691.3153</field>
            <field name="currency_id" ref="CRC"/>
            <field eval="time.strftime('%Y-01-01')" name="name"/>
-       </record>
+        </record>
+
+        <record id="ir_mail_server_localhost0" model="ir.mail_server">
+            <field name="name">localhost</field>
+            <field name="smtp_host">localhost</field>
+            <field eval="25" name="smtp_port"/>
+            <field eval="10" name="priority"/>
+	    </record>
 
         <record id="MUR" model="res.currency">
             <field name="name">MUR</field>

=== modified file 'openerp/addons/base/ir/__init__.py'
--- openerp/addons/base/ir/__init__.py	2010-11-19 08:17:21 +0000
+++ openerp/addons/base/ir/__init__.py	2011-07-20 08:33:46 +0000
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
-#    
+#
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
@@ -15,7 +15,7 @@
 #    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/>.     
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
 
@@ -36,6 +36,7 @@
 import wizard
 import ir_config_parameter
 import osv_memory_autovacuum
+import ir_mail_server
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 

=== modified file 'openerp/addons/base/ir/ir.xml'
--- openerp/addons/base/ir/ir.xml	2011-06-30 14:05:13 +0000
+++ openerp/addons/base/ir/ir.xml	2011-07-20 08:33:46 +0000
@@ -2003,5 +2003,76 @@
             <field name="sequence">5</field>
         </record>         
 
+        <!-- ir.mail.server -->
+        <record model="ir.ui.view" id="ir_mail_server_form">
+            <field name="name">ir.mail.server.form</field>
+            <field name="model">ir.mail_server</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Outgoing Mail Servers">
+                    <group colspan="4">
+                        <field name="name"/>
+                        <field name="sequence"/>
+                    </group>
+                    <notebook colspan="4">
+                        <page string="Configuration">
+                            <group col="4" colspan="2">
+                                <separator string="Connection Information" colspan="4"/>
+                                <field name="smtp_host"/>
+                                <field name="smtp_port"/>
+                                <field name="smtp_encryption" on_change="on_change_encryption(smtp_encryption)"/>
+                                <field name="smtp_debug"/>
+                             </group>
+                             <group col="2" colspan="2">
+                                <separator string="Authentication Information" colspan="2"/>
+                                <field name="smtp_user"/>
+                                <field name="smtp_pass" password="True"/>
+                                <button name="test_smtp_connection" type="object" string="Test Connection" icon="gtk-network" colspan="2"/>
+                            </group>
+                        </page>
+                    </notebook>
+                </form>
+            </field>
+        </record>
+
+        <record model="ir.ui.view" id="ir_mail_server_list">
+            <field name="name">ir.mail.server.list</field>
+            <field name="model">ir.mail_server</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
+                <tree string="Outgoing Mail Servers">
+                    <field name="sequence"/>
+                    <field name="name"/>
+                    <field name="smtp_host"/>
+                    <field name="smtp_user"/>
+                    <field name="smtp_encryption"/>
+                </tree>
+            </field>
+        </record>
+
+        <record id="view_ir_mail_server_search" model="ir.ui.view">
+            <field name="name">ir.mail.server.search</field>
+            <field name="model">ir.mail_server</field>
+            <field name="type">search</field>
+            <field name="arch" type="xml">
+                <search string="Outgoing Mail Servers">
+                    <field name="name"/>
+                    <field name="smtp_host"/>
+                    <field name="smtp_user"/>
+                    <field name="smtp_encryption"/>
+                </search>
+            </field>
+        </record>
+
+        <record model="ir.actions.act_window" id="action_ir_mail_server_list">
+            <field name="name">Outgoing Mail Servers</field>
+            <field name="res_model">ir.mail_server</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">tree,form</field>
+            <field name="view_id" ref="ir_mail_server_list" />
+            <field name="search_view_id" ref="view_ir_mail_server_search"/>
+        </record>
+        <menuitem id="next_id_15" name="Parameters" parent="base.menu_config" groups="base.group_extended" />
+        <menuitem id="menu_mail_servers" parent="base.next_id_15" action="action_ir_mail_server_list" sequence="15"/>
     </data>
 </openerp>

=== modified file 'openerp/addons/base/ir/ir_actions.py'
--- openerp/addons/base/ir/ir_actions.py	2011-07-06 15:40:01 +0000
+++ openerp/addons/base/ir/ir_actions.py	2011-07-20 08:33:46 +0000
@@ -162,7 +162,7 @@
 
     def _invalid_model_msg(self, cr, uid, ids, context=None):
         return _('Invalid model name in the action definition.')
-    
+
     _constraints = [
         (_check_model, _invalid_model_msg, ['res_model','src_model'])
     ]
@@ -208,7 +208,7 @@
             if act.search_view_id:
                 search_view_id = act.search_view_id.id
             else:
-                res_view = self.pool.get('ir.ui.view').search(cr, uid, 
+                res_view = self.pool.get('ir.ui.view').search(cr, uid,
                         [('model','=',act.res_model),('type','=','search'),
                         ('inherit_id','=',False)], context=context)
                 if res_view:
@@ -652,7 +652,10 @@
                 subject = self.merge_message(cr, uid, action.subject, action, context)
                 body = self.merge_message(cr, uid, action.message, action, context)
 
-                if tools.email_send(user, [address], subject, body, debug=False, subtype='html') == True:
+                smtp_server_pool = self.pool.get('ir.mail_server')
+                msg = smtp_server_pool.pack_message(cr, uid, user, [address], subject, body, subtype='html')
+                res_email = smtp_server_pool.send_email(cr, uid, msg, debug=False)
+                if res_email:
                     logger.info('Email successfully sent to: %s', address)
                 else:
                     logger.warning('Failed to send email to: %s', address)

=== modified file 'openerp/addons/base/ir/ir_config_parameter.py'
--- openerp/addons/base/ir/ir_config_parameter.py	2011-07-12 15:20:27 +0000
+++ openerp/addons/base/ir/ir_config_parameter.py	2011-07-20 08:33:46 +0000
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2011 OpenERP SA (<http://www.openerp.com>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -19,7 +19,7 @@
 #
 ##############################################################################
 """
-A module to store some configuration parameters relative to a whole database.
+Store database-specific configuration parameters
 """
 
 from osv import osv,fields
@@ -36,23 +36,19 @@
 }
 
 class ir_config_parameter(osv.osv):
-    """ An osv to old configuration parameters for a given database.
-    
-    To be short, it's just a global dictionary of strings stored in a table. """
-    
+    """Per-database storage of configuration key-value pairs."""
+
     _name = 'ir.config_parameter'
-    
+
     _columns = {
-        # The key of the configuration parameter.
         'key': fields.char('Key', size=256, required=True, select=1),
-        # The value of the configuration parameter.
         'value': fields.text('Value', required=True),
     }
-    
+
     _sql_constraints = [
         ('key_uniq', 'unique (key)', 'Key must be unique.')
     ]
-    
+
     def init(self, cr):
         """
         Initializes the parameters listed in _default_parameters.
@@ -63,12 +59,12 @@
                 self.set_param(cr, 1, key, func())
 
     def get_param(self, cr, uid, key, default=False, context=None):
-        """ Get the value of a parameter.
-        
-        @param key: The key of the parameter.
-        @type key: string
-        @return: The value of the parameter, False if it does not exist.
-        @rtype: string
+        """Retrieve the value for a given key.
+
+        :param string key: The key of the parameter value to retrieve.
+        :param string default: default value if parameter is missing.
+        :return: The value of the parameter, or ``default`` if it does not exist.
+        :rtype: string
         """
         ids = self.search(cr, uid, [('key','=',key)], context=context)
         if not ids:
@@ -78,15 +74,13 @@
         return value
     
     def set_param(self, cr, uid, key, value, context=None):
-        """ Set the value of a parameter.
+        """Sets the value of a parameter.
         
-        @param key: The key of the parameter.
-        @type key: string
-        @param value: The value of the parameter.
-        @type value: string
-        @return: Return the previous value of the parameter of False if it did
-        not existed.
-        @rtype: string
+        :param string key: The key of the parameter value to set.
+        :param string value: The value to set.
+        :return: the previous value of the parameter or False if it did
+                 not exist.
+        :rtype: string
         """
         ids = self.search(cr, uid, [('key','=',key)], context=context)
         if ids:
@@ -97,5 +91,3 @@
         else:
             self.create(cr, uid, {'key': key, 'value': value}, context=context)
             return False
-
-ir_config_parameter()

=== added file 'openerp/addons/base/ir/ir_mail_server.py'
--- openerp/addons/base/ir/ir_mail_server.py	1970-01-01 00:00:00 +0000
+++ openerp/addons/base/ir/ir_mail_server.py	2011-07-20 08:33:46 +0000
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2011 OpenERP S.A (<http://www.openerp.com>)
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+from email.MIMEText import MIMEText
+from email.MIMEBase import MIMEBase
+from email.MIMEMultipart import MIMEMultipart
+from email.Header import Header
+from email.Utils import formatdate, make_msgid, COMMASPACE
+from email import Encoders
+import logging
+import smtplib
+
+from osv import osv
+from osv import fields
+from tools.translate import _
+import tools
+
+# ustr was originally from tools.misc.
+# it is moved to loglevels until we refactor tools.
+from openerp.loglevels import ustr
+
+_logger = logging.getLogger('ir.mail_server')
+
+class MailDeliveryException(osv.except_osv):
+    """Specific exception subclass for mail delivery errors"""
+    def __init__(self, name, value, exc_type='warning'):
+        super(MailDeliveryException, self).__init__(name, value, exc_type=exc_type)
+
+class WriteToLogger(object):
+    """debugging helper: behave as a fd and pipe to DEBUG logger"""
+    def __init__(self, logger):
+        self.logger = logger
+
+    def write(self, s):
+        self.logger.debug(s)
+
+class ir_mail_server(osv.osv):
+    """Represents an SMTP server, able to send outgoing e-mails, with SSL and TLS capabilities."""
+    _name = "ir.mail_server"
+
+    _columns = {
+        'name': fields.char('Name', size=64, required=True, select=True),
+        'smtp_host': fields.char('Server Name', size=128, required=True, help="Hostname or IP of SMTP server"),
+        'smtp_port': fields.integer('SMTP Port', size=5, required=True, help="SMTP Port. Usually 465 for SSL, and 25 or 587 for other cases."),
+        'smtp_user': fields.char('Username', size=64, help="Optional username for SMTP authentication"),
+        'smtp_pass': fields.char('Password', size=64, help="Optional password for SMTP authentication"),
+        'smtp_encryption': fields.selection([('none','None'),
+                                             ('starttls','TLS (STARTTLS)'),
+                                             ('ssl','SSL/TLS')],
+                                            string='Connection Security',
+                                            help="Choose the connection encryption scheme:\n"
+                                                 "- None: SMTP sessions are done in cleartext.\n"
+                                                 "- TLS (STARTTLS): TLS encryption will be requested at start of cleartext SMTP session (Recommended)\n"
+                                                 "- SSL/TLS: Uses Secure SMTP over SSL tunnel, through dedicated SMTP/SSL port (default: 465)"),
+        'smtp_debug': fields.boolean('Debugging', help="If checked, the full output of SMTP sessions will "
+                                                       "be written to the server log (may include confidential info)"),
+        'sequence': fields.integer('Priority', help="When no specific mail server is requested for a mail, the highest priority one "
+                                                    "is used. Default priority is 10 (smaller number = higher priority)"),
+    }
+
+    _defaults = {
+         'smtp_port': 25,
+         'sequence': 10,
+         'smtp_encryption': 'none',
+     }
+
+    def name_get(self, cr, uid, ids, context=None):
+        return [(a["id"], "(%s)" % (a['name'])) for a in self.read(cr, uid, ids, ['name'], context=context)]
+
+    def test_smtp_connection(self, cr, uid, ids, context=None):
+        for smtp_server in self.browse(cr, uid, ids, context=context):
+            smtp = False
+            try:
+                smtp = self.connect(smtp_server.smtp_host,
+                   smtp_server.smtp_port, user_name=smtp_server.smtp_user,
+                   user_password=smtp_server.smtp_pass, encryption=smtp_server.smtp_encryption,
+                   debug=smtp_server.smtp_debug)
+            except Exception, e:
+                raise osv.except_osv(_("Connection test failed!"), _("Here is we got instead: %s") % e)
+            finally:
+                try:
+                    if smtp: smtp.quit()
+                except Exception:
+                    # ignored, just a consequence of the previous exception
+                    pass
+
+        raise osv.except_osv(_("Connection test succeeded!"), _("Everything seems properly set up!"))
+
+    def connect(self, host, port, user=None, password=None, encryption=False, debug=False):
+        """Returns a new SMTP connection to the give SMTP server, authenticated
+           with ``user`` and ``password`` if provided, and encrypted as requested
+           by the ``encryption`` parameter.
+        
+           :param host: host or IP of SMTP server to connect to
+           :param int port: SMTP port to connect to
+           :param user: optional username to authenticate with
+           :param password: optional password to authenticate with
+           :param string encryption: optional: ``'ssl'`` | ``'starttls'``
+           :param bool debug: toggle debugging of SMTP sessions (all i/o
+                              will be output in logs)
+        """
+        if encryption == 'ssl':
+            if not 'SMTP_SSL' in smtplib.__all__:
+                raise osv.except_osv(
+                             _("SMTP-over-SSL mode unavailable"),
+                             _("Your OpenERP Server does not support SMTP-over-SSL. You could use STARTTLS instead."
+                               "If SSL is needed, an upgrade to Python 2.6 on the server-side should do the trick."))
+            connection = smtplib.SMTP_SSL(host, port)
+        else:
+            connection = smtplib.SMTP(host, port)
+        connection.set_debuglevel(debug)
+        if encryption == 'starttls':
+            # starttls() will perform ehlo if needed first
+            connection.starttls()
+
+        # force load/refresh feature list
+        connection.ehlo()
+
+        if user:
+            # Attempt authentication - will raise if AUTH service not supported
+            connection.login(user, password)
+        return connection
+
+    def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
+               attachments=None, message_id=None, references=None, openobject_id=False, subtype='plain', headers=None):
+        """Constructs an email.message.Message object based on the keyword arguments passed, returns it.
+
+        :param body: email body, according to the ``subtype`` (by default, plaintext). If html subtype is used,
+                     the message will be automatically converted to plaintext and wrapped in multipart/alternative.
+        :param reply_to: optional value of Reply-To header
+        :param email_cc: optional list of string values for CC header (to be joined with commas)
+        :param email_bcc: optional list of string values for BCC header (to be joined with commas)
+        :param attachments: list of (filename,filecontent) pairs, where filecontent
+        """
+        email_from = email_from or tools.config.get('email_from')
+        assert email_from, "email_from is mandatory"
+        email_from = ustr(email_from).encode('utf-8') # force to 8-bit utf-8
+
+        if headers is None:
+            headers = {}
+
+        if not email_cc: email_cc = []
+        if not email_bcc: email_bcc = []
+        if not body: body = u''
+
+        email_body = ustr(body).encode('utf-8')
+        email_text = MIMEText(email_body or '', _subtype=subtype,_charset='utf-8')
+        msg = MIMEMultipart()
+
+        if not message_id:
+            if openobject_id:
+                message_id = tools.generate_tracking_message_id(openobject_id)
+            else:
+                message_id = make_msgid()
+        msg['Message-Id'] = message_id
+        if references:
+            msg['references'] = references
+        msg['Subject'] = Header(ustr(subject), 'utf-8')
+        msg['From'] = email_from
+        del msg['Reply-To']
+        if reply_to:
+            msg['Reply-To'] = reply_to
+        else:
+            msg['Reply-To'] = msg['From']
+        msg['To'] = COMMASPACE.join(email_to)
+        if email_cc:
+            msg['Cc'] = COMMASPACE.join(email_cc)
+        if email_bcc:
+            msg['Bcc'] = COMMASPACE.join(email_bcc)
+        msg['Date'] = formatdate(localtime=True)
+        # Custom headers may override normal headers or provide additional ones
+        for key, value in x_headers.iteritems():
+            msg['%s' % key] = str(value)
+
+        if html2text and subtype == 'html':
+            # Always provide alternative text body if possible.
+            text = tools.html2text(email_body.decode('utf-8')).encode('utf-8')
+            alternative_part = MIMEMultipart(_subtype="alternative")
+            alternative_part.attach(MIMEText(text, _charset='utf-8', _subtype='plain'))
+            alternative_part.attach(email_text)
+            msg.attach(alternative_part)
+        else:
+            msg.attach(email_text)
+
+        if attachments:
+            for (fname, fcontent) in attachments:
+                part = MIMEBase('application', "octet-stream")
+                part.set_payload(fcontent)
+                Encoders.encode_base64(part)
+                part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
+                msg.attach(part)
+        return msg
+
+    def send_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None,
+                   smtp_user=None, smtp_password=None, smtp_encryption='none', smtp_debug=False):
+        """Sends an email directly (no queuing).
+
+        No retries are done, the caller should handle MailDeliveryException in order to ensure that
+        the mail is never lost.
+
+        If the mail_server_id is provided, sends using this mail server, ignoring other smtp_* arguments.
+        If mail_server_id is None and smtp_server is None, use the default mail server (highest priority).
+        If mail_server_id is None and smtp_server is not None, use the provided smtp_* arguments.
+        If both mail_server_id and smtp_server are None, look for an 'smtp_server' value in server config,
+        and fails if not found.
+
+        :param message: the email.message.Message to send
+        :param mail_server_id: optional id of ir.mail_server to use for sending. overrides other smtp_* arguments.
+        :param smtp_server: optional hostname of SMTP server to use
+        :param smtp_encryption: one of 'none', 'starttls' or 'ssl' (see ir.mail_server fields for explanation)
+        :param smtp_port: optional SMTP port, if mail_server_id is not passed
+        :param smtp_user: optional SMTP user, if mail_server_id is not passed
+        :param smtp_password: optional SMTP password to use, if mail_server_id is not passed
+        :param smtp_debug: optional SMTP debug flag, if mail_server_id is not passed
+        :param debug: whether to turn on the SMTP level debugging, output to DEBUG log level
+        :return: the Message-ID of the message that was just sent, if successfully sent, otherwise raises
+                 MailDeliveryException and logs root cause.
+        """
+        smtp_from = message['From']
+        if not smtp_from:
+            raise ValueError("Sending an email requires either providing a sender address or having configured one")
+
+        email_to = message['To']
+        email_cc = message['Cc']
+        email_bcc = message['Bcc']
+        smtp_to_list = tools.flatten([email_to, email_cc, email_bcc])
+
+        # Get SMTP Server Details from Mail Server
+        mail_server = None
+        if mail_server_id:
+            mail_server = self.browse(cr, uid, mail_server_id)
+        elif not smtp_server:
+            mail_server_ids = self.search(cr, uid, [], order='sequence', limit=1)
+            if mail_server_ids:
+                mail_server = self.browse(cr, uid, mail_server_ids[0])
+        else:
+            # we were passed an explicit smtp_server or nothing at all
+            smtp_server = smtp_server or tools.config.get('smtp_server')
+            smtp_port = tools.config.get('smtp_port', 25) if smtp_port is None else smtp_port
+            smtp_user = smtp_user or tools.config.get('smtp_user')
+            smtp_password = smtp_password or tools.config.get('smtp_password')
+
+        if mail_server:
+            smtp_server = mail_server.smtp_host
+            smtp_user = mail_server.smtp_user
+            smtp_password = mail_server.smtp_pass
+            smtp_port = mail_server.smtp_port
+            smtp_encryption = mail_server.smtp_encryption
+            smtp_debug = smtp_debug or mail_server.smtp_debug
+
+
+        if not smtp_server:
+            raise osv.except_osv(
+                         _("Missing SMTP Server"),
+                         _("Please define at least one SMTP server, or provide the SMTP parameters explicitly."))
+
+        try:
+            message_id = message['Message-Id']
+            smtp_server = smtp_server
+
+            # Add email in Maildir if smtp_server contains maildir.
+            if smtp_server.startswith('maildir:/'):
+                from mailbox import Maildir
+                maildir_path = smtp_server[8:]
+                mdir = Maildir(maildir_path, factory=None, create = True)
+                mdir.add(message.as_string(True))
+                return message_id
+
+            try:
+                if smtp_debug:
+                    oldstderr = smtplib.stderr
+                    smtplib.stderr = WriteToLogger(_logger)
+                smtp = self.connect(smtp_server, smtp_port, smtp_user, smtp_password, smtp_encryption, smtp_debug)
+                smtp.sendmail(smtp_from, smtp_to_list, message.as_string())
+            finally:
+                if debug:
+                    smtplib.stderr = oldstderr
+                try:
+                    # Close Connection of SMTP Server
+                    smtp.quit()
+                except Exception:
+                    # ignored, just a consequence of the previous exception
+                    pass
+        except Exception, e:
+            msg = _("Mail delivery failed via SMTP server '%s'.\n%s: %s") % (smtp_server, e.__class__.__name__, e)
+            _logger.exception(msg)
+            raise MailDeliveryException(_("Mail delivery failed"), msg)
+        return message_id
+
+    def on_change_encryption(self, cr, uid, ids, smtp_encryption):
+        if smtp_encryption == 'ssl':
+            result = {'value': {'smtp_port': 465}}
+            if not 'SMTP_SSL' in smtplib.__all__:
+                result['warning'] = {'title': _('Warning'),
+                                     'message': _('Your server does not seem to support SSL, you may want to try STARTTLS instead')}
+        else:
+            result = {'value': {'smtp_port': 25}}
+        return result
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file

=== modified file 'openerp/addons/base/res/partner/wizard/partner_wizard_spam.py'
--- openerp/addons/base/res/partner/wizard/partner_wizard_spam.py	2010-12-22 12:35:09 +0000
+++ openerp/addons/base/res/partner/wizard/partner_wizard_spam.py	2011-07-20 08:33:46 +0000
@@ -62,12 +62,9 @@
                     to = '"%s" <%s>' % (name, adr.email)
     #TODO: add some tests to check for invalid email addresses
     #CHECKME: maybe we should use res.partner/email_send
-                    tools.email_send(data.email_from,
-                                     [to],
-                                     data.subject,
-                                     data.text,
-                                     subtype=type_,
-                                     openobject_id="res.partner-%s"%partner.id)
+                    smtp_server_pool = self.pool.get('ir.mail_server')
+                    msg = smtp_server_pool.pack_message(cr, uid, data.email_from, [to], data.subject, data.text, subtype=type_)
+                    smtp_server_pool.send_email(cr, uid, msg)
                     nbr += 1
             event_pool.create(cr, uid,
                     {'name': 'Email(s) sent through mass mailing',

=== modified file 'openerp/addons/base/security/ir.model.access.csv'
--- openerp/addons/base/security/ir.model.access.csv	2011-06-30 13:18:18 +0000
+++ openerp/addons/base/security/ir.model.access.csv	2011-07-20 08:33:46 +0000
@@ -125,4 +125,5 @@
 "access_res_widget_user","res.widget.user","model_res_widget",,1,0,0,0
 "access_res_log_all","res.log","model_res_log",,1,1,1,1
 "access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0
+"access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0
 "access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1

=== modified file 'openerp/modules/loading.py'
--- openerp/modules/loading.py	2011-07-12 13:33:43 +0000
+++ openerp/modules/loading.py	2011-07-20 08:33:46 +0000
@@ -359,7 +359,7 @@
             cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
             for (model, name) in cr.fetchall():
                 model_obj = pool.get(model)
-                if isinstance(model_obj, osv.osv.osv_memory):
+                if isinstance(model_obj, osv.osv.osv_memory) and not isinstance(model_obj, osv.osv.osv):
                     logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
 
             cr.execute("SELECT model from ir_model")

=== modified file 'openerp/osv/orm.py'
--- openerp/osv/orm.py	2011-07-19 12:28:02 +0000
+++ openerp/osv/orm.py	2011-07-20 08:33:46 +0000
@@ -478,6 +478,16 @@
 
     __repr__ = __str__
 
+    def refresh(self):
+        """Force refreshing this browse_record's data and all the data of the
+           records that belong to the same cache, by emptying the cache completely,
+           preserving only the record identifiers (for prefetching optimizations).
+        """
+        for model, model_cache in self._cache.iteritems():
+            # only preserve the ids of the records that were in the cache
+            cached_ids = dict([(i, {'id': i}) for i in model_cache.keys()])
+            self._cache[model].clear()
+            self._cache[model].update(cached_ids)
 
 def get_pg_type(f):
     """
@@ -771,7 +781,7 @@
                         'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
                 nattr = {}
                 for s in attributes:
-                    new = copy.copy(getattr(pool.get(parent_name), s))
+                    new = copy.copy(getattr(pool.get(parent_name), s, {}))
                     if s == '_columns':
                         # Don't _inherit custom fields.
                         for c in new.keys():

=== modified file 'openerp/tools/misc.py'
--- openerp/tools/misc.py	2011-07-14 07:49:40 +0000
+++ openerp/tools/misc.py	2011-07-20 08:33:46 +0000
@@ -45,6 +45,7 @@
 from email.MIMEMultipart import MIMEMultipart
 from email.Header import Header
 from email.Utils import formatdate, COMMASPACE
+from email import Utils
 from email import Encoders
 from itertools import islice, izip
 from lxml import etree
@@ -61,6 +62,7 @@
 import openerp.loglevels as loglevels
 from config import config
 from lru import LRU
+import openerp.pooler as pooler
 
 # get_encodings, ustr and exception_to_unicode were originally from tools.misc.
 # There are moved to loglevels until we refactor tools.
@@ -282,14 +284,6 @@
 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
 
-priorities = {
-        '1': '1 (Highest)',
-        '2': '2 (High)',
-        '3': '3 (Normal)',
-        '4': '4 (Low)',
-        '5': '5 (Lowest)',
-    }
-
 def html2plaintext(html, body_id=None, encoding='utf-8'):
     """ From an HTML text, convert the HTML to plain text.
     If @param body_id is provided then this is the tag where the
@@ -362,142 +356,43 @@
     """
     return "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
 
-def _email_send(smtp_from, smtp_to_list, message, openobject_id=None, ssl=False, debug=False):
-    """ Low-level method to send directly a Message through the configured smtp server.
-    
-        :param smtp_from: RFC-822 envelope FROM (not displayed to recipient)
-        :param smtp_to_list: RFC-822 envelope RCPT_TOs (not displayed to recipient)
-        :param message: an email.message.Message to send
-        :param debug: True if messages should be output to stderr before being sent,
-                      and smtplib.SMTP put into debug mode.
-        :return: True if the mail was delivered successfully to the smtp,
-                 else False (+ exception logged)
-    """
-    class WriteToLogger(object):
-        def __init__(self):
-            self.logger = loglevels.Logger()
-
-        def write(self, s):
-            self.logger.notifyChannel('email_send', loglevels.LOG_DEBUG, s)
-
-    if openobject_id:
-        message['Message-Id'] = generate_tracking_message_id(openobject_id)
-
-    try:
-        smtp_server = config['smtp_server']
-
-        if smtp_server.startswith('maildir:/'):
-            from mailbox import Maildir
-            maildir_path = smtp_server[8:]
-            mdir = Maildir(maildir_path,factory=None, create = True)
-            mdir.add(message.as_string(True))
-            return True
-
-        oldstderr = smtplib.stderr
-        if not ssl: ssl = config.get('smtp_ssl', False)
-        s = smtplib.SMTP()
-        try:
-            # in case of debug, the messages are printed to stderr.
-            if debug:
-                smtplib.stderr = WriteToLogger()
-
-            s.set_debuglevel(int(bool(debug)))  # 0 or 1
-            s.connect(smtp_server, config['smtp_port'])
-            if ssl:
-                s.ehlo()
-                s.starttls()
-                s.ehlo()
-
-            if config['smtp_user'] or config['smtp_password']:
-                s.login(config['smtp_user'], config['smtp_password'])
-
-            s.sendmail(smtp_from, smtp_to_list, message.as_string())
-        finally:
-            try:
-                s.quit()
-                if debug:
-                    smtplib.stderr = oldstderr
-            except Exception:
-                # ignored, just a consequence of the previous exception
-                pass
-
-    except Exception:
-        _logger.error('could not deliver email', exc_info=True)
-        return False
-
-    return True
-
-
 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
-               attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
-
-    """Send an email.
-
-    @param email_from A string used to fill the `From` header, if falsy,
-                  config['email_from'] is used instead.  Also used for
-                  the `Reply-To` header if `reply_to` is not provided
-
-    @param email_to a sequence of addresses to send the mail to.
+               attachments=None, message_id=None, references=None, openobject_id=False, debug=False, subtype='plain', headers=None,
+               smtp_server=None, smtp_port=None, ssl=False, smtp_user=None, smtp_password=None, cr=None, uid=None):
+    """Low-level function for sending an email (deprecated).
+
+    :deprecate: since OpenERP 6.1, please use ir.mail_server.send_email() instead. 
+    :param email_from: A string used to fill the `From` header, if falsy,
+                       config['email_from'] is used instead.  Also used for
+                       the `Reply-To` header if `reply_to` is not provided
+    :param email_to: a sequence of addresses to send the mail to.
     """
-    if x_headers is None:
-        x_headers = {}
-
-        
-
-    if not (email_from or config['email_from']):
-        raise ValueError("Sending an email requires either providing a sender "
-                         "address or having configured one")
-
-    if not email_from: email_from = config.get('email_from', False)
-    email_from = ustr(email_from).encode('utf-8')
-
-    if not email_cc: email_cc = []
-    if not email_bcc: email_bcc = []
-    if not body: body = u''
-
-    email_body = ustr(body).encode('utf-8')
-    email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
-
-    msg = MIMEMultipart()
-
-    msg['Subject'] = Header(ustr(subject), 'utf-8')
-    msg['From'] = email_from
-    del msg['Reply-To']
-    if reply_to:
-        msg['Reply-To'] = reply_to
-    else:
-        msg['Reply-To'] = msg['From']
-    msg['To'] = COMMASPACE.join(email_to)
-    if email_cc:
-        msg['Cc'] = COMMASPACE.join(email_cc)
-    if email_bcc:
-        msg['Bcc'] = COMMASPACE.join(email_bcc)
-    msg['Date'] = formatdate(localtime=True)
-
-    msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
-
-    # Add dynamic X Header
-    for key, value in x_headers.iteritems():
-        msg['%s' % key] = str(value)
-
-    if html2text and subtype == 'html':
-        text = html2text(email_body.decode('utf-8')).encode('utf-8')
-        alternative_part = MIMEMultipart(_subtype="alternative")
-        alternative_part.attach(MIMEText(text, _charset='utf-8', _subtype='plain'))
-        alternative_part.attach(email_text)
-        msg.attach(alternative_part)
-    else:
-        msg.attach(email_text)
-
-    if attach:
-        for (fname,fcontent) in attach:
-            part = MIMEBase('application', "octet-stream")
-            part.set_payload( fcontent )
-            Encoders.encode_base64(part)
-            part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
-            msg.attach(part)
-
-    return _email_send(email_from, flatten([email_to, email_cc, email_bcc]), msg, openobject_id=openobject_id, ssl=ssl, debug=debug)
+
+    # If not cr, get cr from current thread database
+    if not cr:
+        db_name = getattr(threading.currentThread(), 'dbname', None)
+        if db_name:
+            cr = pooler.get_db_only(db_name).cursor()
+        else:
+            raise Exception("No database cursor found, please pass one explicitly")
+
+    # Send Email
+    try:
+        mail_server_pool = pooler.get_pool(cr.dbname).get('ir.mail_server')
+        res = False
+        # Pack Message into MIME Object
+        email_msg = mail_server_pool.build_email(email_from, email_to, subject, body, email_cc, email_bcc, reply_to,
+                   attachments, message_id, references, openobject_id, subtype, headers=headers)
+
+        res = mail_server_pool.send_email(cr, uid or 1, email_msg, mail_server_id=None,
+                       smtp_server=smtp_server, smtp_port=smtp_port, smtp_user=smtp_user, smtp_password=smtp_password,
+                       smtp_encryption=('ssl' if ssl else None), debug=debug)
+    except Exception:
+        _log.exception("tools.email_send failed to deliver email")
+        return False
+    finally:
+        cr.close()
+    return res
 
 #----------------------------------------------------------
 # SMS

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to