mirror of
https://github.com/bringout/oca-ocb-technical.git
synced 2026-04-22 03:32:00 +02:00
19.0 vanilla
This commit is contained in:
parent
5faf7397c5
commit
2696f14ed7
721 changed files with 220375 additions and 91221 deletions
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import iap_account
|
||||
from . import res_config_settings
|
||||
from . import iap_enrich_api
|
||||
from . import iap_service
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import secrets
|
||||
import uuid
|
||||
import werkzeug.urls
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.modules import module
|
||||
from odoo.tools import get_lang
|
||||
from odoo.tools.urls import urljoin as url_join
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -15,16 +20,119 @@ DEFAULT_ENDPOINT = 'https://iap.odoo.com'
|
|||
|
||||
class IapAccount(models.Model):
|
||||
_name = 'iap.account'
|
||||
_rec_name = 'service_name'
|
||||
_description = 'IAP Account'
|
||||
|
||||
service_name = fields.Char()
|
||||
account_token = fields.Char(default=lambda s: uuid.uuid4().hex)
|
||||
name = fields.Char()
|
||||
service_id = fields.Many2one('iap.service', required=True)
|
||||
service_name = fields.Char(related='service_id.technical_name')
|
||||
service_locked = fields.Boolean(default=False) # If True, the service can't be edited anymore
|
||||
description = fields.Char(related='service_id.description')
|
||||
account_token = fields.Char(
|
||||
default=lambda s: uuid.uuid4().hex,
|
||||
help="Account token is your authentication key for this service. Do not share it.",
|
||||
size=43,
|
||||
copy=False,
|
||||
groups="base.group_system",
|
||||
)
|
||||
company_ids = fields.Many2many('res.company')
|
||||
|
||||
# Dynamic fields, which are received from iap server and set when loading the view
|
||||
balance = fields.Char(readonly=True)
|
||||
warning_threshold = fields.Float("Email Alert Threshold")
|
||||
warning_user_ids = fields.Many2many('res.users', string="Email Alert Recipients")
|
||||
state = fields.Selection([('banned', 'Banned'), ('registered', "Registered"), ('unregistered', "Unregistered")], readonly=True)
|
||||
|
||||
@api.constrains('warning_threshold', 'warning_user_ids')
|
||||
def validate_warning_alerts(self):
|
||||
for account in self:
|
||||
if account.warning_threshold < 0:
|
||||
raise UserError(_("Please set a positive email alert threshold."))
|
||||
users_with_no_email = [user.name for user in self.warning_user_ids if not user.email]
|
||||
if users_with_no_email:
|
||||
raise UserError(_(
|
||||
"One of the email alert recipients doesn't have an email address set. Users: %s",
|
||||
",".join(users_with_no_email),
|
||||
))
|
||||
|
||||
def web_read(self, *args, **kwargs):
|
||||
if not self.env.context.get('disable_iap_fetch'):
|
||||
self._get_account_information_from_iap()
|
||||
return super().web_read(*args, **kwargs)
|
||||
|
||||
def web_save(self, *args, **kwargs):
|
||||
return super(IapAccount, self.with_context(disable_iap_fetch=True)).web_save(*args, **kwargs)
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
if (
|
||||
not self.env.context.get('disable_iap_update')
|
||||
and any(warning_attribute in vals for warning_attribute in ('warning_threshold', 'warning_user_ids'))
|
||||
):
|
||||
route = '/iap/1/update-warning-email-alerts'
|
||||
endpoint = iap_tools.iap_get_endpoint(self.env)
|
||||
url = url_join(endpoint, route)
|
||||
for account in self:
|
||||
data = {
|
||||
'account_token': account.sudo().account_token,
|
||||
'warning_threshold': account.warning_threshold,
|
||||
'warning_emails': [{
|
||||
'email': user.email,
|
||||
'lang_code': user.lang or get_lang(self.env).code,
|
||||
} for user in account.warning_user_ids],
|
||||
}
|
||||
try:
|
||||
iap_tools.iap_jsonrpc(url=url, params=data)
|
||||
except AccessError as e:
|
||||
_logger.warning("Update of the warning email configuration has failed: %s", str(e))
|
||||
return res
|
||||
|
||||
def _get_account_information_from_iap(self):
|
||||
# During testing, we don't want to call the iap server
|
||||
if module.current_test:
|
||||
return
|
||||
route = '/iap/1/get-accounts-information'
|
||||
endpoint = iap_tools.iap_get_endpoint(self.env)
|
||||
url = url_join(endpoint, route)
|
||||
params = {
|
||||
'iap_accounts': [{
|
||||
'token': account.sudo().account_token,
|
||||
'service': account.service_id.technical_name,
|
||||
} for account in self if account.service_id],
|
||||
'dbuuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
||||
}
|
||||
try:
|
||||
accounts_information = iap_tools.iap_jsonrpc(url=url, params=params)
|
||||
except AccessError as e:
|
||||
_logger.warning("Fetch of the IAP accounts information has failed: %s", str(e))
|
||||
return
|
||||
|
||||
for token, information in accounts_information.items():
|
||||
information.pop('link_to_service_page', None)
|
||||
accounts = self.filtered(lambda acc: secrets.compare_digest(acc.sudo().account_token, token))
|
||||
|
||||
for account in accounts:
|
||||
# Default rounding of 4 decimal places to avoid large decimals
|
||||
balance_amount = round(information['balance'], None if account.service_id.integer_balance else 4)
|
||||
balance = f"{balance_amount} {account.service_id.unit_name or ''}"
|
||||
|
||||
account_info = self._get_account_info(account, balance, information)
|
||||
account.with_context(disable_iap_update=True, tracking_disable=True).write(account_info)
|
||||
|
||||
def _get_account_info(self, account_id, balance, information):
|
||||
return {
|
||||
'balance': balance,
|
||||
'warning_threshold': information['warning_threshold'],
|
||||
'state': information['registered'],
|
||||
'service_locked': True, # The account exist on IAP, prevent the edition of the service
|
||||
}
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
accounts = super().create(vals_list)
|
||||
for account in accounts:
|
||||
if not account.name:
|
||||
account.name = account.service_id.name
|
||||
|
||||
if self.env['ir.config_parameter'].sudo().get_param('database.is_neutralized'):
|
||||
# Disable new accounts on a neutralized database
|
||||
for account in accounts:
|
||||
|
|
@ -40,7 +148,7 @@ class IapAccount(models.Model):
|
|||
('company_ids', '=', False)
|
||||
]
|
||||
accounts = self.search(domain, order='id desc')
|
||||
accounts_without_token = accounts.filtered(lambda acc: not acc.account_token)
|
||||
accounts_without_token = accounts.filtered(lambda acc: not acc.sudo().account_token)
|
||||
if accounts_without_token:
|
||||
with self.pool.cursor() as cr:
|
||||
# In case of a further error that will rollback the database, we should
|
||||
|
|
@ -53,6 +161,13 @@ class IapAccount(models.Model):
|
|||
IapAccount.search(domain + [('account_token', '=', False)]).sudo().unlink()
|
||||
accounts = accounts - accounts_without_token
|
||||
if not accounts:
|
||||
service = self.env['iap.service'].search([('technical_name', '=', service_name)], limit=1)
|
||||
if not service:
|
||||
raise UserError(self.env._("No service exists with the provided technical name"))
|
||||
if module.current_test:
|
||||
# During testing, we don't want to commit the creation of a new IAP account to the database
|
||||
return self.sudo().create({'service_id': service.id})
|
||||
|
||||
with self.pool.cursor() as cr:
|
||||
# Since the account did not exist yet, we will encounter a NoCreditError,
|
||||
# which is going to rollback the database and undo the account creation,
|
||||
|
|
@ -65,10 +180,10 @@ class IapAccount(models.Model):
|
|||
if not account:
|
||||
if not force_create:
|
||||
return account
|
||||
account = IapAccount.create({'service_name': service_name})
|
||||
account = IapAccount.create({'service_id': service.id})
|
||||
# fetch 'account_token' into cache with this cursor,
|
||||
# as self's cursor cannot see this account
|
||||
account_token = account.account_token
|
||||
account_token = account.sudo().account_token
|
||||
account = self.browse(account.id)
|
||||
self.env.cache.set(account, IapAccount._fields['account_token'], account_token)
|
||||
return account
|
||||
|
|
@ -78,63 +193,55 @@ class IapAccount(models.Model):
|
|||
return accounts[0]
|
||||
|
||||
@api.model
|
||||
def get_credits_url(self, service_name, base_url='', credit=0, trial=False):
|
||||
""" Called notably by ajax crash manager, buy more widget, partner_autocomplete, sanilmail. """
|
||||
def get_account_id(self, service_name):
|
||||
return self.get(service_name).id
|
||||
|
||||
@api.model
|
||||
def get_credits_url(self, service_name, account_token=None):
|
||||
""" Called notably by: buy more widget, partner_autocomplete, snailmail, ... """
|
||||
dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
|
||||
if not base_url:
|
||||
endpoint = iap_tools.iap_get_endpoint(self.env)
|
||||
route = '/iap/1/credit'
|
||||
base_url = endpoint + route
|
||||
account_token = self.get(service_name).account_token
|
||||
endpoint = iap_tools.iap_get_endpoint(self.env)
|
||||
route = '/iap/1/credit'
|
||||
base_url = url_join(endpoint, route)
|
||||
account_token = account_token or self.get(service_name).sudo().account_token
|
||||
hashed_account_token = self._hash_iap_token(account_token)
|
||||
d = {
|
||||
'dbuuid': dbuuid,
|
||||
'service_name': service_name,
|
||||
'account_token': account_token,
|
||||
'credit': credit,
|
||||
'account_token': hashed_account_token,
|
||||
'hashed': 1,
|
||||
}
|
||||
if trial:
|
||||
d.update({'trial': trial})
|
||||
return '%s?%s' % (base_url, werkzeug.urls.url_encode(d))
|
||||
|
||||
@api.model
|
||||
def get_account_url(self):
|
||||
""" Called only by res settings """
|
||||
route = '/iap/services'
|
||||
endpoint = iap_tools.iap_get_endpoint(self.env)
|
||||
all_accounts = self.search([
|
||||
'|',
|
||||
('company_ids', '=', self.env.company.id),
|
||||
('company_ids', '=', False),
|
||||
])
|
||||
def _hash_iap_token(self, key):
|
||||
# disregard possible suffix
|
||||
key = (key or '').split('+')[0]
|
||||
if not key:
|
||||
raise UserError(_('The IAP token provided is invalid or empty.'))
|
||||
return hashlib.sha1(key.encode('utf-8')).hexdigest()
|
||||
|
||||
global_account_per_service = {
|
||||
account.service_name: account.account_token
|
||||
for account in all_accounts.filtered(lambda acc: not acc.company_ids)
|
||||
def action_buy_credits(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.env['iap.account'].get_credits_url(
|
||||
account_token=self.sudo().account_token,
|
||||
service_name=self.service_name,
|
||||
),
|
||||
}
|
||||
company_account_per_service = {
|
||||
account.service_name: account.account_token
|
||||
for account in all_accounts.filtered(lambda acc: acc.company_ids)
|
||||
}
|
||||
|
||||
# Prioritize company specific accounts over global accounts
|
||||
account_per_service = {**global_account_per_service, **company_account_per_service}
|
||||
|
||||
parameters = {'tokens': list(account_per_service.values())}
|
||||
|
||||
return '%s?%s' % (endpoint + route, werkzeug.urls.url_encode(parameters))
|
||||
|
||||
@api.model
|
||||
def get_config_account_url(self):
|
||||
""" Called notably by ajax partner_autocomplete. """
|
||||
account = self.env['iap.account'].get('partner_autocomplete')
|
||||
action = self.env.ref('iap.iap_account_action')
|
||||
menu = self.env.ref('iap.iap_account_menu')
|
||||
no_one = self.user_has_groups('base.group_no_one')
|
||||
if not self.env.user.has_group('base.group_no_one'):
|
||||
return False
|
||||
if account:
|
||||
url = "/web#id=%s&action=%s&model=iap.account&view_type=form&menu_id=%s" % (account.id, action.id, menu.id)
|
||||
url = f"/odoo/action-iap.iap_account_action/{account.id}?menu_id={menu.id}"
|
||||
else:
|
||||
url = "/web#action=%s&model=iap.account&view_type=form&menu_id=%s" % (action.id, menu.id)
|
||||
return no_one and url
|
||||
url = f"/odoo/action-iap.iap_account_action?menu_id={menu.id}"
|
||||
return url
|
||||
|
||||
@api.model
|
||||
def get_credits(self, service_name):
|
||||
|
|
@ -144,15 +251,15 @@ class IapAccount(models.Model):
|
|||
if account:
|
||||
route = '/iap/1/balance'
|
||||
endpoint = iap_tools.iap_get_endpoint(self.env)
|
||||
url = endpoint + route
|
||||
url = url_join(endpoint, route)
|
||||
params = {
|
||||
'dbuuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
||||
'account_token': account.account_token,
|
||||
'account_token': account.sudo().account_token,
|
||||
'service_name': service_name,
|
||||
}
|
||||
try:
|
||||
credit = iap_tools.iap_jsonrpc(url=url, params=params)
|
||||
except Exception as e:
|
||||
except AccessError as e:
|
||||
_logger.info('Get credit error : %s', str(e))
|
||||
credit = -1
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from odoo import models, api
|
|||
from odoo.addons.iap.tools import iap_tools
|
||||
|
||||
|
||||
class IapEnrichAPI(models.AbstractModel):
|
||||
class IapEnrichApi(models.AbstractModel):
|
||||
_name = 'iap.enrich.api'
|
||||
_description = 'IAP Lead Enrichment API'
|
||||
_DEFAULT_ENDPOINT = 'https://iap-services.odoo.com'
|
||||
|
|
@ -14,7 +14,7 @@ class IapEnrichAPI(models.AbstractModel):
|
|||
def _contact_iap(self, local_endpoint, params):
|
||||
account = self.env['iap.account'].get('reveal')
|
||||
dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
|
||||
params['account_token'] = account.account_token
|
||||
params['account_token'] = account.sudo().account_token
|
||||
params['dbuuid'] = dbuuid
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('enrich.endpoint', self._DEFAULT_ENDPOINT)
|
||||
return iap_tools.iap_jsonrpc(base_url + local_endpoint, params=params, timeout=300)
|
||||
|
|
|
|||
19
odoo-bringout-oca-ocb-iap/iap/models/iap_service.py
Normal file
19
odoo-bringout-oca-ocb-iap/iap/models/iap_service.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class IapService(models.Model):
|
||||
_name = 'iap.service'
|
||||
_description = 'IAP Service'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
technical_name = fields.Char(readonly=True, required=True)
|
||||
description = fields.Char(required=True, translate=True)
|
||||
unit_name = fields.Char(required=True, translate=True)
|
||||
integer_balance = fields.Boolean(required=True)
|
||||
|
||||
_unique_technical_name = models.Constraint(
|
||||
'UNIQUE(technical_name)',
|
||||
'Only one service can exist with a specific technical_name',
|
||||
)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
@api.model
|
||||
def _redirect_to_iap_account(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.env['iap.account'].get_account_url(),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue