def generate_recipients(self, results, res_ids): """Generates the recipients of the template. Default values can ben generated instead of the template values if requested by template or context. Emails (email_to, email_cc) can be transformed into partners if requested in the context. """ self.ensure_one() if self.use_default_to or self._context.get('tpl_force_default_to'): default_recipients = self.env[ 'mail.thread'].message_get_default_recipients( res_model=self.model, res_ids=res_ids) for res_id, recipients in default_recipients.iteritems(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) for res_id, values in results.iteritems(): partner_ids = values.get('partner_ids', list()) if self._context.get('tpl_partners_only'): mails = tools.email_split(values.pop( 'email_to', '')) + tools.email_split( values.pop('email_cc', '')) for mail in mails: partner_id = self.env['res.partner'].find_or_create(mail) partner_ids.append(partner_id) partner_to = values.pop('partner_to', '') if partner_to: # placeholders could generate '', 3, 2 due to some empty field values tpl_partner_ids = [ int(pid) for pid in partner_to.split(',') if pid ] partner_ids += self.env['res.partner'].sudo().browse( tpl_partner_ids).exists().ids results[res_id]['partner_ids'] = partner_ids return results
def message_new(self, msg, custom_values=None): """ Overrides mail_thread message_new that is called by the mailgateway through message_process. This override updates the document according to the email. """ if custom_values is None: custom_values = {} email = tools.email_split(msg.get('from')) and tools.email_split(msg.get('from'))[0] or False user = self.env['res.users'].search([('login', '=', email)], limit=1) if user: employee = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1) if employee: custom_values['employee_id'] = employee and employee[0].id return super(HrEquipmentRequest, self).message_new(msg, custom_values=custom_values)
def email_split(self, cr, uid, ids, msg, context=None): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias issue_ids = self.browse(cr, uid, ids, context=context) aliases = [ issue.project_id.alias_name for issue in issue_ids if issue.project_id ] return filter(lambda x: x.split('@')[0] not in aliases, email_list)
def test_email_split(self): cases = [ ("John <*****@*****.**>", ['*****@*****.**']), # regular form ("d@x; 1@2", ['d@x', '1@2']), # semi-colon + extra space ("'(ss)' <*****@*****.**>, 'foo' <foo@bar>", ['*****@*****.**', 'foo@bar']), # comma + single-quoting ('"*****@*****.**"<*****@*****.**>', ['*****@*****.**']), # double-quoting ('"<jg>" <*****@*****.**>', ['*****@*****.**']), # double-quoting with brackets ] for text, expected in cases: self.assertEqual(email_split(text), expected, 'email_split is broken')
def _parse_partner_name(self, text, context=None): """ Supported syntax: - 'Raoul <*****@*****.**>': will find name and email address - otherwise: default, everything is set as the name """ emails = tools.email_split(text.replace(' ',',')) if emails: email = emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() else: name, email = text, '' return name, email
def send_get_email_dict(self, cr, uid, ids, partner=None, context=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self).send_get_email_dict(cr, uid, ids, partner, context=context) mail = self.browse(cr, uid, ids[0], context=context) base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') if mail.mailing_id and res.get('body') and res.get('email_to'): emails = tools.email_split(res.get('email_to')[0]) email_to = emails and emails[0] or False unsubscribe_url= self._get_unsubscribe_url(cr, uid, mail, email_to, context=context) link_to_replace = base_url+'/unsubscribe_from_list' if link_to_replace in res['body']: res['body'] = res['body'].replace(link_to_replace, unsubscribe_url if unsubscribe_url else '#') return res
def _parse_partner_name(self, text, context=None): """ Supported syntax: - 'Raoul <*****@*****.**>': will find name and email address - otherwise: default, everything is set as the name """ emails = tools.email_split(text.replace(' ', ',')) if emails: email = emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() else: name, email = text, '' return name, email
def find_or_create(self, cr, uid, email, context=None): """ Find a partner with the given ``email`` or use :py:method:`~.name_create` to create one :param str email: email-like string, which should contain at least one email, e.g. ``"Raoul Grosbedon <*****@*****.**>"``""" assert email, 'an email is required for find_or_create to work' emails = tools.email_split(email) if emails: email = emails[0] ids = self.search(cr, uid, [('email','=ilike',email)], context=context) if not ids: return self.name_create(cr, uid, email, context=context)[0] return ids[0]
def find_or_create(self, cr, uid, email, context=None): """ Find a partner with the given ``email`` or use :py:method:`~.name_create` to create one :param str email: email-like string, which should contain at least one email, e.g. ``"Raoul Grosbedon <*****@*****.**>"``""" assert email, 'an email is required for find_or_create to work' emails = tools.email_split(email) if emails: email = emails[0] ids = self.search(cr, uid, [('email', '=ilike', email)], context=context) if not ids: return self.name_create(cr, uid, email, context=context)[0] return ids[0]
def send_mail_test(self, cr, uid, ids, context=None): Mail = self.pool['mail.mail'] for wizard in self.browse(cr, uid, ids, context=context): mailing = wizard.mass_mailing_id test_emails = tools.email_split(wizard.email_to) mail_ids = [] for test_mail in test_emails: mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.name, 'body_html': '', 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], } mail_mail_obj = Mail.browse(cr, uid, Mail.create(cr, uid, mail_values, context=context), context=context) unsubscribe_url = Mail._get_unsubscribe_url(cr, uid, mail_mail_obj, test_mail, context=context) body = tools.append_content_to_html(mailing.body_html, unsubscribe_url, plaintext=False, container_tag='p') Mail.write(cr, uid, mail_mail_obj.id, {'body_html': mailing.body_html}, context=context) mail_ids.append(mail_mail_obj.id) Mail.send(cr, uid, mail_ids, context=context) return True
def send_get_email_dict(self, cr, uid, ids, partner=None, context=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self).send_get_email_dict(cr, uid, ids, partner, context=context) mail = self.browse(cr, uid, ids[0], context=context) base_url = self.pool.get('ir.config_parameter').get_param( cr, uid, 'web.base.url') if mail.mailing_id and res.get('body') and res.get('email_to'): emails = tools.email_split(res.get('email_to')[0]) email_to = emails and emails[0] or False unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context) link_to_replace = base_url + '/unsubscribe_from_list' if link_to_replace in res['body']: res['body'] = res['body'].replace( link_to_replace, unsubscribe_url if unsubscribe_url else '#') return res
def extract_email(email): """ extract the email address from a user-friendly email address """ addresses = email_split(email) return addresses[0] if addresses else ''
def email_split(self, cr, uid, ids, msg, context=None): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias issue_ids = self.browse(cr, uid, ids, context=context) aliases = [issue.project_id.alias_name for issue in issue_ids if issue.project_id] return filter(lambda x: x.split('@')[0] not in aliases, email_list)
def generate_data(self, cr, uid, event, isCreating=False, context=None): if not context: context = {} if event.allday: start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0] final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.stop, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(days=1), context=context).isoformat('T').split('T')[0] type = 'date' vstype = 'dateTime' else: start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T') final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.stop, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T') type = 'dateTime' vstype = 'date' attendee_list = [] for attendee in event.attendee_ids: email = tools.email_split(attendee.email) email = email[0] if email else '*****@*****.**' attendee_list.append({ 'email': email, 'displayName': attendee.partner_id.name, 'responseStatus': attendee.state or 'needsAction', }) reminders = [] for alarm in event.alarm_ids: reminders.append({ "method": "email" if alarm.type == "email" else "popup", "minutes": alarm.duration_minutes }) data = { "summary": event.name or '', "description": event.description or '', "start": { type: start_date, vstype: None, 'timeZone': context.get('tz', 'UTC'), }, "end": { type: final_date, vstype: None, 'timeZone': context.get('tz', 'UTC'), }, "attendees": attendee_list, "reminders": { "overrides": reminders, "useDefault": "false" }, "location": event.location or '', "visibility": event['class'] or 'public', } if event.recurrency and event.rrule: data["recurrence"] = ["RRULE:" + event.rrule] if not event.active: data["state"] = "cancelled" if not self.get_need_synchro_attendee(cr, uid, context=context): data.pop("attendees") if isCreating: other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids if other_att.google_internal_event_id] if other_google_ids: data["id"] = other_google_ids[0] return data
def send(self, auto_commit=False, raise_exception=False): """ 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). Emails successfully delivered are marked as 'sent', and those that fail to be deliver are marked as 'exception', and the corresponding error mail is output in the server logs. :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 bool raise_exception: whether to raise an exception if the email sending process has failed :return: True """ IrMailServer = self.env['ir.mail_server'] for mail in self: try: # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method if mail.model: model = self.env['ir.model'].sudo().search([('model', '=', mail.model)])[0] else: model = None if model: mail = mail.with_context(model_name=model.name) # 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. attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) for a in mail.attachment_ids.sudo().read(['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append(mail.send_get_email_dict(partner=partner)) # headers headers = {} bounce_alias = self.env['ir.config_parameter'].get_param("mail.bounce.alias") catchall_domain = self.env['ir.config_parameter'].get_param("mail.catchall.domain") 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) if mail.headers: try: headers.update(eval(mail.headers)) except Exception: pass # Writing on the mail object may fail (e.g. lock on user) which # would trigger a rollback *after* actually sending the email. # To avoid sending twice the same email, provoke the failure earlier mail.write({ 'state': 'exception', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.'), }) mail_sent = False # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.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 = IrMailServer.send_email(msg, mail_server_id=mail.mail_server_id.id) except AssertionError as error: if error.message == IrMailServer.NO_VALID_RECIPIENT: # No valid recipient found for this particular # mail item -> ignore error to avoid blocking # delivery to next recipients, if any. If this is # the only recipient, the mail will show as failed. _logger.info("Ignoring invalid recipients for mail.mail %s: %s", mail.message_id, email.get('email_to')) else: raise if res: mail.write({'state': 'sent', 'message_id': res, 'failure_reason': False}) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message_v9(mail_sent=mail_sent) 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 psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({'state': 'exception', 'failure_reason': failure_reason}) mail._postprocess_sent_message_v9(mail_sent=False) 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 is True: self._cr.commit() return True
def send(self, auto_commit=False, raise_exception=False): """ 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). Emails successfully delivered are marked as 'sent', and those that fail to be deliver are marked as 'exception', and the corresponding error mail is output in the server logs. :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 bool raise_exception: whether to raise an exception if the email sending process has failed :return: True """ IrMailServer = self.env['ir.mail_server'] for mail in self: try: # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method if mail.model: model = self.env['ir.model'].sudo().search([ ('model', '=', mail.model) ])[0] else: model = None if model: mail = mail.with_context(model_name=model.name) # 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. attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) for a in mail.attachment_ids.sudo().read( ['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append( mail.send_get_email_dict(partner=partner)) # headers headers = {} bounce_alias = self.env['ir.config_parameter'].get_param( "mail.bounce.alias") catchall_domain = self.env['ir.config_parameter'].get_param( "mail.catchall.domain") 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) if mail.headers: try: headers.update(eval(mail.headers)) except Exception: pass # Writing on the mail object may fail (e.g. lock on user) which # would trigger a rollback *after* actually sending the email. # To avoid sending twice the same email, provoke the failure earlier mail.write({ 'state': 'exception', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) mail_sent = False # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.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 = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id) except AssertionError as error: if error.message == IrMailServer.NO_VALID_RECIPIENT: # No valid recipient found for this particular # mail item -> ignore error to avoid blocking # delivery to next recipients, if any. If this is # the only recipient, the mail will show as failed. _logger.info( "Ignoring invalid recipients for mail.mail %s: %s", mail.message_id, email.get('email_to')) else: raise if res: mail.write({ 'state': 'sent', 'message_id': res, 'failure_reason': False }) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message_v9(mail_sent=mail_sent) 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 psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message_v9(mail_sent=False) 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 is True: self._cr.commit() return True