Example #1
0
    def _create_moderation_rule(self, status):
        """Create a moderation rule <mail.group.moderation> with the given status.

        Update existing moderation rule for the same email address if found,
        otherwise create a new rule.
        """
        if status not in ('ban', 'allow'):
            raise ValueError(_('Wrong status (%s)', status))

        for message in self:
            if not email_normalize(message.email_from):
                raise UserError(
                    _('The email "%s" is not valid.', message.email_from))

        existing_moderation = self.env['mail.group.moderation'].search(
            expression.OR([[('email', '=',
                             email_normalize(message.email_from)),
                            ('mail_group_id', '=', message.mail_group_id.id)]
                           for message in self]))
        existing_moderation.status = status

        # Add the value in a set to create only 1 moderation rule per (email_normalized, group)
        moderation_to_create = {
            (email_normalize(message.email_from), message.mail_group_id.id)
            for message in self if email_normalize(
                message.email_from) not in existing_moderation.mapped('email')
        }

        self.env['mail.group.moderation'].create([{
            'email': email,
            'mail_group_id': mail_group_id,
            'status': status,
        } for email, mail_group_id in moderation_to_create])
Example #2
0
    def _odoo_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.odoo_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 odoo_attendee in attendees_by_emails.values():
            # Remove old attendees
            if tools.email_normalize(odoo_attendee.email) not in emails:
                attendee_commands += [(2, odoo_attendee.id)]
                partner_commands += [(3, odoo_attendee.partner_id.id)]
        return attendee_commands, partner_commands
Example #3
0
    def event_track_proposal_post(self, event, **post):
        if not event.can_access_from_current_website():
            raise NotFound()

        # Only accept existing tag indices. Use search instead of browse + exists:
        # this prevents users to register colorless tags if not allowed to (ACL).
        input_tag_indices = [int(tag_id) for tag_id in post['tags'].split(',') if tag_id]
        valid_tag_indices = request.env['event.track.tag'].search([('id', 'in', input_tag_indices)]).ids

        contact = request.env['res.partner']
        visitor_partner = request.env['website.visitor']._get_visitor_from_request().partner_id
        # Contact name is required. Therefore, empty contacts are not considered here. At least one of contact_phone
        # and contact_email must be filled. Email is verified. If the post tries to create contact with no valid entry,
        # raise exception. If normalized email is the same as logged partner, use its partner_id on track instead.
        # This prevents contact duplication. Otherwise, create new contact with contact additional info of post.
        if post.get('add_contact_information'):
            valid_contact_email = tools.email_normalize(post.get('contact_email'))
            # Here, the phone is not formatted. To format it, one needs a country. Based on a country, from geoip for instance.
            # The problem is that one could propose a track in country A with phone number of country B. Validity is therefore
            # quite tricky. We accept any format of contact_phone. Could be improved with select country phone widget.
            if valid_contact_email or post.get('contact_phone'):
                if visitor_partner and valid_contact_email == visitor_partner.email_normalized:
                    contact = visitor_partner
                else:
                    contact = request.env['res.partner'].sudo().create({
                        'email': valid_contact_email,
                        'name': post.get('contact_name'),
                        'phone': post.get('contact_phone'),
                    })
            else:
                raise exceptions.ValidationError(_("Format Error : please enter a valid contact phone or contact email."))
        # If the speaker email is the same as logged user's, then also uses its partner on track, same as above.
        else:
            valid_speaker_email = tools.email_normalize(post['partner_email'])
            if visitor_partner and valid_speaker_email == visitor_partner.email_normalized:
                contact = visitor_partner

        track = request.env['event.track'].with_context({'mail_create_nosubscribe': True}).sudo().create({
            'name': post['track_name'],
            'partner_id': contact.id,
            'partner_name': post['partner_name'],
            'partner_email': post['partner_email'],
            'partner_phone': post['partner_phone'],
            'partner_function': post['partner_function'],
            'contact_phone': contact.phone,
            'contact_email': contact.email,
            'event_id': event.id,
            'tag_ids': [(6, 0, valid_tag_indices)],
            'description': plaintext2html(post['description']),
            'partner_biography': plaintext2html(post['partner_biography']),
            'user_id': False,
            'image': base64.b64encode(post['image'].read()) if post.get('image') else False,
        })

        if request.env.user != request.website.user_id:
            track.sudo().message_subscribe(partner_ids=request.env.user.partner_id.ids)

        return request.redirect('/event/%s/track_proposal/success/%s' % (event.id, track.id))
Example #4
0
 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): formataddr(
             (name, tools.email_normalize(email)))
         for (name, email) in tools.email_split_tuples(cc_string)
     }
Example #5
0
    def _find_mail_server(self, email_from, mail_servers=None):
        """Find the appropriate mail server for the given email address.

        Returns: Record<ir.mail_server>, email_from
        - Mail server to use to send the email (None if we use the odoo-bin arguments)
        - Email FROM to use to send the email (in some case, it might be impossible
          to use the given email address directly if no mail server is configured for)
        """
        email_from_normalized = email_normalize(email_from)
        email_from_domain = email_domain_extract(email_from_normalized)
        notifications_email = email_normalize(self._get_default_from_address())
        notifications_domain = email_domain_extract(notifications_email)

        if mail_servers is None:
            mail_servers = self.sudo().search([], order='sequence')

        # 1. Try to find a mail server for the right mail from
        mail_server = mail_servers.filtered(lambda m: email_normalize(m.from_filter) == email_from_normalized)
        if mail_server:
            return mail_server[0], email_from

        mail_server = mail_servers.filtered(lambda m: email_domain_normalize(m.from_filter) == email_from_domain)
        if mail_server:
            return mail_server[0], email_from

        # 2. Try to find a mail server for <*****@*****.**>
        if notifications_email:
            mail_server = mail_servers.filtered(lambda m: email_normalize(m.from_filter) == notifications_email)
            if mail_server:
                return mail_server[0], notifications_email

            mail_server = mail_servers.filtered(lambda m: email_domain_normalize(m.from_filter) == notifications_domain)
            if mail_server:
                return mail_server[0], notifications_email

        # 3. Take the first mail server without "from_filter" because
        # nothing else has been found... Will spoof the FROM because
        # we have no other choices
        mail_server = mail_servers.filtered(lambda m: not m.from_filter)
        if mail_server:
            return mail_server[0], email_from

        # 4. Return the first mail server even if it was configured for another domain
        if mail_servers:
            return mail_servers[0], email_from

        # 5: SMTP config in odoo-bin arguments
        from_filter = self.env['ir.config_parameter'].sudo().get_param(
            'mail.default.from_filter', tools.config.get('from_filter'))

        if self._match_from_filter(email_from, from_filter):
            return None, email_from

        if notifications_email and self._match_from_filter(notifications_email, from_filter):
            return None, notifications_email

        return None, email_from
Example #6
0
 def _odoo_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,
             'responseStatus': 'needsAction',
         }]
     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.odoo_id(
             self.env)).attendee_ids
     attendees_by_emails = {
         tools.email_normalize(a.email): a
         for a in existing_attendees
     }
     partners = self._get_sync_partner(emails)
     for attendee in zip(emails, partners, google_attendees):
         email = attendee[0]
         if email in attendees_by_emails:
             # Update existing attendees
             attendee_commands += [(1, attendees_by_emails[email].id, {
                 'state':
                 attendee[2].get('responseStatus')
             })]
         else:
             # Create new attendees
             if attendee[2].get('self'):
                 partner = self.env.user.partner_id
             elif attendee[1]:
                 partner = attendee[1]
             else:
                 continue
             attendee_commands += [(0, 0, {
                 'state':
                 attendee[2].get('responseStatus'),
                 'partner_id':
                 partner.id
             })]
             partner_commands += [(4, partner.id)]
             if attendee[2].get('displayName') and not partner.name:
                 partner.name = attendee[2].get('displayName')
     for odoo_attendee in attendees_by_emails.values():
         # Remove old attendees but only if it does not correspond to the current user.
         email = tools.email_normalize(odoo_attendee.email)
         if email not in emails and email != self.env.user.email:
             attendee_commands += [(2, odoo_attendee.id)]
             partner_commands += [(3, odoo_attendee.partner_id.id)]
     return attendee_commands, partner_commands
Example #7
0
    def _match_from_filter(self, email_from, from_filter):
        """Return True is the given email address match the "from_filter" field.

        The from filter can be Falsy (always match),
        a domain name or an full email address.
        """
        if not from_filter:
            return True

        normalized_mail_from = email_normalize(email_from)
        if '@' in from_filter:
            return email_normalize(from_filter) == normalized_mail_from

        return email_domain_extract(normalized_mail_from) == email_domain_normalize(from_filter)
Example #8
0
 def _create_portal_users(self):
     partners_without_user = self.filtered(lambda partner: not partner.user_ids)
     if not partners_without_user:
         return self.env['res.users']
     created_users = self.env['res.users']
     for partner in partners_without_user:
         created_users += self.env['res.users'].with_context(no_reset_password=True).sudo()._create_user_from_template({
             'email': email_normalize(partner.email),
             'login': email_normalize(partner.email),
             'partner_id': partner.id,
             'company_id': self.env.company.id,
             'company_ids': [(6, 0, self.env.company.ids)],
             'active': True,
         })
     return created_users
Example #9
0
    def insert_record(self, request, model, values, custom, meta=None):
        is_lead_model = model.model == 'crm.lead'
        if is_lead_model:
            values_email_normalized = tools.email_normalize(values.get('email_from'))
            visitor_sudo = request.env['website.visitor']._get_visitor_from_request()
            visitor_partner = visitor_sudo.partner_id
            if values_email_normalized and visitor_partner and visitor_partner.email_normalized == values_email_normalized:
                # Here, 'phone' in values has already been formatted, see _handle_website_form.
                values_phone = values.get('phone')
                # We write partner id on crm only if no phone exists on partner or in input,
                # or if both numbers (after formating) are the same. This way we get additional phone
                # if possible, without modifying an existing one. (see inverse function on model crm.lead)
                if values_phone and visitor_partner.phone:
                    if visitor_partner._phone_format(visitor_partner.phone) == values_phone:
                        values['partner_id'] = visitor_partner.id
                else:
                    values['partner_id'] = visitor_partner.id
            if 'company_id' not in values:
                values['company_id'] = request.website.company_id.id
            lang = request.context.get('lang', False)
            values['lang_id'] = values.get('lang_id') or request.env['res.lang']._lang_get_id(lang)

        result = super(WebsiteForm, self).insert_record(request, model, values, custom, meta=meta)

        if is_lead_model and visitor_sudo and result:
            lead_sudo = request.env['crm.lead'].browse(result).sudo()
            if lead_sudo.exists():
                vals = {'lead_ids': [(4, result)]}
                if not visitor_sudo.lead_ids and not visitor_sudo.partner_id:
                    vals['name'] = lead_sudo.contact_name
                visitor_sudo.write(vals)
        return result
Example #10
0
    def res_partners_search(self, search_term, limit=30, **kwargs):
        """
        Used for the plugin search contact functionality where the user types a string query in order to search for
        matching contacts, the string query can either be the name of the contact, it's reference or it's email.
        We choose these fields because these are probably the most interesting fields that the user can perform a
        search on.
        The method returns an array containing the dicts of the matched contacts.
        """

        normalized_email = tools.email_normalize(search_term)

        if normalized_email:
            filter_domain = [('email_normalized', '=', search_term)]
        else:
            filter_domain = ['|', '|', ('display_name', 'ilike', search_term), ('ref', '=', search_term),
                             ('email', 'ilike', search_term)]

        # Search for the partner based on the email.
        # If multiple are found, take the first one.
        partners = request.env['res.partner'].search(filter_domain, limit=limit)

        partners = [
            self._get_partner_data(partner)
            for partner in partners
        ]
        return {"partners": partners}
