258 lines
11 KiB
Python
258 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
import threading
|
|
import uuid
|
|
import werkzeug.urls
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.addons.iap.tools import iap_tools
|
|
from odoo.exceptions import AccessError
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
DEFAULT_ENDPOINT = 'https://iap.odoo.com'
|
|
|
|
|
|
class IapAccount(models.Model):
|
|
_name = 'iap.account'
|
|
_rec_name = 'service_name'
|
|
_description = 'IAP Account'
|
|
|
|
name = fields.Char()
|
|
service_name = fields.Char(readonly=True)
|
|
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)
|
|
company_ids = fields.Many2many('res.company')
|
|
account_info_id = fields.Many2one(
|
|
'iap.account.info', compute='_compute_info', inverse='_inverse_info', search='_search_info')
|
|
account_info_ids = fields.One2many(
|
|
'iap.account.info', 'account_id',
|
|
string="Accounts from IAP")
|
|
balance = fields.Char(compute='_compute_balance')
|
|
description = fields.Char(related='account_info_id.description')
|
|
warn_me = fields.Boolean(
|
|
related='account_info_id.warn_me',
|
|
help="We will send you an email when your balance gets below that threshold",
|
|
readonly=False)
|
|
warning_threshold = fields.Float(related='account_info_id.warning_threshold', readonly=False)
|
|
warning_email = fields.Char(related='account_info_id.warning_email', readonly=False)
|
|
show_token = fields.Boolean()
|
|
|
|
@api.model
|
|
def get_view(self, view_id=None, view_type='form', **kwargs):
|
|
res = super().get_view(view_id, view_type, **kwargs)
|
|
if view_type == 'tree':
|
|
self.env['iap.account'].get_services()
|
|
return res
|
|
|
|
@api.depends('account_info_ids')
|
|
def _compute_info(self):
|
|
for account in self:
|
|
if account.account_info_ids:
|
|
account.account_info_id = account.account_info_ids[-1]
|
|
|
|
@api.depends('account_info_id')
|
|
def _compute_balance(self):
|
|
for account in self:
|
|
account.balance = f'{account.account_info_id.balance} {account.account_info_id.unit_name}' if account.account_info_id else "0 Credits"
|
|
|
|
def _inverse_info(self):
|
|
for account in self:
|
|
if account.account_info_ids:
|
|
# delete previous reference
|
|
account_info = account.env['iap.account.info'].browse(account.account_info_ids[0].id)
|
|
account_info.account_id = False
|
|
# set new reference
|
|
account.account_info_id.account_id = account
|
|
|
|
def _search_info(self, operator, value):
|
|
return []
|
|
|
|
def write(self, values):
|
|
res = super(IapAccount, self).write(values)
|
|
iap_edits = ['warn_me', 'warning_threshold', 'warning_email']
|
|
if any(edited_attribute in values for edited_attribute in iap_edits):
|
|
try:
|
|
route = '/iap/update-warning-odoo'
|
|
endpoint = iap_tools.iap_get_endpoint(self.env)
|
|
url = endpoint + route
|
|
data = {
|
|
'account_token': self.mapped('account_token')[0],
|
|
'dbuuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
|
'warn_me': values.get('warn_me'),
|
|
'warning_threshold': values.get('warning_threshold'),
|
|
'warning_email': values.get('warning_email'),
|
|
}
|
|
iap_tools.iap_jsonrpc(url=url, params=data)
|
|
except AccessError as e:
|
|
_logger.warning('Save service error : %s', str(e))
|
|
return res
|
|
|
|
def get_services(self):
|
|
try:
|
|
route = '/iap/services-token'
|
|
endpoint = iap_tools.iap_get_endpoint(self.env)
|
|
url = endpoint + route
|
|
account_tokens = self.env['iap.account'].sudo().search([]).mapped('account_token')
|
|
params = {
|
|
'dbuuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
|
'iap_accounts': account_tokens,
|
|
}
|
|
services = iap_tools.iap_jsonrpc(url=url, params=params)
|
|
for service in services:
|
|
account_id = self.env['iap.account'].sudo().search(
|
|
[('account_token', '=', service['account_token'])]).ids[0]
|
|
service['account_id'] = account_id
|
|
self.env['iap.account.info'].create(service)
|
|
except AccessError as e:
|
|
_logger.warning('Get services error : %s', str(e))
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
accounts = super().create(vals_list)
|
|
if self.env['ir.config_parameter'].sudo().get_param('database.is_neutralized'):
|
|
# Disable new accounts on a neutralized database
|
|
for account in accounts:
|
|
account.account_token = f"{account.account_token.split('+')[0]}+disabled"
|
|
return accounts
|
|
|
|
@api.model
|
|
def get(self, service_name, force_create=True):
|
|
domain = [
|
|
('service_name', '=', service_name),
|
|
'|',
|
|
('company_ids', 'in', self.env.companies.ids),
|
|
('company_ids', '=', False)
|
|
]
|
|
accounts = self.search(domain, order='id desc')
|
|
accounts_without_token = accounts.filtered(lambda acc: not acc.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
|
|
# use a different SQL cursor to avoid undo the accounts deletion.
|
|
|
|
# Flush the pending operations to avoid a deadlock.
|
|
self.env.flush_all()
|
|
IapAccount = self.with_env(self.env(cr=cr))
|
|
# Need to use sudo because regular users do not have delete right
|
|
IapAccount.search(domain + [('account_token', '=', False)]).sudo().unlink()
|
|
accounts = accounts - accounts_without_token
|
|
if not accounts:
|
|
if hasattr(threading.current_thread(), 'testing') and threading.current_thread().testing:
|
|
# During testing, we don't want to commit the creation of a new IAP account to the database
|
|
return self.create({'service_name': service_name})
|
|
|
|
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,
|
|
# preventing the process to continue any further.
|
|
|
|
# Flush the pending operations to avoid a deadlock.
|
|
self.env.flush_all()
|
|
IapAccount = self.with_env(self.env(cr=cr))
|
|
account = IapAccount.search(domain, order='id desc', limit=1)
|
|
if not account:
|
|
if not force_create:
|
|
return account
|
|
account = IapAccount.create({'service_name': service_name})
|
|
# fetch 'account_token' into cache with this cursor,
|
|
# as self's cursor cannot see this account
|
|
account_token = account.account_token
|
|
account = self.browse(account.id)
|
|
self.env.cache.set(account, IapAccount._fields['account_token'], account_token)
|
|
return account
|
|
accounts_with_company = accounts.filtered(lambda acc: acc.company_ids)
|
|
if accounts_with_company:
|
|
return accounts_with_company[0]
|
|
return accounts[0]
|
|
|
|
@api.model
|
|
def get_credits_url(self, service_name, base_url='', credit=0, trial=False, account_token=False):
|
|
""" Called notably by ajax crash manager, buy more widget, partner_autocomplete, sanilmail. """
|
|
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
|
|
if not account_token:
|
|
account_token = self.get(service_name).account_token
|
|
d = {
|
|
'dbuuid': dbuuid,
|
|
'service_name': service_name,
|
|
'account_token': account_token,
|
|
'credit': credit,
|
|
}
|
|
if trial:
|
|
d.update({'trial': trial})
|
|
return '%s?%s' % (base_url, werkzeug.urls.url_encode(d))
|
|
|
|
def action_buy_credits(self):
|
|
for account in self:
|
|
return {
|
|
'type': 'ir.actions.act_url',
|
|
'url': self.env['iap.account'].get_credits_url(
|
|
account_token=account.account_token,
|
|
service_name=account.service_name,
|
|
),
|
|
}
|
|
|
|
def action_toggle_show_token(self):
|
|
for account in self:
|
|
account.show_token = not account.show_token
|
|
|
|
@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 account:
|
|
url = "/web#id=%s&action=%s&model=iap.account&view_type=form&menu_id=%s" % (account.id, action.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
|
|
|
|
@api.model
|
|
def get_credits(self, service_name):
|
|
account = self.get(service_name, force_create=False)
|
|
credit = 0
|
|
|
|
if account:
|
|
route = '/iap/1/balance'
|
|
endpoint = iap_tools.iap_get_endpoint(self.env)
|
|
url = endpoint + route
|
|
params = {
|
|
'dbuuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
|
'account_token': account.account_token,
|
|
'service_name': service_name,
|
|
}
|
|
try:
|
|
credit = iap_tools.iap_jsonrpc(url=url, params=params)
|
|
except AccessError as e:
|
|
_logger.info('Get credit error : %s', str(e))
|
|
credit = -1
|
|
|
|
return credit
|
|
|
|
|
|
class IAPAccountInfo(models.TransientModel):
|
|
_name = 'iap.account.info'
|
|
_description = 'IAP Account Info'
|
|
_transient_max_hours = 1
|
|
|
|
account_id = fields.Many2one('iap.account', string='IAP Account')
|
|
account_token = fields.Char()
|
|
balance = fields.Float(string='Balance', digits=(16, 4), default=0)
|
|
account_uuid_hashed = fields.Char(string='Account UUID')
|
|
service_name = fields.Char(string='Related Service')
|
|
description = fields.Char()
|
|
warn_me = fields.Boolean('Warn me', default=False)
|
|
warning_threshold = fields.Float('Threshold')
|
|
warning_email = fields.Char()
|
|
unit_name = fields.Char(default='Credits')
|