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 _flectra_attendee_commands(self, google_event): attendee_commands = [] partner_commands = [] google_attendees = google_event.attendees or [] if len( google_attendees ) == 0 and google_event.organizer and google_event.organizer.get( 'self', False): user = google_event.owner(self.env) google_attendees += [{ 'email': user.partner_id.email, 'status': { 'response': 'accepted' }, }] emails = [a.get('email') for a in google_attendees] existing_attendees = self.env['calendar.attendee'] if google_event.exists(self.env): existing_attendees = self.browse(google_event.flectra_id( self.env)).attendee_ids attendees_by_emails = { tools.email_normalize(a.email): a for a in existing_attendees } for attendee in google_attendees: email = attendee.get('email') if email in attendees_by_emails: # Update existing attendees attendee_commands += [(1, attendees_by_emails[email].id, { 'state': attendee.get('responseStatus') })] else: # Create new attendees partner = self.env.user.partner_id if attendee.get( 'self') else self.env['res.partner'].find_or_create( attendee.get('email')) attendee_commands += [(0, 0, { 'state': attendee.get('responseStatus'), 'partner_id': partner.id })] partner_commands += [(4, partner.id)] if attendee.get('displayName') and not partner.name: partner.name = attendee.get('displayName') for flectra_attendee in attendees_by_emails.values(): # Remove old attendees if tools.email_normalize(flectra_attendee.email) not in emails: attendee_commands += [(2, flectra_attendee.id)] partner_commands += [(3, flectra_attendee.partner_id.id)] return attendee_commands, partner_commands
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: limit = None if self.survey_users_login_required else 1 partner = Partner.search( [('email_normalized', '=', email_normalized)], limit=limit) 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 _parse_partner_name(self, text): """ Parse partner name (given by text) in order to find a name and an email. Supported syntax: * Raoul <*****@*****.**> * "Raoul le Grand" <*****@*****.**> * Raoul [email protected] (strange fault tolerant support from df40926d2a57c101a3e2d221ecfd08fbb4fea30e) Otherwise: default, everything is set as the name. Starting from 13.3 returned email will be normalized to have a coherent encoding. """ name, email = '', '' split_results = tools.email_split_tuples(text) if split_results: name, email = split_results[0] if email and not name: fallback_emails = tools.email_split(text.replace(' ', ',')) if fallback_emails: email = fallback_emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() if email: email = tools.email_normalize(email) else: name, email = text, '' return name, email
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 _remove(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.action_archive() else: record = record.create({'email': email, 'active': False}) return record
def iap_enrich(self, from_cron=False): # Split self in a list of sub-recordsets or 50 records to prevent timeouts batches = [self[index:index + 50] for index in range(0, len(self), 50)] for leads in batches: lead_emails = {} with self._cr.savepoint(): try: self._cr.execute( "SELECT 1 FROM {} WHERE id in %(lead_ids)s FOR UPDATE NOWAIT".format(self._table), {'lead_ids': tuple(leads.ids)}, log_exceptions=False) for lead in leads: # 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.email_from) if not normalized_email: lead.message_post_with_view( 'crm_iap_lead_enrich.mail_message_lead_enrich_no_email', subtype_id=self.env.ref('mail.mt_note').id) continue email_domain = normalized_email.split('@')[1] # Discard domains of generic email providers as it won't return relevant information if email_domain in EMAIL_PROVIDERS: lead.write({'iap_enrich_done': True}) lead.message_post_with_view( 'crm_iap_lead_enrich.mail_message_lead_enrich_notfound', subtype_id=self.env.ref('mail.mt_note').id) else: lead_emails[lead.id] = email_domain if lead_emails: try: iap_response = self.env['iap.enrich.api']._request_enrich(lead_emails) except iap_tools.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'), } leads[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) # Since there are no credits left, there is no point to process the other batches break 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) except OperationalError: _logger.error('A batch of leads could not be enriched :%s', repr(leads)) continue # Commit processed batch to avoid complete rollbacks and therefore losing credits. if not self.env.registry.in_test_mode(): self.env.cr.commit()
def res_partner_get_by_email(self, email, name, **kwargs): response = {} #compute the sender's domain normalized_email = tools.email_normalize(email) if not normalized_email: response['error'] = 'Bad email.' return response sender_domain = normalized_email.split('@')[1] # Search for the partner based on the email. # If multiple are found, take the first one. partner = request.env['res.partner'].search( [('email', 'in', [normalized_email, email])], limit=1) if partner: response['partner'] = { 'id': partner.id, 'name': partner.name, 'title': partner.function, 'email': partner.email, 'image': partner.image_128, 'phone': partner.phone, 'mobile': partner.mobile, 'enrichment_info': None } # if there is already a company for this partner, just take it without enrichment. if partner.parent_id: response['partner']['company'] = self._get_company_dict( partner.parent_id) else: company = self._find_existing_company(sender_domain) if not company: # create and enrich company company, enrichment_info = self._create_company_from_iap( sender_domain) response['enrichment_info'] = enrichment_info partner.write({'parent_id': company}) response['partner']['company'] = self._get_company_dict( company) else: #no partner found response['partner'] = { 'id': -1, 'name': name, 'email': email, 'enrichment_info': None } company = self._find_existing_company(sender_domain) if not company: # create and enrich company company, enrichment_info = self._create_company_from_iap( sender_domain) response['enrichment_info'] = enrichment_info response['partner']['company'] = self._get_company_dict(company) return response
def _validate_char_box(self, answer): # Email format validation # all the strings of the form "<something>@<anything>.<extension>" will be accepted if self.validation_email: if not tools.email_normalize(answer): return {self.id: _('This answer must be an email address')} # Answer validation (if properly defined) # Length of the answer must be in a range if self.validation_required: if not (self.validation_length_min <= len(answer) <= self.validation_length_max): return {self.id: self.validation_error_msg} return {}
def find_or_create(self, email, assert_valid_email=False): """ Override to use the email_normalized field. """ if not email: raise ValueError( _('An email is required for find_or_create to work')) parsed_name, parsed_email = self._parse_partner_name(email) if parsed_email: email_normalized = tools.email_normalize(parsed_email) if email_normalized: partners = self.search( [('email_normalized', '=', email_normalized)], limit=1) if partners: return partners return super(Partner, self).find_or_create( email, assert_valid_email=assert_valid_email)
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 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, 'subject': self.subject, '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, # if mail_to is void, keep falsy values to allow searching / debugging traces 'email': mail_to or mail_values.get('email_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 _flectra_attendee_commands_m(self, microsoft_event): commands_attendee = [] commands_partner = [] microsoft_attendees = microsoft_event.attendees or [] emails = [a.get('emailAddress').get('address') for a in microsoft_attendees if email_normalize(a.get('emailAddress').get('address'))] existing_attendees = self.env['calendar.attendee'] if microsoft_event.exists(self.env): existing_attendees = self.env['calendar.attendee'].search([ ('event_id', '=', microsoft_event.flectra_id(self.env)), ('email', 'in', emails)]) elif self.env.user.partner_id.email not in emails: commands_attendee += [(0, 0, {'state': 'accepted', 'partner_id': self.env.user.partner_id.id})] commands_partner += [(4, self.env.user.partner_id.id)] partners = self.env['mail.thread']._mail_find_partner_from_emails(emails, records=self, force_create=True) attendees_by_emails = {a.email: a for a in existing_attendees} for email, partner, m_attendee in zip(emails, partners, microsoft_attendees): state = ATTENDEE_CONVERTER_M2O.get(m_attendee.get('status').get('response')) if email in attendees_by_emails: # Update existing attendees commands_attendee += [(1, attendees_by_emails[email].id, {'state': state})] else: # Create new attendees commands_attendee += [(0, 0, {'state': state, 'partner_id': partner.id})] commands_partner += [(4, partner.id)] if m_attendee.get('emailAddress').get('name') and not partner.name: partner.name = m_attendee.get('emailAddress').get('name') for flectra_attendee in attendees_by_emails.values(): # Remove old attendees if flectra_attendee.email not in emails: commands_attendee += [(2, flectra_attendee.id)] commands_partner += [(3, flectra_attendee.partner_id.id)] return commands_attendee, commands_partner
def _compute_email_normalized(self): self._assert_primary_email() for record in self: record.email_normalized = tools.email_normalize( record[self._primary_email])