Example #11
0
    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
        langs = set(valid_partners.mapped('lang')) - {False}
        if len(langs) == 1:
            self = self.with_context(lang=langs.pop())
        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'}
Example #12
0
    def _process_answer(self, mail_channel, message_body):
        """ Method called when the user reacts to the current chatbot.script step.
        For most chatbot.script.step#step_types it simply returns the next chatbot.script.step of
        the script (see '_fetch_next_step').

        Some extra processing is done for steps of type 'question_email' and 'question_phone' where
        we store the user raw answer (the mail message HTML body) into the chatbot.message in order
        to be able to recover it later (see '_chatbot_prepare_customer_values').

        :param mail_channel:
        :param message_body:
        :return: script step to display next
        :rtype: 'chatbot.script.step' """

        self.ensure_one()

        user_text_answer = html2plaintext(message_body)
        if self.step_type == 'question_email' and not email_normalize(user_text_answer):
            # if this error is raised, display an error message but do not go to next step
            raise ValidationError(_('"%s" is not a valid email.', user_text_answer))

        if self.step_type in ['question_email', 'question_phone']:
            chatbot_message = self.env['chatbot.message'].search([
                ('mail_channel_id', '=', mail_channel.id),
                ('script_step_id', '=', self.id),
            ], limit=1)

            if chatbot_message:
                chatbot_message.write({'user_raw_answer': message_body})
                chatbot_message.flush()

        return self._fetch_next_step(mail_channel.chatbot_message_ids.user_script_answer_id)
Example #13
0
    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.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 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'),
                    }
                    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)
Example #14
0
 def _get_sync_partner(self, emails):
     normalized_emails = [
         email_normalize(contact) for contact in emails
         if email_normalize(contact)
     ]
     user_partners = self.env['mail.thread']._mail_search_on_user(
         normalized_emails, extra_domain=[('share', '=', False)])
     partners = [user_partner for user_partner in user_partners]
     remaining = [
         email for email in normalized_emails
         if email not in [partner.email_normalized for partner in partners]
     ]
     if remaining:
         partners += self.env['mail.thread']._mail_find_partner_from_emails(
             remaining, records=self, force_create=True)
     return partners
Example #15
0
    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'}
Example #16
0
    def _find_members(self, email, partner_id):
        """Get all the members record corresponding to the email / partner_id.

        Can be called in batch and return a dictionary
            {'group_id': <mail.group.member>}

        Multiple members might have the same email address, but with different partner
        because there's no unique constraint on the email field of the <res.partner>
        model.

        When a partner is given for the search, return in priority
        - The member whose partner match the given partner
        - The member without partner but whose email match the given email

        When no partner is given for the search, return in priority
        - A member whose email match the given email and has no partner
        - A member whose email match the given email and has partner
        """
        order = 'partner_id ASC'
        domain = [('email_normalized', '=', email_normalize(email))]
        if partner_id:
            domain = expression.OR([
                expression.AND([
                    [('partner_id', '=', False)],
                    domain,
                ]),
                [('partner_id', '=', partner_id)],
            ])
            order = 'partner_id DESC'

        domain = expression.AND([domain, [('mail_group_id', 'in', self.ids)]])
        members_data = self.env['mail.group.member'].sudo().search(domain,
                                                                   order=order)
        return {member.mail_group_id.id: member for member in members_data}
Example #17
0
    def groups_index(self, email='', **kw):
        """View of the group lists. Allow the users to subscribe and unsubscribe."""
        if kw.get('group_id') and kw.get('token'):
            group_id = int(kw.get('group_id'))
            token = kw.get('token')
            group = request.env['mail.group'].browse(group_id).exists().sudo()
            if not group:
                raise werkzeug.exceptions.NotFound()

            if token != group._generate_group_access_token():
                raise werkzeug.exceptions.NotFound()

            mail_groups = group

        else:
            mail_groups = request.env['mail.group'].search([]).sudo()

        if not request.env.user._is_public():
            # Force the email if the user is logged
            email_normalized = request.env.user.email_normalized
            partner_id = request.env.user.partner_id.id
        else:
            email_normalized = tools.email_normalize(email)
            partner_id = None

        members_data = mail_groups._find_members(email_normalized, partner_id)

        return request.render('mail_group.mail_groups', {
            'mail_groups': [{
                'group': group,
                'is_member': bool(members_data.get(group.id, False)),
            } for group in mail_groups],
            'email': email_normalized,
            'is_mail_group_manager': request.env.user.has_group('mail_group.group_mail_group_manager'),
        })
