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.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) for res_id, values in results.items(): 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(MaintenanceRequest, self).message_new(msg, custom_values=custom_values)
def message_route_verify(self, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, allow_private=False, drop_alias=False): res = super(MailThread, self).message_route_verify( message, message_dict, route, update_author=update_author, assert_model=assert_model, create_fallback=create_fallback, allow_private=allow_private, drop_alias=drop_alias) if res: alias = route[4] email_from = decode_message_header(message, 'From') message_id = message.get('Message-Id') # Alias: check alias_contact settings for employees if alias and alias.alias_contact == 'employees': email_address = email_split(email_from)[0] employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) if not employee: mail_template = self.env.ref('hr.mail_template_data_unknown_employee_email_address') self._routing_warn(_('alias %s does not accept unknown employees') % alias.alias_name, _('skipping'), message_id, route, False) self._routing_create_bounce_email(email_from, mail_template.body_html, message) return False return res
def send_mail_test(self): self.ensure_one() mails = self.env['mail.mail'] mailing = self.mass_mailing_id test_emails = tools.email_split(self.email_to) mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout') for test_mail in test_emails: # Convert links in absolute URLs before the application of the shortener mailing.write({'body_html': self.env['mail.thread']._replace_local_links(mailing.body_html)}) body = tools.html_sanitize(mailing.body_html, sanitize_attributes=True, sanitize_style=True) mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.name, 'body_html': mass_mail_layout.render({'body': body}, engine='ir.qweb', minimal_qcontext=True), 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], 'auto_delete': True, } mail = self.env['mail.mail'].create(mail_values) mails |= mail mails.send() return True
def _extract_moderation_values(self, message_type, **kwargs): """ This method is used to compute moderation status before the creation of a message. For this operation the message's author email address is required. This address is returned with status for other computations. """ moderation_status = 'accepted' email = '' if self.moderation and message_type in ['email', 'comment']: author_id = kwargs.get('author_id') if author_id and isinstance(author_id, pycompat.integer_types): email = self.env['res.partner'].browse([author_id]).email elif author_id: email = author_id.email elif kwargs.get('email_from'): email = tools.email_split(kwargs['email_from'])[0] else: email = self.env.user.email if email in self.mapped('moderator_ids.email'): return moderation_status, email status = self.env['mail.moderation'].sudo().search([('email', '=', email), ('channel_id', 'in', self.ids)]).mapped('status') if status and status[0] == 'allow': moderation_status = 'accepted' elif status and status[0] == 'ban': moderation_status = 'rejected' else: moderation_status = 'pending_moderation' return moderation_status, email
def send_mail_test(self): self.ensure_one() mails = self.env['mail.mail'] mailing = self.mass_mailing_id test_emails = tools.email_split(self.email_to) for test_mail in test_emails: # Convert links in absolute URLs before the application of the shortener mailing.write({'body_html': self.env['mail.template']._replace_local_links(mailing.body_html)}) 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 = self.env['mail.mail'].create(mail_values) unsubscribe_url = mail._get_unsubscribe_url(test_mail) body = tools.append_content_to_html(mailing.body_html, unsubscribe_url, plaintext=False, container_tag='p') mail.write({'body_html': body}) mails |= mail mails.send() return True
def message_new(self, msg_dict, custom_values=None): if custom_values is None: custom_values = {} # Retrieve the email address from the email field. The string is constructed like # 'foo <bar>'. We will extract 'bar' from this email_address = email_split(msg_dict.get('email_from', False))[0] # Look after an employee who has this email address or an employee for whom the related # user has this email address. In the case not employee is found, we send back an email # to explain that the expense will not be created. employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) if not employee: # Send back an email to explain why the expense has not been created mail_template = self.env.ref('hr_expense.mail_template_data_expense_unknown_email_address') mail_template.with_context(email_to=email_address).send_mail(self.env.ref('base.module_hr_expense').id) return False expense_description = msg_dict.get('subject', '') # Match the first occurence of '[]' in the string and extract the content inside it # Example: '[foo] bar (baz)' becomes 'foo'. This is potentially the product code # of the product to encode on the expense. If not, take the default product instead # which is 'Fixed Cost' default_product = self.env.ref('hr_expense.product_product_fixed_cost') pattern = '\[([^)]*)\]' product_code = re.search(pattern, expense_description) if product_code is None: product = default_product else: expense_description = expense_description.replace(product_code.group(), '') product = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?' # Match the last occurence of a float in the string # Example: '[foo] 50.3 bar 34.5' becomes '34.5'. This is potentially the price # to encode on the expense. If not, take 1.0 instead expense_price = re.findall(pattern, expense_description) # TODO: International formatting if not expense_price: price = 1.0 else: price = expense_price[-1][0] expense_description = expense_description.replace(price, '') try: price = float(price) except ValueError: price = 1.0 custom_values.update({ 'name': expense_description.strip(), 'employee_id': employee.id, 'product_id': product.id, 'product_uom_id': product.uom_id.id, 'quantity': 1, 'unit_amount': price }) return super(HrExpense, self).message_new(msg_dict, custom_values)
def _update_moderation_email(self, emails, status): """ This method adds emails into either white or black of the channel list of emails according to status. If an email in emails is already moderated, the method updates the email status. :param emails: list of email addresses to put in white or black list of channel. :param status: value is 'allow' or 'ban'. Emails are put in white list if 'allow', in black list if 'ban'. """ self.ensure_one() splitted_emails = [tools.email_split(email)[0] for email in emails if tools.email_split(email)] moderated = self.env['mail.moderation'].sudo().search([ ('email', 'in', splitted_emails), ('channel_id', 'in', self.ids) ]) cmds = [(1, record.id, {'status': status}) for record in moderated] not_moderated = [email for email in splitted_emails if email not in moderated.mapped('email')] cmds += [(0, 0, {'email': email, 'status': status}) for email in not_moderated] return self.write({'moderation_ids': cmds})
def generate_data(self, event, isCreating=False): if event.allday: start_date = event.start_date final_date = ( datetime.strptime(event.stop_date, tools.DEFAULT_SERVER_DATE_FORMAT) + timedelta(days=1) ).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) type = "date" vstype = "dateTime" else: start_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.start)).isoformat( "T" ) final_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.stop)).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": self.env.context.get("tz") or "UTC"}, "end": {type: final_date, vstype: None, "timeZone": self.env.context.get("tz") or "UTC"}, "attendees": attendee_list, "reminders": {"overrides": reminders, "useDefault": "false"}, "location": event.location or "", "visibility": event["privacy"] 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(): 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 message_new(self, msg_dict, custom_values=None): if custom_values is None: custom_values = {} email_address = email_split(msg_dict.get('email_from', False))[0] employee = self.env['hr.employee'].search([ '|', ('work_email', 'ilike', email_address), ('user_id.email', 'ilike', email_address) ], limit=1) expense_description = msg_dict.get('subject', '') # Match the first occurence of '[]' in the string and extract the content inside it # Example: '[foo] bar (baz)' becomes 'foo'. This is potentially the product code # of the product to encode on the expense. If not, take the default product instead # which is 'Fixed Cost' default_product = self.env.ref('hr_expense.product_product_fixed_cost') pattern = '\[([^)]*)\]' product_code = re.search(pattern, expense_description) if product_code is None: product = default_product else: expense_description = expense_description.replace(product_code.group(), '') products = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product product = products.filtered(lambda p: p.default_code == product_code.group(1)) or products[0] account = product.product_tmpl_id._get_product_accounts()['expense'] pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?' # Match the last occurence of a float in the string # Example: '[foo] 50.3 bar 34.5' becomes '34.5'. This is potentially the price # to encode on the expense. If not, take 1.0 instead expense_price = re.findall(pattern, expense_description) # TODO: International formatting if not expense_price: price = 1.0 else: price = expense_price[-1][0] expense_description = expense_description.replace(price, '') try: price = float(price) except ValueError: price = 1.0 custom_values.update({ 'name': expense_description.strip(), 'employee_id': employee.id, 'product_id': product.id, 'product_uom_id': product.uom_id.id, 'tax_ids': [(4, tax.id, False) for tax in product.supplier_taxes_id], 'quantity': 1, 'unit_amount': price, 'company_id': employee.company_id.id, }) if account: custom_values['account_id'] = account.id return super(HrExpense, self).message_new(msg_dict, custom_values)
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 find_or_create(self, email): """ 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] partners = self.search([('email', '=ilike', email)], limit=1) return partners.id or self.name_create(email)[0]
def _send_prepare_values(self, partner=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self)._send_prepare_values(partner) base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url').rstrip('/') if self.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(email_to) 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 get_mail_values(self, res_ids): """ Override method that generated the mail content by creating the mail.mail.statistics 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.model in [item[0] for item in self.env['mail.mass_mailing']._get_mailing_model()]: 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['mail.mass_mailing'].create({ 'mass_mailing_campaign_id': self.mass_mailing_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': self.model, 'mailing_domain': self.active_domain, }) blacklist = self._context.get('mass_mailing_blacklist') seen_list = self._context.get('mass_mailing_seen_list') for res_id in res_ids: mail_values = res[res_id] recips = tools.email_split(mail_values.get('email_to')) mail_to = recips[0].lower() if recips else False if (blacklist and mail_to in blacklist) or (seen_list and mail_to in seen_list): # 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) stat_vals = { 'model': self.model, 'res_id': res_id, 'mass_mailing_id': mass_mailing.id } # propagate exception state to stat when still-born if mail_values.get('state') == 'cancel': stat_vals['exception'] = fields.Datetime.now() mail_values.update({ 'mailing_id': mass_mailing.id, 'statistics_ids': [(0, 0, stat_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 message_new(self, msg_dict, custom_values=None): self = self.with_context(default_user_id=False) if custom_values is None: custom_values = {} regex = re.compile("^\[(.*)\]") match = regex.match(msg_dict.get('subject')).group(1) book_id = self.env['library.book'].search( [('name', '=', match), ('state', '=', 'available')], limit=1) custom_values['book_id'] = book_id.id email_from = email_escape_char(email_split(msg_dict.get('from'))[0]) custom_values['borrower_id'] = self._search_on_partner(email_from) return super(LibraryBookRent, self).message_new(msg_dict, custom_values)
def send(self, auto_commit=False, raise_exception=False): """ Prevents deleting emails for better control. Sends a CC to all linked contacts that have option activated. """ self.write({"auto_delete": False}) for mail in self: cc = mail.recipient_ids.mapped("other_contact_ids").filtered("email_copy") email_cc = [] if cc: email_cc = email_split(mail.email_cc or "") email_cc.extend(cc.mapped("email")) mail.email_cc = ";".join(email_cc) return super().send(auto_commit, raise_exception)
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. """ custom_values = custom_values if custom_values is not None else {} # Concatenate subject and body for request text request_text_tmpl = "<h1>%(subject)s</h1>\n<br/>\n<br/>%(body)s" request_text = request_text_tmpl % { 'subject': msg.get('subject', _("No Subject")), 'body': msg.get('body', '') } # Compute defaults defaults = { 'request_text': request_text, 'name': "###new###", # Special name to avoid # placing subject to as name } # Update defaults with partner and created_by_id if possible author_id = msg.get('author_id') if author_id: author = self.env['res.partner'].browse(author_id) defaults['partner_id'] = author.commercial_partner_id.id if len(author.user_ids) == 1: defaults['created_by_id'] = author.user_ids[0].id else: defaults['author_id'] = author.id else: author = False defaults.update(custom_values) request = super(RequestRequest, self).message_new(msg, custom_values=defaults) # Find partners from email and subscribe them email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) partner_ids = request._find_partner_from_emails(email_list, force_create=False) partner_ids = [pid for pid in partner_ids if pid] if author: partner_ids += [author.id] request.message_subscribe(partner_ids) return request
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 send_get_email_dict(self, partner=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self).send_get_email_dict(partner) base_url = self.env['ir.config_parameter'].get_param('web.base.url') if self.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(email_to) 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 message_route(self, message, message_dict, model=None, thread_id=None, custom_values=None): """ Override to udpate mass mailing statistics based on bounce emails """ bounce_alias = self.env['ir.config_parameter'].sudo().get_param("mail.bounce.alias") email_to = decode_message_header(message, 'To') email_to_localpart = (tools.email_split(email_to) or [''])[0].split('@', 1)[0].lower() if bounce_alias and bounce_alias in email_to_localpart: bounce_re = re.compile("%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias), re.UNICODE) bounce_match = bounce_re.search(email_to) if bounce_match: bounced_mail_id = bounce_match.group(1) self.env['mail.mail.statistics'].set_bounced(mail_mail_ids=[bounced_mail_id]) return super(MailThread, self).message_route(message, message_dict, model, thread_id, custom_values)
def _get_duplicated_leads_by_emails(self, partner_id, email, include_lost=False): _logger.info(f"\n\n\nContext Value: {self._context}\n\n\n") """ Search for opportunities that have the same partner and that arent done or cancelled """ if not email: return self.env['crm.lead'] partner_match_domain = [] for email in set(email_split(email) + [email]): partner_match_domain.append(('email_from', '=ilike', email)) if partner_id: partner_match_domain.append(('partner_id', '=', partner_id)) partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain if not partner_match_domain: return self.env['crm.lead'] domain = partner_match_domain if 'lead' in self._context and self._context.get('lead'): domain += [('name', '=', self._context.get('lead_name'))] if 'team_id' in self._context and self._context.get('team_id'): domain += [('team_id', '=', self._context.get('team_id'))] if 'user_id' in self._context and self._context.get('user_id'): domain += [('user_id', '=', self._context.get('user_id'))] if 'contact_name' in self._context and self._context.get( 'contact_name'): domain += [('contact_name', '=', self._context.get('contact_name')) ] if 'subdivision_id' in self._context and self._context.get( 'subdivision_id'): domain += [('subdivision_id', '=', self._context.get('subdivision_id'))] if 'subdivision_phase_id' in self._context and self._context.get( 'subdivision_phase_id'): domain += [('subdivision_phase_id', '=', self._context.get('subdivision_phase_id'))] if 'subdivision_project' in self._context and self._context.get( 'subdivision_project'): domain += [('house_model_id', '=', self._context.get('house_model_id'))] if not include_lost: domain += ['&', ('active', '=', True), ('probability', '<', 100)] else: domain += [ '|', '&', ('type', '=', 'lead'), ('active', '=', True), ('type', '=', 'opportunity') ] return self.search(domain)
def message_update(self, msg, update_vals=None): """Override message_update to subscribe partners""" email_list = tools.email_split( (msg.get("to") or "") + "," + (msg.get("cc") or "") ) partner_ids = [ p.id for p in self.env["mail.thread"]._mail_find_partner_from_emails( email_list, records=self, force_create=False ) if p ] self.message_subscribe(partner_ids) return super().message_update(msg, update_vals=update_vals)
def _alias_check_contact(self, message, message_dict, alias): if alias.alias_contact == 'employees' and self.ids: email_from = tools.decode_message_header(message, 'From') email_address = tools.email_split(email_from)[0] employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) if not employee: return { 'error_message': 'restricted to employees', 'error_template': self.env.ref('hr.mail_template_data_unknown_employee_email_address').body_html, } return True return super(MailAlias, self)._alias_check_contact(message, message_dict, alias)
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. """ custom_values = custom_values if custom_values is not None else {} # Ensure we have message_id if not msg.get('message_id'): msg['message_id'] = self.env['mail.message']._get_message_id(msg) # Compute default request text request_text = MAIL_REQUEST_TEXT_TMPL % { 'subject': msg.get('subject', _("No Subject")), 'body': msg.get('body', ''), } # Update defaults with partner and created_by_id if possible defaults = { 'name': "###new###", # Spec name to avoid using subj as req name 'request_text': request_text, 'original_message_id': msg['message_id'], } author_id = msg.get('author_id') if author_id: author = self.env['res.partner'].browse(author_id) defaults['partner_id'] = author.commercial_partner_id.id defaults['author_id'] = author.id if len(author.user_ids) == 1: defaults['created_by_id'] = author.user_ids[0].id else: author = False defaults.update(custom_values) request = super(RequestRequest, self).message_new(msg, custom_values=defaults) # Find partners from email and subscribe them email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) partner_ids = request._mail_find_partner_from_emails( email_list, force_create=False) partner_ids = [pid for pid in partner_ids if pid] if author: partner_ids += [author.id] request.message_subscribe(partner_ids) return request
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.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) for res_id, values in results.items(): 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 #Add By AARSOL For For user to add in it. partner_ids += self.env.user.partner_id and self.env.user.partner_id.ids results[res_id]['partner_ids'] = partner_ids return results
def message_new(self, msg, custom_values=None): partner_email = tools.email_split(msg["from"])[0] # Automatically create a partner if not msg.get("author_id"): alias = tools.email_split(msg["to"])[0].split("@")[0] project = self.env["project.project"].search([("alias_name", "=", alias)]) partner = self.env["res.partner"].create({ "parent_id": project.partner_id.id, "name": partner_email.split("@")[0], "email": partner_email, }) msg["author_id"] = partner.id if custom_values is None: custom_values = {} custom_values.update({ "description": msg["body"], "author_id": msg["author_id"] }) return super(ProjectTask, self).message_new(msg, custom_values=custom_values)
def _update_moderation_email(self, emails, status): """ This method adds emails into either white or black of the channel list of emails according to status. If an email in emails is already moderated, the method updates the email status. :param emails: list of email addresses to put in white or black list of channel. :param status: value is 'allow' or 'ban'. Emails are put in white list if 'allow', in black list if 'ban'. """ self.ensure_one() splitted_emails = [ tools.email_split(email)[0] for email in emails if tools.email_split(email) ] moderated = self.env['mail.moderation'].sudo().search([ ('email', 'in', splitted_emails), ('channel_id', 'in', self.ids) ]) cmds = [(1, record.id, {'status': status}) for record in moderated] not_moderated = [ email for email in splitted_emails if email not in moderated.mapped('email') ] cmds += [(0, 0, { 'email': email, 'status': status }) for email in not_moderated] return self.write({'moderation_ids': cmds})
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'): records = self.env[self.model].browse(res_ids).sudo() default_recipients = self.env['mail.thread']._message_get_default_recipients_on_records(records) for res_id, recipients in default_recipients.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) records_company = None if self._context.get('tpl_partners_only') and self.model and results and 'company_id' in self.env[self.model]._fields: records = self.env[self.model].browse(results.keys()).read(['company_id']) records_company = {rec['id']: (rec['company_id'][0] if rec['company_id'] else None) for rec in records} for res_id, values in results.items(): 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', '')) Partner = self.env['res.partner'] if records_company: Partner = Partner.with_context(default_company_id=records_company[res_id]) for mail in mails: partner_id = 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 _alias_check_contact_on_record(self, record, message, message_dict, alias): if alias.alias_contact == 'employees': email_from = tools.decode_message_header(message, 'From') email_address = tools.email_split(email_from)[0] employee = self.env['hr.employee'].search( [('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search( [('user_id.email', 'ilike', email_address)], limit=1) if not employee: return _('restricted to employees') return True return super(AliasMixin, self)._alias_check_contact_on_record( record, message, message_dict, alias)
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'): records = self.env[self.model].browse(res_ids).sudo() default_recipients = records._message_get_default_recipients() for res_id, recipients in default_recipients.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) records_company = None if self._context.get('tpl_partners_only') and self.model and results and 'company_id' in self.env[self.model]._fields: records = self.env[self.model].browse(results.keys()).read(['company_id']) records_company = {rec['id']: (rec['company_id'][0] if rec['company_id'] else None) for rec in records} for res_id, values in results.items(): 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', '')) Partner = self.env['res.partner'] if records_company: Partner = Partner.with_context(default_company_id=records_company[res_id]) for mail in mails: partner = 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_update(self, msg, update_vals=None): """ Override message_update to subscribe partners """ email_list = tools.email_split((msg.get("to") or "") + "," + (msg.get("cc") or "")) partner_ids = list( map( lambda x: x.id, self.env["mail.thread"]._mail_find_partner_from_emails( email_list, records=self, force_create=False), )) self.message_subscribe(partner_ids) stage_id_new = self.env["helpdesk.ticket.stage"].search( [("state", "=", "new")], limit=1) self.stage_id = stage_id_new.id if stage_id_new else self.stage_id.id return super().message_update(msg, update_vals=update_vals)
def _get_opt_out_list(self): """Handle models with opt_out field for excluding them.""" self.ensure_one() model = self.env[self.mailing_model_real].with_context( active_test=False) if self.mailing_model_real != "mailing.contact" and "opt_out" in model._fields: email_fname = "email_from" if "email" in model._fields: email_fname = "email" domain = safe_eval(self.mailing_domain) domain = [("opt_out", "=", True)] + domain recs = self.env[self.mailing_model_real].search(domain) normalized_email = (tools.email_split(c[email_fname]) for c in recs) return {e[0].lower() for e in normalized_email if e} return super()._get_opt_out_list()
def find_or_create(self, email): """ 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) name_emails = tools.email_split_and_format(email) if emails: email = emails[0] name_email = name_emails[0] else: name_email = email partners = self.search([('email', '=ilike', email)], limit=1) return partners.id or self.name_create(name_email)[0]
def message_new(self, msg_dict, custom_values=None): """This function extracts required fields of hr.holidays from incoming mail then creating records""" try: if custom_values is None: custom_values = {} msg_subject = msg_dict.get('subject', '') subject = re.search('LEAVE REQUEST', msg_subject) if subject is not None: email_address = email_split(msg_dict.get('email_from', False))[0] employee = self.env['hr.employee'].sudo().search([ '|', ('work_email', 'ilike', email_address), ('user_id.email', 'ilike', email_address) ], limit=1) msg_body = msg_dict.get('body', '') cleaner = re.compile('<.*?>') clean_msg_body = re.sub(cleaner, '', msg_body) date_list = re.findall(r'\d{2}/\d{2}/\d{4}', clean_msg_body) print('date_list',date_list) if len(date_list) > 0: date_from = date_list[0] # if len(date_list) > 1: if len(date_list) == 1: start_date = datetime.strptime(date_list[0], '%d/%m/%Y') date_to = start_date print('date_to',date_to) # no_of_days_temp = 1 else: start_date = datetime.strptime(date_list[0], '%d/%m/%Y') # start_date = start_date + timedelta(days=1) date_to = datetime.strptime(date_list[1], '%d/%m/%Y') no_of_days_temp = (datetime.strptime(str(date_to), "%Y-%m-%d %H:%M:%S") - datetime.strptime( str(start_date), '%Y-%m-%d %H:%M:%S')).days no_of_days_temp += 1 custom_values.update({ 'name': msg_subject.strip(), 'employee_id': employee.id, 'holiday_status_id': 1, 'request_date_from': start_date, 'request_date_to': date_to, 'number_of_days': no_of_days_temp }) return super(HrLeaveAlias, self).message_new(msg_dict, custom_values) except: pass
def _get_opt_out_list(self): """Handle models with opt_out field for excluding them.""" self.ensure_one() model = self.env[self.mailing_model_real].with_context( active_test=False) if (self.mailing_model_real != "mail.mass_mailing.contact" and 'opt_out' in model._fields): email_fname = 'email_from' if 'email' in model._fields: email_fname = 'email' domain = safe_eval(self.mailing_domain) domain = [('opt_out', '=', True)] + domain recs = self.env[self.mailing_model_real].search(domain) normalized_email = (tools.email_split(c[email_fname]) for c in recs) return set(e[0].lower() for e in normalized_email if e) return super()._get_opt_out_list()
def _send_prepare_values(self, partner=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self)._send_prepare_values(partner) base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url').rstrip('/') if self.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 urls_to_replace = [ (base_url + '/unsubscribe_from_list', self.mailing_id._get_unsubscribe_url(email_to, self.res_id)), (base_url + '/view', self.mailing_id._get_view_url(email_to, self.res_id)) ] for url_to_replace, new_url in urls_to_replace: if url_to_replace in res['body']: res['body'] = res['body'].replace(url_to_replace, new_url if new_url else '#') return res
def message_new(self, msg_dict, custom_values=None): if custom_values is None: custom_values = {} # find or create customer email = email_split(msg_dict.get('email_from', False))[0] name = email_split_and_format(msg_dict.get('email_from', False))[0] customer = self.env['plant.customer'].find_or_create(email, name) # happy Xmas plants = self.env['plant.plant'].search([]) plant = self.env['plant.plant'].browse([random.choice(plants.ids)]) custom_values.update({ 'customer_id': customer.id, 'line_ids': [(4, plant.id)], }) return super(Order, self).message_new(msg_dict, custom_values=custom_values)
def send_mail_test(self): """ Send with Sendgrid if needed. """ self.ensure_one() mailing = self.mass_mailing_id template = mailing.email_template_id.with_context( lang=mailing.lang.code or self.env.context['lang']) if template: # Send with SendGrid (and use E-mail Template) sendgrid_template = template.sendgrid_localized_template res_id = self.env.user.partner_id.id body = template.render_template(mailing.body_html, template.model, [res_id], post_process=True)[res_id] test_emails = tools.email_split(self.email_to) emails = self.env['mail.mail'] for test_mail in test_emails: email_vals = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.name, 'body_html': body, 'sendgrid_template_id': sendgrid_template.id, 'substitution_ids': template.render_substitutions(res_id)[res_id], 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], } emails += emails.create(email_vals) emails.send_sendgrid() else: super(TestMassMailing, self).send_mail_test() return True
def send_mail_test(self): self.ensure_one() ctx = dict(self.env.context) ctx.pop('default_state', None) self = self.with_context(ctx) mails_sudo = self.env['mail.mail'].sudo() mailing = self.mass_mailing_id test_emails = tools.email_split(self.email_to) mass_mail_layout = self.env.ref( 'mass_mailing.mass_mailing_mail_layout') for test_mail in test_emails: # Convert links in absolute URLs before the application of the shortener body = self.env['mail.render.mixin']._replace_local_links( mailing.body_html) body = tools.html_sanitize(body, sanitize_attributes=True, sanitize_style=True) mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.subject, 'body_html': mass_mail_layout._render({'body': body}, engine='ir.qweb', minimal_qcontext=True), 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], 'auto_delete': True, 'mail_server_id': mailing.mail_server_id.id, } mail = self.env['mail.mail'].sudo().create(mail_values) mails_sudo |= mail mails_sudo.send() return True
def _create_lead_partner_data(self, name): """ extract data from lead to create a partner :param name : furtur name of the partner :param is_company : True if the partner is a company :param parent_id : id of the parent partner (False if no parent) :returns res.partner record """ email_split = tools.email_split(self.partner_email) return { 'name': name, 'mobile': self.partner_mobile, 'user_id': self.user_id.id, 'team_id': self.team_id.id, 'email': email_split[0] if email_split else False, 'customer': True }
def message_route_process(self, message, message_dict, routes): rcpt_tos = ','.join([ tools.decode_message_header(message, 'Delivered-To'), tools.decode_message_header(message, 'To'), tools.decode_message_header(message, 'Cc'), tools.decode_message_header(message, 'Resent-To'), tools.decode_message_header(message, 'Resent-Cc') ]) rcpt_tos_websiteparts = [ e.split('@')[1].lower() for e in tools.email_split(rcpt_tos) ] website = self.env['website'].sudo().search([('domain', 'in', rcpt_tos_websiteparts)]) if website: self = self.with_context(website_id=website[0].id) return super(MailThread, self).message_route_process(message, message_dict, routes)
def message_route_verify(self, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, allow_private=False, drop_alias=False): res = super(MailThread, self).message_route_verify(message, message_dict, route, update_author=update_author, assert_model=assert_model, create_fallback=create_fallback, allow_private=allow_private, drop_alias=drop_alias) if res: alias = route[4] email_from = decode_message_header(message, 'From') message_id = message.get('Message-Id') # Alias: check alias_contact settings for employees if alias and alias.alias_contact == 'employees': email_address = email_split(email_from)[0] employee = self.env['hr.employee'].search( [('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search( [('user_id.email', 'ilike', email_address)], limit=1) if not employee: mail_template = self.env.ref( 'hr.mail_template_data_unknown_employee_email_address') self._routing_warn( _('alias %s does not accept unknown employees') % alias.alias_name, _('skipping'), message_id, route, False) self._routing_create_bounce_email(email_from, mail_template.body_html, message) return False return res
def send_mail_test(self): self.ensure_one() mails = self.env['mail.mail'] mailing = self.mass_mailing_id outgoing_mail_server = int( self.env['ir.config_parameter'].sudo().get_param( 'mass_mailing_outgoing_mailserver.outgoing_mail_server')) test_emails = tools.email_split(self.email_to) for test_mail in test_emails: # Convert links in absolute URLs before the application of the # shortener mailing.write({ 'body_html': self.env['mail.template']._replace_local_links( mailing.body_html) }) mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.name, 'body_html': mailing.body_html, 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], } if mailing.outgoing_mail_server: mail_values.update( {'mail_server_id': mailing.outgoing_mail_server.id}) elif outgoing_mail_server: mail_values.update({'mail_server_id': outgoing_mail_server}) mail = self.env['mail.mail'].create(mail_values) mails |= mail mails.send() return True
def _create_lead_partner_data(self, name, is_company, parent_id=False): """ extract data from lead to create a partner :param name : furtur name of the partner :param is_company : True if the partner is a company :param parent_id : id of the parent partner (False if no parent) :returns res.partner record """ email_split = tools.email_split(self.partner_email) return { 'name': name, 'user_id': self.env.context.get('default_user_id') or self.user_id.id, 'partner_name': self.partner_name, 'comment': self.description, 'team_id': self.team_id.id, 'mobile': self.partner_mobile, 'email': email_split[0] if email_split else False, 'type': 'contact' }
def message_route_process(self, message, message_dict, routes): rcpt_tos = ",".join([ tools.decode_message_header(message, "Delivered-To"), tools.decode_message_header(message, "To"), tools.decode_message_header(message, "Cc"), tools.decode_message_header(message, "Resent-To"), tools.decode_message_header(message, "Resent-Cc"), ]) rcpt_tos_websiteparts = [ e.split("@")[1].lower() for e in tools.email_split(rcpt_tos) ] website = (self.env["website"].sudo().search([ ("domain", "in", rcpt_tos_websiteparts) ])) if website: self = self.with_context(website_id=website[0].id) return super(MailThread, self).message_route_process(message, message_dict, routes)
def _create_user(self): """ Override portal user creation to prevent sending e-mail to new user. """ res_users = self.env['res.users'].with_context( noshortcut=True, no_reset_password=True) email = email_split(self.email) if email: email = email[0] else: email = self.partner_id.lastname.lower() + '@cs.local' values = { 'email': email, 'login': email, 'partner_id': self.partner_id.id, 'groups_id': [(6, 0, [])], 'notify_email': 'none', } return res_users.create(values)
def message_route_verify(self, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, allow_private=False): res = super(MailThread, self).message_route_verify(message, message_dict, route, update_author, assert_model, create_fallback, allow_private) if res: alias = route[4] email_from = decode_header(message, 'From') message_id = message.get('Message-Id') # Identically equal to the definition in mail module because sub methods are local # variables and cannot be called with super def _create_bounce_email(body_html): bounce_to = decode_header(message, 'Return-Path') or email_from bounce_mail_values = { 'body_html': body_html, 'subject': 'Re: %s' % message.get('subject'), 'email_to': bounce_to, 'auto_delete': True, } bounce_from = self.env['ir.mail_server']._get_default_bounce_address() if bounce_from: bounce_mail_values['email_from'] = 'MAILER-DAEMON <%s>' % bounce_from self.env['mail.mail'].create(bounce_mail_values).send() def _warn(message): _logger.info('Routing mail with Message-Id %s: route %s: %s', message_id, route, message) # Alias: check alias_contact settings for employees if alias and alias.alias_contact == 'employees': email_address = email_split(email_from)[0] employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) if not employee: mail_template = self.env.ref('hr.mail_template_data_unknown_employee_email_address') _warn('alias %s does not accept unknown employees, skipping' % alias.alias_name) _create_bounce_email(mail_template.body_html) return False return res
def send_mail_test(self): self.ensure_one() mails = self.env['mail.mail'] mailing = self.mass_mailing_id test_emails = tools.email_split(self.email_to) for test_mail in test_emails: # Convert links in absolute URLs before the application of the shortener mailing.write({'body_html': self.env['mail.template']._replace_local_links(mailing.body_html)}) mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.name, 'body_html': mailing.body_html, 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], 'auto_delete': True, } mail = self.env['mail.mail'].create(mail_values) mails |= mail mails.send() return True
def message_new(self, msg_dict, custom_values=None): email_address = email_split(msg_dict.get('email_from', False))[0] employee = self.env['hr.employee'].search([ '|', ('work_email', 'ilike', email_address), ('user_id.email', 'ilike', email_address) ], limit=1) expense_description = msg_dict.get('subject', '') if employee.user_id: currencies = employee.user_id.company_id.currency_id | employee.user_id.company_ids.mapped('currency_id') else: currencies = employee.company_id.currency_id product, price, currency_id, expense_description = self._parse_expense_subject(expense_description, currencies) vals = { 'employee_id': employee.id, 'name': expense_description, 'unit_amount': price, 'product_id': product.id if product else None, 'product_uom_id': product.uom_id.id, 'tax_ids': [(4, tax.id, False) for tax in product.supplier_taxes_id], 'quantity': 1, 'company_id': employee.company_id.id, 'currency_id': currency_id.id } account = product.product_tmpl_id._get_product_accounts()['expense'] if account: vals['account_id'] = account.id expense = super(HrExpense, self).message_new(msg_dict, dict(custom_values or {}, **vals)) self._send_expense_success_mail(msg_dict, expense) return expense
def email_split(self, msg): email_list = tools.email_split((msg.get("to") or "") + "," + (msg.get("cc") or "")) # check left-part is not already an alias aliases = self.mapped("project_id.alias_name") return filter(lambda x: x.split("@")[0] not in aliases, email_list)
def generate_data(self, event, isCreating=False): if event.allday: start_date = fields.Date.to_string(event.start_date) final_date = fields.Date.to_string(event.stop_date + timedelta(days=1)) type = 'date' vstype = 'dateTime' else: start_date = fields.Datetime.context_timestamp(self, event.start).isoformat('T') final_date = fields.Datetime.context_timestamp(self, event.stop).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.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': self.env.context.get('tz') or 'UTC', }, "end": { type: final_date, vstype: None, 'timeZone': self.env.context.get('tz') or 'UTC', }, "attendees": attendee_list, "reminders": { "overrides": reminders, "useDefault": "false" }, "location": event.location or '', "visibility": event['privacy'] 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(): 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 and not other_att.google_internal_event_id.startswith('_')] if other_google_ids: data["id"] = other_google_ids[0] return data
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] for mail_id in self.ids: try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # 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']), a['mimetype']) for a in mail.attachment_ids.sudo().read(['datas_fname', 'datas', 'mimetype'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail._send_prepare_values()) for partner in mail.recipient_ids: email_list.append(mail._send_prepare_values(partner=partner)) # headers headers = {} ICP = self.env['ir.config_parameter'].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.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(safe_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, smtp_session=smtp_session) except AssertionError as error: if str(error) == 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(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(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 email_split(self, msg): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias aliases = self.mapped('project_id.alias_name') return [x for x in email_list if x.split('@')[0] not in aliases]
def email_split(self, msg): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias return filter(lambda x: x.split('@')[0] not in self.mapped('project_id.alias_name'), email_list)
def get_mail_values(self, res_ids): """ Override method that generated the mail content by creating the mail.mail.statistics 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['mail.mass_mailing'].create({ 'mass_mailing_campaign_id': self.mass_mailing_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} blacklist = self._context.get('mass_mailing_blacklist') seen_list = self._context.get('mass_mailing_seen_list') for res_id in res_ids: mail_values = res[res_id] if mail_values.get('email_to'): recips = tools.email_split(mail_values['email_to']) else: recips = tools.email_split(partners_email.get(res_id)) mail_to = recips[0].lower() if recips else False if (blacklist and mail_to in blacklist) or (seen_list and mail_to in seen_list): # 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) stat_vals = { 'model': self.model, 'res_id': res_id, 'mass_mailing_id': mass_mailing.id } # propagate exception state to stat when still-born if mail_values.get('state') == 'cancel': stat_vals['exception'] = fields.Datetime.now() mail_values.update({ 'mailing_id': mass_mailing.id, 'statistics_ids': [(0, 0, stat_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 extract_email(email): """ extract the email address from a user-friendly email address """ addresses = email_split(email) return addresses[0] if addresses else ''
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] IrAttachment = self.env['ir.attachment'] for mail_id in self.ids: success_pids = [] failure_type = None processing_pid = None mail = None try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # remove attachments if user send the link with the access_token body = mail.body_html or '' attachments = mail.attachment_ids for link in re.findall(r'/web/(?:content|image)/([0-9]+)', body): attachments = attachments - IrAttachment.browse(int(link)) # 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']), a['mimetype']) for a in attachments.sudo().read(['datas_fname', 'datas', 'mimetype'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail._send_prepare_values()) for partner in mail.recipient_ids: values = mail._send_prepare_values(partner=partner) values['partner_id'] = partner email_list.append(values) # headers headers = {} ICP = self.env['ir.config_parameter'].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.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(safe_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.'), }) # Update notification in a transient exception state to avoid concurrent # update in case an email bounces while sending all emails related to current # mail record. notifs = self.env['mail.notification'].search([ ('is_email', '=', True), ('mail_id', 'in', mail.ids), ('email_status', 'not in', ('sent', 'canceled')) ]) if notifs: notif_msg = _('Error without exception. Probably due do concurrent access update of notification records. Please see with an administrator.') notifs.sudo().write({ 'email_status': 'exception', 'failure_type': 'UNKNOWN', 'failure_reason': notif_msg, }) # 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) processing_pid = email.pop("partner_id", None) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session) if processing_pid: success_pids.append(processing_pid) processing_pid = None except AssertionError as error: if str(error) == IrMailServer.NO_VALID_RECIPIENT: failure_type = "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 has been sent at least once, no major exception occured mail.write({'state': 'sent', 'message_id': res, 'failure_reason': False}) _logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 mail._postprocess_sent_message(success_pids=success_pids, failure_type=failure_type) 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) # mail status will stay on ongoing since transaction will be rollback raise except (psycopg2.Error, smtplib.SMTPServerDisconnected): # If an error with the database or SMTP session occurs, chances are that the cursor # or SMTP session are unusable, causing further errors when trying to save the state. _logger.exception( 'Exception while processing mail with ID %r and Msg-Id %r.', mail.id, mail.message_id) 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(success_pids=success_pids, failure_reason=failure_reason, failure_type='UNKNOWN') if raise_exception: if isinstance(e, (AssertionError, UnicodeEncodeError)): if isinstance(e, UnicodeEncodeError): value = "Invalid text: %s" % e.object else: # 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