X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fmail%2Fmail_mail.py;h=9dcea0793389e796d7e4b46e0b2ba6132b4192b9;hb=f7a76cbb17093784b4e4fc8fdb449819133b18a6;hp=b71176bcd0892bd2a581da46ab40f0359eb4dbf2;hpb=2fb2d9f956b5ddd37da3cc3fdb9f55ea1f0e1878;p=odoo%2Fodoo.git diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index b71176b..9dcea07 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -28,8 +28,8 @@ from urlparse import urljoin from openerp import tools from openerp import SUPERUSER_ID +from openerp.addons.base.ir.ir_mail_server import MailDeliveryException from openerp.osv import fields, osv -from openerp.osv.orm import except_orm from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -45,7 +45,6 @@ class mail_mail(osv.Model): _columns = { 'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'), - 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1), 'state': fields.selection([ ('outgoing', 'Outgoing'), ('sent', 'Sent'), @@ -56,28 +55,18 @@ class mail_mail(osv.Model): 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"), 'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1), - 'email_from': fields.char('From', help='Message sender, taken from user preferences.'), - 'email_to': fields.text('To', help='Message recipients'), + 'email_to': fields.text('To', help='Message recipients (emails)'), + 'recipient_ids': fields.many2many('res.partner', string='To (Partners)'), 'email_cc': fields.char('Cc', help='Carbon copy message recipients'), - 'reply_to': fields.char('Reply-To', help='Preferred response address for the message'), 'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"), - # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification # and during unlink() we will not cascade delete the parent and its attachments - 'notification': fields.boolean('Is Notification') + 'notification': fields.boolean('Is Notification', + help='Mail has been created to notify people of an existing mail.message'), } - def _get_default_from(self, cr, uid, context=None): - this = self.pool.get('res.users').browse(cr, uid, uid, context=context) - if this.alias_domain: - return '%s@%s' % (this.alias_name, this.alias_domain) - elif this.email: - return this.email - raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias.")) - _defaults = { 'state': 'outgoing', - 'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx), } def default_get(self, cr, uid, fields, context=None): @@ -88,6 +77,7 @@ class mail_mail(osv.Model): return super(mail_mail, self).default_get(cr, uid, fields, context=context) def create(self, cr, uid, values, context=None): + # notification field: if not set, set if mail comes from an existing mail.message if 'notification' not in values and values.get('mail_message_id'): values['notification'] = True return super(mail_mail, self).create(cr, uid, values, context=context) @@ -123,7 +113,7 @@ class mail_mail(osv.Model): if context is None: context = {} if not ids: - filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')] + filters = [('state', '=', 'outgoing')] if 'filters' in context: filters.extend(context['filters']) ids = self.search(cr, uid, filters, context=context) @@ -151,6 +141,32 @@ class mail_mail(osv.Model): self.unlink(cr, SUPERUSER_ID, [mail.id], context=context) return True + #------------------------------------------------------ + # mail_mail formatting, tools and send mechanism + #------------------------------------------------------ + + def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None): + """ Generate URLs for links in mails: + - partner is an user and has read access to the document: direct link to document with model, res_id + """ + if partner and partner.user_ids: + base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') + # the parameters to encode for the query and fragment part of url + query = {'db': cr.dbname} + fragment = { + 'login': partner.user_ids[0].login, + 'action': 'mail.action_mail_redirect', + } + if mail.notification: + fragment['message_id'] = mail.mail_message_id.id + elif mail.model and mail.res_id: + fragment.update(model=mail.model, res_id=mail.res_id) + + url = urljoin(base_url, "/web?%s#%s" % (urlencode(query), urlencode(fragment))) + return _("""Access your messages and documents in OpenERP""") % url + else: + return None + def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None): """ If subject is void and record_name defined: ' posted on ' @@ -166,62 +182,18 @@ class mail_mail(osv.Model): def send_get_mail_body(self, cr, uid, mail, partner=None, context=None): """ Return a specific ir_email body. The main purpose of this method - is to be inherited by Portal, to add a link for signing in, in - each notification email a partner receives. + is to be inherited to add custom content depending on some module. :param browse_record mail: mail.mail browse_record :param browse_record partner: specific recipient partner """ body = mail.body_html - # partner is a user, link to a related document (incentive to install portal) - if partner and partner.user_ids and mail.model and mail.res_id \ - and self.check_access_rights(cr, partner.user_ids[0].id, 'read', raise_exception=False): - related_user = partner.user_ids[0] - try: - self.pool.get(mail.model).check_access_rule(cr, related_user.id, [mail.res_id], 'read', context=context) - base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') - # the parameters to encode for the query and fragment part of url - query = {'db': cr.dbname} - fragment = { - 'login': related_user.login, - 'model': mail.model, - 'id': mail.res_id, - } - url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment))) - text = _("""

Access this document directly in OpenERP

""") % url - body = tools.append_content_to_html(body, ("

%s

" % text), plaintext=False) - except except_orm, e: - pass - return body - def send_get_mail_reply_to(self, cr, uid, mail, partner=None, context=None): - """ Return a specific ir_email reply_to. - - :param browse_record mail: mail.mail browse_record - :param browse_record partner: specific recipient partner - """ - if mail.reply_to: - return mail.reply_to - email_reply_to = False - - # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias - if mail.model and mail.res_id and hasattr(self.pool.get(mail.model), 'message_get_reply_to'): - email_reply_to = self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0] - # no alias reply_to -> reply_to will be the email_from, only the email part - if not email_reply_to and mail.email_from: - emails = tools.email_split(mail.email_from) - if emails: - email_reply_to = emails[0] - - # format 'Document name ' - if email_reply_to and mail.model and mail.res_id: - document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0] - if document_name: - # generate reply to - - email_reply_to = formataddr((_('Followers of %s') % document_name[1], email_reply_to)) - - return email_reply_to + # generate footer + link = self._get_partner_access_link(cr, uid, mail, partner, context=context) + if link: + body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div') + return body def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): """ Return a dictionary for specific email values, depending on a @@ -232,7 +204,6 @@ class mail_mail(osv.Model): """ body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context) subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context) - reply_to = self.send_get_mail_reply_to(cr, uid, mail, partner=partner, context=context) body_alternative = tools.html2plaintext(body) # generate email_to, heuristic: @@ -251,10 +222,9 @@ class mail_mail(osv.Model): 'body_alternative': body_alternative, 'subject': subject, 'email_to': email_to, - 'reply_to': reply_to, } - def send(self, cr, uid, ids, auto_commit=False, recipient_ids=None, context=None): + def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): """ Sends the selected emails immediately, ignoring their current state (mails that have already been sent should not be passed unless they should actually be re-sent). @@ -265,53 +235,60 @@ class mail_mail(osv.Model): :param bool auto_commit: whether to force a commit of the mail status after sending each mail (meant only for scheduler processing); should never be True during normal transactions (default: False) - :param list recipient_ids: specific list of res.partner recipients. - If set, one email is sent to each partner. Its is possible to - tune the sent email through ``send_get_mail_body`` and ``send_get_mail_subject``. - If not specified, one email is sent to mail_mail.email_to. + :param bool raise_exception: whether to raise an exception if the + email sending process has failed :return: True """ ir_mail_server = self.pool.get('ir.mail_server') - for mail in self.browse(cr, uid, ids, context=context): + ir_attachment = self.pool['ir.attachment'] + + for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): try: - # handle attachments - attachments = [] - for attach in mail.attachment_ids: - attachments.append((attach.datas_fname, base64.b64decode(attach.datas))) + # load attachment binary data with a separate read(), as prefetching all + # `datas` (binary field) could bloat the browse cache, triggerring + # soft/hard mem limits with temporary data. + attachment_ids = [a.id for a in mail.attachment_ids] + attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) + for a in ir_attachment.read(cr, SUPERUSER_ID, attachment_ids, + ['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] - if recipient_ids: - partner_obj = self.pool.get('res.partner') - existing_recipient_ids = partner_obj.exists(cr, SUPERUSER_ID, recipient_ids, context=context) - for partner in partner_obj.browse(cr, SUPERUSER_ID, existing_recipient_ids, context=context): - email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context)) - else: + if mail.email_to: email_list.append(self.send_get_email_dict(cr, uid, mail, context=context)) + for partner in mail.recipient_ids: + email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context)) + # headers + headers = {} + bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context) + catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) + if bounce_alias and catchall_domain: + if mail.model and mail.res_id: + headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) + else: + headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain) # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = ir_mail_server.build_email( - email_from = mail.email_from, - email_to = email.get('email_to'), - subject = email.get('subject'), - body = email.get('body'), - body_alternative = email.get('body_alternative'), - email_cc = tools.email_split(mail.email_cc), - reply_to = email.get('reply_to'), - attachments = attachments, - message_id = mail.message_id, - references = mail.references, - object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), - subtype = 'html', - subtype_alternative = 'plain') + email_from=mail.email_from, + email_to=email.get('email_to'), + subject=email.get('subject'), + body=email.get('body'), + body_alternative=email.get('body_alternative'), + email_cc=tools.email_split(mail.email_cc), + reply_to=mail.reply_to, + attachments=attachments, + message_id=mail.message_id, + references=mail.references, + object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), + subtype='html', + subtype_alternative='plain', + headers=headers) try: - res = ir_mail_server.send_email( - cr, uid, - msg, - mail_server_id=mail.mail_server_id.id, - context=context - ) + res = ir_mail_server.send_email(cr, uid, msg, + mail_server_id=mail.mail_server_id.id, + context=context) except AssertionError as error: if error.message == ir_mail_server.NO_VALID_RECIPIENT: # No valid recipient found for this particular @@ -332,14 +309,25 @@ class mail_mail(osv.Model): # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1 if mail_sent: + _logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) self._postprocess_sent_message(cr, uid, mail, context=context) except MemoryError: # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job # instead of marking the mail as failed + _logger.exception('MemoryError while processing mail with ID %r and Msg-Id %r. '\ + 'Consider raising the --limit-memory-hard startup option', + mail.id, mail.message_id) raise - except Exception: + except Exception as e: _logger.exception('failed sending mail.mail %s', mail.id) mail.write({'state': 'exception'}) + if raise_exception: + if isinstance(e, AssertionError): + # get the args of the original error, wrap into a value and throw a MailDeliveryException + # that is an except_orm, with name and value as arguments + value = '. '.join(e.args) + raise MailDeliveryException(_("Mail Delivery Failed"), value) + raise if auto_commit == True: cr.commit()