Example #18
0
 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)
Example #19
0
    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'}
Example #20
0
    def res_partners_search(self, search_term, limit=30, **kwargs):
        """
        Used for the plugin search contact functionality where the user types a string query in order to search for
        matching contacts, the string query can either be the name of the contact, it's reference or it's email.
        We choose these fields because these are probably the most interesting fields that the user can perform a
        search on.
        The method returns an array containing the dicts of the matched contacts.
        """

        #In a multi-company environment, the method may return contacts not belonging to the company that the user
        #is connected to, this may result in the user not being able to view the contact in Odoo, while this may happen
        #it is not supported for now and users are encouraged to check if they are connected to the correct company before
        # clicking on a contact.

        normalized_email = tools.email_normalize(search_term)

        if normalized_email:
            filter_domain = [('email_normalized', '=', search_term)]
        else:
            filter_domain = [
                '|', '|', ('display_name', 'ilike', search_term),
                ('ref', '=', search_term), ('email', 'ilike', search_term)
            ]

        # Search for the partner based on the email.
        # If multiple are found, take the first one.
        partners = request.env['res.partner'].search(filter_domain,
                                                     limit=limit)

        partners = [self._get_partner_data(partner) for partner in partners]
        return {"partners": partners}
Example #21
0
    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 not parsed_email and assert_valid_email:
            raise ValueError(
                _('%(email)s is not recognized as a valid email. This is required to create a new customer.'
                  ))
        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

        # We don't want to call `super()` to avoid searching twice on the email
        # Especially when the search `email =ilike` cannot be as efficient as
        # a search on email_normalized with a btree index
        # If you want to override `find_or_create()` your module should depend on `mail`
        create_values = {self._rec_name: parsed_name or parsed_email}
        if parsed_email:  # otherwise keep default_email in context
            create_values['email'] = parsed_email
        return self.create(create_values)
Example #22
0
 def _create_user(self):
     """ create a new user for wizard_user.partner_id
         :returns record of res.users
     """
     return self.env['res.users'].with_context(
         no_reset_password=True)._create_user_from_template({
             'email':
             email_normalize(self.email),
             'login':
             email_normalize(self.email),
             'partner_id':
             self.partner_id.id,
             'company_id':
             self.env.company.id,
             'company_ids': [(6, 0, self.env.company.ids)],
         })
Example #23
0
 def owner(self, env):
     # Owner/organizer could be desynchronised between Google and Odoo.
     # Let userA, userB be two new users (never synced to Google before).
     # UserA creates an event in Odoo (he is the owner) but userB syncs first.
     # There is no way to insert the event into userA's calendar since we don't have
     # any authentication access. The event is therefore inserted into userB's calendar
     # (he is the organizer in Google). The "real" owner (in Odoo) is stored as an
     # extended property. There is currently no support to "transfert" ownership when
     # userA syncs his calendar the first time.
     real_owner_id = self.extendedProperties and self.extendedProperties.get(
         'shared', {}).get('%s_owner_id' % env.cr.dbname)
     try:
         # If we create an event without user_id, the event properties will be 'false'
         # and python will interpret this a a NoneType, that's why we have the 'except TypeError'
         real_owner_id = int(real_owner_id)
     except (ValueError, TypeError):
         real_owner_id = False
     real_owner = real_owner_id and env['res.users'].browse(
         real_owner_id) or env['res.users']
     if real_owner_id and real_owner.exists():
         return real_owner
     elif self.organizer and self.organizer.get('self'):
         return env.user
     elif self.organizer and self.organizer.get('email'):
         # In Google: 1 email = 1 user; but in Odoo several users might have the same email :/
         org_email = email_normalize(self.organizer.get('email'))
         return env['res.users'].search(
             [('email_normalized', '=', org_email)], limit=1)
     else:
         return env['res.users']
