def _mail_cc_sanitized_raw_dict(self, cc_string): '''return a dict of sanitize_email:raw_email from a string of cc''' if not cc_string: return {} return { tools.email_normalize(email): tools.formataddr( (name, tools.email_normalize(email))) for (name, email) in tools.email_split_tuples(cc_string) }
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): """ Override _search in order to grep search on email field and make it lower-case and sanitized """ if args: new_args = [] for arg in args: if isinstance( arg, (list, tuple)) and arg[0] == 'email' and isinstance( arg[2], str): normalized = tools.email_normalize(arg[2]) if normalized: new_args.append([arg[0], arg[1], normalized]) else: new_args.append(arg) else: new_args.append(arg) else: new_args = args return super(MailBlackList, self)._search(new_args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
def action_invite(self): """ Process the wizard content and proceed with sending the related email(s), rendering any template patterns on the fly if needed """ self.ensure_one() Partner = self.env['res.partner'] # compute partners and emails, try to find partners for given emails valid_partners = self.partner_ids valid_emails = [] for email in emails_split.split(self.emails or ''): partner = False email_normalized = tools.email_normalize(email) if email_normalized: partner = Partner.search([('email_normalized', '=', email_normalized)]) if partner: valid_partners |= partner else: email_formatted = tools.email_split_and_format(email) if email_formatted: valid_emails.extend(email_formatted) if not valid_partners and not valid_emails: raise UserError(_("Please enter at least one valid recipient.")) answers = self._prepare_answers(valid_partners, valid_emails) for answer in answers: self._send_mail(answer) return {'type': 'ir.actions.act_window_close'}
def _add(self, email): normalized = tools.email_normalize(email) record = self.env["mail.blacklist"].with_context( active_test=False).search([('email', '=', normalized)]) if len(record) > 0: record.write({'active': True}) else: record = self.create({'email': email}) return record
def iap_enrich(self, from_cron=False): lead_emails = {} for lead in self: # If lead is lost, active == False, but is anyway removed from the search in the cron. if lead.probability == 100 or lead.iap_enrich_done: continue normalized_email = tools.email_normalize( lead.partner_address_email) or tools.email_normalize( lead.email_from) if normalized_email: lead_emails[lead.id] = normalized_email.split('@')[1] else: lead.message_post_with_view( 'crm_iap_lead_enrich.mail_message_lead_enrich_no_email', subtype_id=self.env.ref('mail.mt_note').id) if lead_emails: try: iap_response = self.env['iap.enrich.api']._request_enrich( lead_emails) except InsufficientCreditError: _logger.info( 'Sent batch %s enrich requests: failed because of credit', len(lead_emails)) if not from_cron: data = { 'url': self.env['iap.account'].get_credits_url('reveal'), } self[0].message_post_with_view( 'crm_iap_lead_enrich.mail_message_lead_enrich_no_credit', values=data, subtype_id=self.env.ref('mail.mt_note').id) except Exception as e: _logger.info( 'Sent batch %s enrich requests: failed with exception %s', len(lead_emails), e) else: _logger.info('Sent batch %s enrich requests: success', len(lead_emails)) self._iap_enrich_from_response(iap_response)
def update_opt_out(self, email, list_ids, value): if len(list_ids) > 0: model = self.env['mailing.contact'].with_context(active_test=False) records = model.search([('email_normalized', '=', tools.email_normalize(email))]) opt_out_records = self.env['mailing.contact.subscription'].search([ ('contact_id', 'in', records.ids), ('list_id', 'in', list_ids), ('opt_out', '!=', value) ]) opt_out_records.write({'opt_out': value}) message = _('The recipient <strong>unsubscribed from %s</strong> mailing list(s)') \ if value else _('The recipient <strong>subscribed to %s</strong> mailing list(s)') for record in records: # filter the list_id by record record_lists = opt_out_records.filtered( lambda rec: rec.contact_id.id == record.id) if len(record_lists) > 0: record.sudo().message_post(body=_(message % ', '.join( str(list.name) for list in record_lists.mapped('list_id'))))
def create(self, values): # First of all, extract values to ensure emails are really unique (and don't modify values in place) new_values = [] all_emails = [] for value in values: email = tools.email_normalize(value.get('email')) if not email: raise UserError(_('Invalid email address %r') % value['email']) if email in all_emails: continue all_emails.append(email) new_value = dict(value, email=email) new_values.append(new_value) """ To avoid crash during import due to unique email, return the existing records if any """ sql = '''SELECT email, id FROM mail_blacklist WHERE email = ANY(%s)''' emails = [v['email'] for v in new_values] self._cr.execute(sql, (emails, )) bl_entries = dict(self._cr.fetchall()) to_create = [v for v in new_values if v['email'] not in bl_entries] # TODO DBE Fixme : reorder ids according to incoming ids. results = super(MailBlackList, self).create(to_create) return self.env['mail.blacklist'].browse(bl_entries.values()) | results
def write(self, values): if 'email' in values: values['email'] = tools.email_normalize(values['email']) return super(MailBlackList, self).write(values)
def _compute_email_normalized(self): self._assert_primary_email() for record in self: record.email_normalized = tools.email_normalize( record[self._primary_email])
def get_mail_values(self, res_ids): """ Override method that generated the mail content by creating the mailing.trace values in the o2m of mail_mail, when doing pure email mass mailing. """ self.ensure_one() res = super(MailComposeMessage, self).get_mail_values(res_ids) # use only for allowed models in mass mailing if self.composition_mode == 'mass_mail' and \ (self.mass_mailing_name or self.mass_mailing_id) and \ self.env['ir.model'].sudo().search([('model', '=', self.model), ('is_mail_thread', '=', True)], limit=1): mass_mailing = self.mass_mailing_id if not mass_mailing: reply_to_mode = 'email' if self.no_auto_thread else 'thread' reply_to = self.reply_to if self.no_auto_thread else False mass_mailing = self.env['mailing.mailing'].create({ 'campaign_id': self.campaign_id.id, 'name': self.mass_mailing_name, 'template_id': self.template_id.id, 'state': 'done', 'reply_to_mode': reply_to_mode, 'reply_to': reply_to, 'sent_date': fields.Datetime.now(), 'body_html': self.body, 'mailing_model_id': self.env['ir.model']._get(self.model).id, 'mailing_domain': self.active_domain, }) # Preprocess res.partners to batch-fetch from db # if recipient_ids is present, it means they are partners # (the only object to fill get_default_recipient this way) recipient_partners_ids = [] read_partners = {} for res_id in res_ids: mail_values = res[res_id] if mail_values.get('recipient_ids'): # recipient_ids is a list of x2m command tuples at this point recipient_partners_ids.append( mail_values.get('recipient_ids')[0][1]) read_partners = self.env['res.partner'].browse( recipient_partners_ids) partners_email = {p.id: p.email for p in read_partners} opt_out_list = self._context.get('mass_mailing_opt_out_list') seen_list = self._context.get('mass_mailing_seen_list') mass_mail_layout = self.env.ref( 'mass_mailing.mass_mailing_mail_layout', raise_if_not_found=False) for res_id in res_ids: mail_values = res[res_id] if mail_values.get('email_to'): mail_to = tools.email_normalize(mail_values['email_to']) else: partner_id = (mail_values.get('recipient_ids') or [(False, '')])[0][1] mail_to = tools.email_normalize( partners_email.get(partner_id)) if (opt_out_list and mail_to in opt_out_list) or (seen_list and mail_to in seen_list) \ or (not mail_to or not email_re.findall(mail_to)): # prevent sending to blocked addresses that were included by mistake mail_values['state'] = 'cancel' elif seen_list is not None: seen_list.add(mail_to) trace_vals = { 'model': self.model, 'res_id': res_id, 'mass_mailing_id': mass_mailing.id, 'email': mail_to, } if mail_values.get('body_html') and mass_mail_layout: mail_values['body_html'] = mass_mail_layout.render( {'body': mail_values['body_html']}, engine='ir.qweb', minimal_qcontext=True) # propagate ignored state to trace when still-born if mail_values.get('state') == 'cancel': trace_vals['ignored'] = fields.Datetime.now() mail_values.update({ 'mailing_id': mass_mailing.id, 'mailing_trace_ids': [(0, 0, trace_vals)], # email-mode: keep original message for routing 'notification': mass_mailing.reply_to_mode == 'thread', 'auto_delete': not mass_mailing.keep_archives, }) return res
def test_mail_bounced_auto_blacklist(self): mass_mailing_contacts = self.env['mailing.contact'] mass_mailing = self.env['mailing.mailing'] mail_blacklist = self.env['mail.blacklist'] mail_statistics = self.env['mailing.trace'] # create mailing contact record self.mailing_contact_1 = mass_mailing_contacts.create({ 'name': 'test email 1', 'email': '*****@*****.**' }) base_parsed_values = { 'email_from': '*****@*****.**', 'to': '*****@*****.**', 'message_id': '<*****@*****.**>', 'bounced_partner': self.env['res.partner'].sudo(), 'bounced_message': self.env['mail.message'].sudo() } # create bounced history of 4 statistics for idx in range(4): mail_statistics.create({ 'model': 'mailing.contact', 'res_id': self.mailing_contact_1.id, 'bounced': datetime.datetime.now() - datetime.timedelta(weeks=idx + 2), 'email': self.mailing_contact_1.email, 'message_id': '<*****@*****.**>' % idx, }) base_parsed_values.update({ 'bounced_email': tools.email_normalize(self.mailing_contact_1.email), 'bounced_msg_id': '<*****@*****.**>' % idx }) self.env['mail.thread']._routing_handle_bounce( False, base_parsed_values) # create mass mailing record self.mass_mailing = mass_mailing.create({ 'name': 'test', 'subject': 'Booooounce!', 'mailing_domain': [('id', 'in', [self.mailing_contact_1.id])], 'body_html': 'This is a bounced mail for auto blacklist demo' }) self.mass_mailing.action_put_in_queue() res_ids = self.mass_mailing._get_remaining_recipients() composer_values = { 'body': self.mass_mailing.convert_links()[self.mass_mailing.id], 'subject': self.mass_mailing.name, 'model': self.mass_mailing.mailing_model_real, 'email_from': self.mass_mailing.email_from, 'composition_mode': 'mass_mail', 'mass_mailing_id': self.mass_mailing.id, 'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids], } composer = self.env['mail.compose.message'].with_context( active_ids=res_ids, mass_mailing_seen_list=self.mass_mailing._get_seen_list()).create( composer_values) composer.send_mail() mail_statistics.create({ 'model': 'mailing.contact', 'res_id': self.mailing_contact_1.id, 'bounced': datetime.datetime.now(), 'email': self.mailing_contact_1.email, 'message_id': '<*****@*****.**>', }) base_parsed_values.update({ 'bounced_email': tools.email_normalize(self.mailing_contact_1.email), 'bounced_msg_id': '<*****@*****.**>' }) # call bounced self.env['mail.thread']._routing_handle_bounce(False, base_parsed_values) # check blacklist blacklist_record = mail_blacklist.search([ ('email', '=', self.mailing_contact_1.email) ]) self.assertEqual( len(blacklist_record), 1, 'The email %s must be blacklisted' % self.mailing_contact_1.email)