123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- # -*- coding: utf-8 -*-
- # Part of Odoo. See LICENSE file for full copyright and licensing details.
- import base64
- import json
- import logging
- import requests
- from werkzeug.exceptions import Forbidden
- from odoo import http, tools, _
- from odoo.addons.iap.tools import iap_tools
- from odoo.exceptions import AccessError
- from odoo.http import request
- _logger = logging.getLogger(__name__)
- class MailPluginController(http.Controller):
- @http.route('/mail_client_extension/modules/get', type="json", auth="outlook", csrf=False, cors="*")
- def modules_get(self, **kwargs):
- """
- deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
- for supporting older versions
- """
- return {'modules': ['contacts', 'crm']}
- @http.route('/mail_plugin/partner/enrich_and_create_company',
- type="json", auth="outlook", cors="*")
- def res_partner_enrich_and_create_company(self, partner_id):
- """
- Route used when the user clicks on the create and enrich partner button
- it will try to find a company using IAP, if a company is found
- the enriched company will then be created in the database
- """
- partner = request.env['res.partner'].browse(partner_id).exists()
- if not partner:
- return {'error': _("This partner does not exist")}
- if partner.parent_id:
- return {'error': _("The partner already has a company related to him")}
- normalized_email = partner.email_normalized
- if not normalized_email:
- return {'error': _('The email of this contact is not valid and we can not enrich it')}
- company, enrichment_info = self._create_company_from_iap(normalized_email)
- if company:
- partner.write({'parent_id': company})
- return {
- 'enrichment_info': enrichment_info,
- 'company': self._get_company_data(company),
- }
- @http.route('/mail_plugin/partner/enrich_and_update_company', type='json', auth='outlook', cors='*')
- def res_partner_enrich_and_update_company(self, partner_id):
- """
- Enriches an existing company using IAP
- """
- partner = request.env['res.partner'].browse(partner_id).exists()
- if not partner:
- return {'error': _("This partner does not exist")}
- if not partner.is_company:
- return {'error': 'Contact must be a company'}
- normalized_email = partner.email_normalized
- if not normalized_email:
- return {'error': 'The email of this contact is not valid and we can not enrich it'}
- domain = tools.email_domain_extract(normalized_email)
- iap_data = self._iap_enrich(domain)
- if 'enrichment_info' in iap_data: # means that an issue happened with the enrichment request
- return {
- 'enrichment_info': iap_data['enrichment_info'],
- 'company': self._get_company_data(partner),
- }
- phone_numbers = iap_data.get('phone_numbers')
- partner_values = {}
- if not partner.phone and phone_numbers:
- partner_values.update({'phone': phone_numbers[0]})
- if not partner.iap_enrich_info:
- partner_values.update({'iap_enrich_info': json.dumps(iap_data)})
- if not partner.image_128:
- logo_url = iap_data.get('logo')
- if logo_url:
- try:
- response = requests.get(logo_url, timeout=2)
- if response.ok:
- partner_values.update({'image_1920': base64.b64encode(response.content)})
- except Exception:
- pass
- model_fields_to_iap_mapping = {
- 'street': 'street_name',
- 'city': 'city',
- 'zip': 'postal_code',
- 'website': 'domain',
- }
- # only update keys for which we dont have values yet
- partner_values.update({
- model_field: iap_data.get(iap_key)
- for model_field, iap_key in model_fields_to_iap_mapping.items() if not partner[model_field]
- })
- partner.write(partner_values)
- partner.message_post_with_view(
- 'iap_mail.enrich_company',
- values=iap_data,
- subtype_id=request.env.ref('mail.mt_note').id,
- )
- return {
- 'enrichment_info': {'type': 'company_updated'},
- 'company': self._get_company_data(partner),
- }
- @http.route(['/mail_client_extension/partner/get', '/mail_plugin/partner/get']
- , type="json", auth="outlook", cors="*")
- 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).exists()
- return self._get_contact_data(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._get_contact_data(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']:
- response['partner'] = {
- 'id': -1,
- 'email': email,
- 'name': name,
- 'enrichment_info': None,
- }
- company = self._find_existing_company(normalized_email)
- can_create_partner = request.env['res.partner'].check_access_rights('create', raise_exception=False)
- if not company and can_create_partner: # create and enrich company
- company, enrichment_info = self._create_company_from_iap(normalized_email)
- response['partner']['enrichment_info'] = enrichment_info
- response['partner']['company'] = self._get_company_data(company)
- return response
- @http.route('/mail_plugin/partner/search', type="json", auth="outlook", cors="*")
- 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', 'ilike', 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}
- @http.route(['/mail_client_extension/partner/create', '/mail_plugin/partner/create'],
- type="json", auth="outlook", cors="*")
- def res_partner_create(self, email, name, company):
- """
- params email: email of the new partner
- params name: name of the new partner
- params company: parent company id of the new partner
- """
- # old route name "/mail_client_extension/partner/create is deprecated as of saas-14.3,it is not needed for newer
- # versions of the mail plugin but necessary for supporting older versions
- # TODO search the company again instead of relying on the one provided here?
- # Create the partner if needed.
- partner_info = {
- 'name': name,
- 'email': email,
- }
- #see if the partner has a parent company
- if company and company > -1:
- partner_info['parent_id'] = company
- partner = request.env['res.partner'].create(partner_info)
- response = {'id': partner.id}
- return response
- @http.route('/mail_plugin/log_mail_content', type="json", auth="outlook", cors="*")
- def log_mail_content(self, model, res_id, message, attachments=None):
- """Log the email on the given record.
- :param model: Model of the record on which we want to log the email
- :param res_id: ID of the record
- :param message: Body of the email
- :param attachments: List of attachments of the email.
- List of tuple: (filename, base 64 encoded content)
- """
- if model not in self._mail_content_logging_models_whitelist():
- raise Forbidden()
- if attachments:
- attachments = [
- (name, base64.b64decode(content))
- for name, content in attachments
- ]
- request.env[model].browse(res_id).message_post(body=message, attachments=attachments)
- return True
- @http.route('/mail_plugin/get_translations', type="json", auth="outlook", cors="*")
- def get_translations(self):
- return self._prepare_translations()
- def _iap_enrich(self, domain):
- """
- Returns enrichment data for a given domain, in case an error happens the response will
- contain an enrichment_info key explaining what went wrong
- """
- if domain in iap_tools._MAIL_DOMAIN_BLACKLIST:
- # Can not enrich the provider domain names (gmail.com; outlook.com, etc)
- return {'enrichment_info': {'type': 'missing_data'}}
- enriched_data = {}
- try:
- response = request.env['iap.enrich.api']._request_enrich({domain: domain}) # The key doesn't matter
- except iap_tools.InsufficientCreditError:
- enriched_data['enrichment_info'] = {'type': 'insufficient_credit', 'info': request.env['iap.account'].get_credits_url('reveal')}
- except Exception:
- enriched_data["enrichment_info"] = {'type': 'other', 'info': 'Unknown reason'}
- else:
- enriched_data = response.get(domain)
- if not enriched_data:
- enriched_data = {'enrichment_info': {'type': 'no_data', 'info': 'The enrichment API found no data for the email provided.'}}
- return enriched_data
- def _find_existing_company(self, email):
- """Find the company corresponding to the given domain and its IAP cache.
- :param email: Email of the company we search
- :return: The partner corresponding to the company
- """
- search = self._get_iap_search_term(email)
- partner_iap = request.env["res.partner.iap"].sudo().search([("iap_search_domain", "=", search)], limit=1)
- if partner_iap:
- return partner_iap.partner_id.sudo(False)
- return request.env["res.partner"].search([("is_company", "=", True), ("email_normalized", "=ilike", "%" + search)], limit=1)
- def _get_company_data(self, company):
- if not company:
- return {'id': -1}
- try:
- company.check_access_rights('read')
- company.check_access_rule('read')
- except AccessError:
- return {'id': company.id, 'name': _('No Access')}
- fields_list = ['id', 'name', 'phone', 'mobile', 'email', 'website']
- company_values = dict((fname, company[fname]) for fname in fields_list)
- company_values['address'] = {'street': company.street,
- 'city': company.city,
- 'zip': company.zip,
- 'country': company.country_id.name if company.country_id else ''}
- company_values['additionalInfo'] = json.loads(company.iap_enrich_info) if company.iap_enrich_info else {}
- company_values['image'] = company.image_1920
- return company_values
- def _create_company_from_iap(self, email):
- domain = tools.email_domain_extract(email)
- iap_data = self._iap_enrich(domain)
- if 'enrichment_info' in iap_data:
- return None, iap_data['enrichment_info']
- phone_numbers = iap_data.get('phone_numbers')
- emails = iap_data.get('email')
- new_company_info = {
- 'is_company': True,
- 'name': iap_data.get("name") or domain,
- 'street': iap_data.get("street_name"),
- 'city': iap_data.get("city"),
- 'zip': iap_data.get("postal_code"),
- 'phone': phone_numbers[0] if phone_numbers else None,
- 'website': iap_data.get("domain"),
- 'email': emails[0] if emails else None
- }
- logo_url = iap_data.get('logo')
- if logo_url:
- try:
- response = requests.get(logo_url, timeout=2)
- if response.ok:
- new_company_info['image_1920'] = base64.b64encode(response.content)
- except Exception as e:
- _logger.warning('Download of image for new company %s failed, error %s', new_company_info.name, e)
- if iap_data.get('country_code'):
- country = request.env['res.country'].search([('code', '=', iap_data['country_code'].upper())])
- if country:
- new_company_info['country_id'] = country.id
- if iap_data.get('state_code'):
- state = request.env['res.country.state'].search([
- ('code', '=', iap_data['state_code']),
- ('country_id', '=', country.id)
- ])
- if state:
- new_company_info['state_id'] = state.id
- new_company_info.update({
- 'iap_search_domain': self._get_iap_search_term(email),
- 'iap_enrich_info': json.dumps(iap_data),
- })
- new_company = request.env['res.partner'].create(new_company_info)
- new_company.message_post_with_view(
- 'iap_mail.enrich_company',
- values=iap_data,
- subtype_id=request.env.ref('mail.mt_note').id,
- )
- return new_company, {'type': 'company_created'}
- def _get_partner_data(self, partner):
- fields_list = ['id', 'name', 'email', 'phone', 'mobile', 'is_company']
- partner_values = dict((fname, partner[fname]) for fname in fields_list)
- partner_values['image'] = partner.image_128
- partner_values['title'] = partner.function
- partner_values['enrichment_info'] = None
- try:
- partner.check_access_rights('write')
- partner.check_access_rule('write')
- partner_values['can_write_on_partner'] = True
- except AccessError:
- partner_values['can_write_on_partner'] = False
- if not partner_values['name']:
- # Always ensure that the partner has a name
- name, email = request.env['res.partner']._parse_partner_name(
- partner_values['email'])
- partner_values['name'] = name or email
- return partner_values
- def _get_contact_data(self, partner):
- """
- method used to return partner related values, it can be overridden by other modules if extra information have to
- be returned with the partner (e.g., leads, ...)
- """
- if partner:
- partner_response = self._get_partner_data(partner)
- if partner.company_type == 'company':
- partner_response['company'] = self._get_company_data(partner)
- elif partner.parent_id:
- partner_response['company'] = self._get_company_data(partner.parent_id)
- else:
- partner_response['company'] = self._get_company_data(None)
- else: # no partner found
- partner_response = {}
- return {
- 'partner': partner_response,
- 'user_companies': request.env.user.company_ids.ids,
- 'can_create_partner': request.env['res.partner'].check_access_rights(
- 'create', raise_exception=False),
- }
- def _mail_content_logging_models_whitelist(self):
- """
- Returns all models that emails can be logged to and that can be used by the "log_mail_content" method,
- it can be overridden by sub modules in order to whitelist more models
- """
- return ['res.partner']
- def _get_iap_search_term(self, email):
- """Return the domain or the email depending if the domain is blacklisted or not.
- So if the domain is blacklisted, we search based on the entire email address
- (e.g. asbl@gmail.com). But if the domain is not blacklisted, we search based on
- the domain (e.g. bob@sncb.be -> sncb.be)
- """
- domain = tools.email_domain_extract(email)
- return ("@" + domain) if domain not in iap_tools._MAIL_DOMAIN_BLACKLIST else email
- def _translation_modules_whitelist(self):
- """
- Returns the list of modules to be translated
- Other mail plugin modules have to override this method to include their module names
- """
- return ['mail_plugin']
- def _prepare_translations(self):
- lang = request.env['res.users'].browse(request.uid).lang
- translations_per_module = request.env["ir.http"].get_translations_for_webclient(
- self._translation_modules_whitelist(), lang)[0]
- translations_dict = {}
- for module in self._translation_modules_whitelist():
- translations = translations_per_module.get(module, {})
- messages = translations.get('messages', {})
- for message in messages:
- translations_dict.update({message['id']: message['string']})
- return translations_dict
|