Example #24
0
    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
Example #25
0
    def res_partner_get(self,
                        email=None,
                        name=None,
                        partner_id=None,
                        **kwargs):
        """
        returns a partner given it's id or an email and a name.
        In case the partner does not exist, we return partner having an id -1, we also look if an existing company
        matching the contact exists in the database, if none is found a new company is enriched and created automatically

        old route name "/mail_client_extension/partner/get is deprecated as of saas-14.3, it is not needed for newer
        versions of the mail plugin but necessary for supporting older versions, only the route name is deprecated not
        the entire method.
        """

        if not (partner_id or (name and email)):
            return {
                'error':
                _('You need to specify at least the partner_id or the name and the email'
                  )
            }

        if partner_id:
            partner = request.env['res.partner'].browse(partner_id)
            return self._prepare_contact_values(partner)

        normalized_email = tools.email_normalize(email)
        if not normalized_email:
            return {'error': _('Bad Email.')}

        # 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]),
            ('email_normalized', '=', normalized_email)
        ],
                                                    limit=1)

        response = self._prepare_contact_values(partner)

        # if no partner is found in the database, we should also return an empty one having id = -1, otherwise older versions of
        # plugin won't work
        if not response['partner']:
            sender_domain = tools.email_domain_extract(email)
            response['partner'] = {
                'id': -1,
                'email': email,
                'name': name,
                'enrichment_info': None,
            }
            company = self._find_existing_company(normalized_email)
            if not company:  # create and enrich company
                company, enrichment_info = self._create_company_from_iap(
                    sender_domain)
                response['partner']['enrichment_info'] = enrichment_info
            response['partner']['company'] = self._prepare_company_values(
                company)

        return response
Example #26
0
 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.write({'active': False})
     else:
         record = record.create({'email': email, 'active': False})
     return record
Example #27
0
 def write(self, values):
     if 'email' in values:
         email_normalized = email_normalize(values['email'])
         if not email_normalized:
             raise UserError(
                 _('Invalid email address %r', values.get('email')))
         values['email'] = email_normalized
     return super(MailGroupModeration, self).write(values)
Example #28
0
 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.action_unarchive()
     else:
         record = self.create({'email': email})
     return record
Example #29
0
 def create(self, vals_list):
     for values in vals_list:
         email_normalized = email_normalize(values.get('email'))
         if not email_normalized:
             raise UserError(
                 _('Invalid email address %r', values.get('email')))
         values['email'] = email_normalized
     return super(MailGroupModeration, self).create(vals_list)
Example #30
0
 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.write({'active': False})
     else:
         record = record.create({'email': email, 'active': False})
     return record
Example #31
0
    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_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 iap_tools._MAIL_DOMAIN_BLACKLIST:
                            lead.write({'iap_enrich_done': True})
                            lead.message_post_with_view(
                                'crm_iap_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_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()
Example #32
0
 def mail_validate(email):
     global _flanker_lib_warning
     if not _flanker_lib_warning:
         _flanker_lib_warning = True
         _logger.info(
             "The `flanker` Python module is not installed,"
             "so email validation fallback to email_normalize. Use 'pip install flanker' to install it"
         )
     return tools.email_normalize(email)
Example #33
0
File: main.py Project: EdyKend/odoo
 def send_feedback(self, mailing_id, res_id, email, feedback, token):
     mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
     if mailing.exists() and email:
         if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
             return 'unauthorized'
         model = request.env[mailing.mailing_model_real]
         records = model.sudo().search([('email_normalized', '=', tools.email_normalize(email))])
         for record in records:
             record.sudo().message_post(body=_("Feedback from %s: %s" % (email, feedback)))
         return bool(records)
     return 'error'
Example #34
0
 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], tools.pycompat.text_type):
                 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)
Example #35
0
    def update_opt_out(self, email, list_ids, value):
        if len(list_ids) > 0:
            model = self.env['mail.mass_mailing.contact'].with_context(active_test=False)
            records = model.search([('email_normalized', '=', tools.email_normalize(email))])
            opt_out_records = self.env['mail.mass_mailing.list_contact_rel'].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'))))
Example #36
0
    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
Example #37
0
 def write(self, values):
     if 'email' in values:
         values['email'] = tools.email_normalize(values['email'])
     return super(MailBlackList, self).write(values)
Example #38
0
 def _compute_email_normalized(self):
     self._assert_primary_email()
     for record in self:
         record.email_normalized = tools.email_normalize(record[self._primary_email])
Example #39
0
 def _compute_is_email_valid(self):
     for record in self:
         normalized = tools.email_normalize(record.email)
         record.is_email_valid = normalized if not normalized else True
Example #40
0
File: main.py Project: EdyKend/odoo
 def blacklist_check(self, mailing_id, res_id, email, token):
     if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
         return 'unauthorized'
     if email:
         record = request.env['mail.blacklist'].sudo().with_context(active_test=False).search([('email', '=', tools.email_normalize(email))])
         if record['active']:
             return True
         return False
     return 'error'
Example #41
0
File: main.py Project: EdyKend/odoo
    def mailing(self, mailing_id, email=None, res_id=None, token="", **post):
        mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
        if mailing.exists():
            res_id = res_id and int(res_id)
            if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)):
                raise exceptions.AccessDenied()

            if mailing.mailing_model_real == 'mail.mass_mailing.contact':
                # Unsubscribe directly + Let the user choose his subscriptions
                mailing.update_opt_out(email, mailing.contact_list_ids.ids, True)

                contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email_normalized', '=', tools.email_normalize(email))])
                subscription_list_ids = contacts.mapped('subscription_list_ids')
                # In many user are found : if user is opt_out on the list with contact_id 1 but not with contact_id 2,
                # assume that the user is not opt_out on both
                # TODO DBE Fixme : Optimise the following to get real opt_out and opt_in
                opt_out_list_ids = subscription_list_ids.filtered(lambda rel: rel.opt_out).mapped('list_id')
                opt_in_list_ids = subscription_list_ids.filtered(lambda rel: not rel.opt_out).mapped('list_id')
                opt_out_list_ids = set([list.id for list in opt_out_list_ids if list not in opt_in_list_ids])

                unique_list_ids = set([list.list_id.id for list in subscription_list_ids])
                list_ids = request.env['mail.mass_mailing.list'].sudo().browse(unique_list_ids)
                unsubscribed_list = ', '.join(str(list.name) for list in mailing.contact_list_ids if list.is_public)
                return request.render('mass_mailing.page_unsubscribe', {
                    'contacts': contacts,
                    'list_ids': list_ids,
                    'opt_out_list_ids': opt_out_list_ids,
                    'unsubscribed_list': unsubscribed_list,
                    'email': email,
                    'mailing_id': mailing_id,
                    'res_id': res_id,
                    'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param('mass_mailing.show_blacklist_buttons'),
                })
            else:
                opt_in_lists = request.env['mail.mass_mailing.list_contact_rel'].sudo().search([
                    ('contact_id.email_normalized', '=', email),
                    ('opt_out', '=', False)
                ]).mapped('list_id')
                blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
                self._log_blacklist_action(
                    blacklist_rec, mailing_id,
                    _("""Requested blacklisting via unsubscribe link."""))
                return request.render('mass_mailing.page_unsubscribed', {
                    'email': email,
                    'mailing_id': mailing_id,
                    'res_id': res_id,
                    'list_ids': opt_in_lists,
                    'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param(
                        'mass_mailing.show_blacklist_buttons'),
                })
        return request.redirect('/web')
Example #42
0
    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}

            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)
                stat_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 stat when still-born
                if mail_values.get('state') == 'cancel':
                    stat_vals['ignored'] = 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
Example #43
0
 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): formataddr((name, tools.email_normalize(email))) 
         for (name, email) in tools.email_split_tuples(cc_string)}