mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 01:52:07 +02:00
18.0 vanilla
This commit is contained in:
parent
d72e748793
commit
0a7ae8db93
337 changed files with 399651 additions and 232598 deletions
|
|
@ -5,20 +5,17 @@ __all__ = [
|
|||
'convert_file', 'convert_sql_import',
|
||||
'convert_csv_import', 'convert_xml_import'
|
||||
]
|
||||
|
||||
import base64
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
import os.path
|
||||
import pprint
|
||||
import re
|
||||
import subprocess
|
||||
import warnings
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
import pytz
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from lxml import etree, builder
|
||||
try:
|
||||
import jingtrang
|
||||
|
|
@ -26,33 +23,22 @@ except ImportError:
|
|||
jingtrang = None
|
||||
|
||||
import odoo
|
||||
from . import pycompat
|
||||
from .config import config
|
||||
from .misc import file_open, file_path, SKIPPED_ELEMENT_TYPES
|
||||
from .translate import _
|
||||
from odoo import SUPERUSER_ID, api
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from .safe_eval import safe_eval as s_eval, pytz, time
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
from .safe_eval import safe_eval as s_eval, pytz, time
|
||||
safe_eval = lambda expr, ctx={}: s_eval(expr, ctx, nocopy=True)
|
||||
|
||||
def safe_eval(expr, ctx={}):
|
||||
return s_eval(expr, ctx, nocopy=True)
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
...
|
||||
|
||||
class RecordDictWrapper(dict):
|
||||
"""
|
||||
Used to pass a record as locals in eval:
|
||||
records do not strictly behave like dict, so we force them to.
|
||||
"""
|
||||
def __init__(self, record):
|
||||
self.record = record
|
||||
def __getitem__(self, key):
|
||||
if key in self.record:
|
||||
return self.record[key]
|
||||
return dict.__getitem__(self, key)
|
||||
|
||||
def _get_idref(self, env, model_str, idref):
|
||||
idref2 = dict(idref,
|
||||
Command=odoo.fields.Command,
|
||||
|
|
@ -90,8 +76,7 @@ def _eval_xml(self, node, env):
|
|||
if node.tag in ('field','value'):
|
||||
t = node.get('type','char')
|
||||
f_model = node.get('model')
|
||||
if node.get('search'):
|
||||
f_search = node.get("search")
|
||||
if f_search := node.get('search'):
|
||||
f_use = node.get("use",'id')
|
||||
f_name = node.get("name")
|
||||
idref2 = {}
|
||||
|
|
@ -110,8 +95,7 @@ def _eval_xml(self, node, env):
|
|||
if isinstance(f_val, tuple):
|
||||
f_val = f_val[0]
|
||||
return f_val
|
||||
a_eval = node.get('eval')
|
||||
if a_eval:
|
||||
if a_eval := node.get('eval'):
|
||||
idref2 = _get_idref(self, env, f_model, self.idref)
|
||||
try:
|
||||
return safe_eval(a_eval, idref2)
|
||||
|
|
@ -144,44 +128,44 @@ def _eval_xml(self, node, env):
|
|||
if t == 'html':
|
||||
return _process("".join(etree.tostring(n, method='html', encoding='unicode') for n in node))
|
||||
|
||||
data = node.text
|
||||
if node.get('file'):
|
||||
with file_open(node.get('file'), 'rb', env=env) as f:
|
||||
if t == 'base64':
|
||||
with file_open(node.get('file'), 'rb', env=env) as f:
|
||||
return base64.b64encode(f.read())
|
||||
|
||||
with file_open(node.get('file'), env=env) as f:
|
||||
data = f.read()
|
||||
else:
|
||||
data = node.text or ''
|
||||
|
||||
if t == 'base64':
|
||||
return base64.b64encode(data)
|
||||
match t:
|
||||
case 'file':
|
||||
path = data.strip()
|
||||
try:
|
||||
file_path(os.path.join(self.module, path))
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(
|
||||
f"No such file or directory: {path!r} in {self.module}"
|
||||
) from None
|
||||
return '%s,%s' % (self.module, path)
|
||||
case 'char':
|
||||
return data
|
||||
case 'int':
|
||||
d = data.strip()
|
||||
if d == 'None':
|
||||
return None
|
||||
return int(d)
|
||||
case 'float':
|
||||
return float(data.strip())
|
||||
case 'list':
|
||||
return [_eval_xml(self, n, env) for n in node.iterchildren('value')]
|
||||
case 'tuple':
|
||||
return tuple(_eval_xml(self, n, env) for n in node.iterchildren('value'))
|
||||
case 'base64':
|
||||
raise ValueError("base64 type is only compatible with file data")
|
||||
case t:
|
||||
raise ValueError(f"Unknown type {t!r}")
|
||||
|
||||
# after that, only text content makes sense
|
||||
data = pycompat.to_text(data)
|
||||
if t == 'file':
|
||||
path = data.strip()
|
||||
try:
|
||||
file_path(os.path.join(self.module, path))
|
||||
except FileNotFoundError:
|
||||
raise IOError("No such file or directory: '%s' in %s" % (
|
||||
path, self.module))
|
||||
return '%s,%s' % (self.module, path)
|
||||
|
||||
if t == 'char':
|
||||
return data
|
||||
|
||||
if t == 'int':
|
||||
d = data.strip()
|
||||
if d == 'None':
|
||||
return None
|
||||
return int(d)
|
||||
|
||||
if t == 'float':
|
||||
return float(data.strip())
|
||||
|
||||
if t in ('list','tuple'):
|
||||
res=[]
|
||||
for n in node.iterchildren(tag='value'):
|
||||
res.append(_eval_xml(self, n, env))
|
||||
if t=='tuple':
|
||||
return tuple(res)
|
||||
return res
|
||||
elif node.tag == "function":
|
||||
model_str = node.get('model')
|
||||
model = env[model_str]
|
||||
|
|
@ -189,9 +173,8 @@ def _eval_xml(self, node, env):
|
|||
# determine arguments
|
||||
args = []
|
||||
kwargs = {}
|
||||
a_eval = node.get('eval')
|
||||
|
||||
if a_eval:
|
||||
if a_eval := node.get('eval'):
|
||||
idref2 = _get_idref(self, env, model_str, self.idref)
|
||||
args = list(safe_eval(a_eval, idref2))
|
||||
for child in node:
|
||||
|
|
@ -254,16 +237,14 @@ form: module.record_id""" % (xml_id,)
|
|||
d_model = rec.get("model")
|
||||
records = self.env[d_model]
|
||||
|
||||
d_search = rec.get("search")
|
||||
if d_search:
|
||||
if d_search := rec.get("search"):
|
||||
idref = _get_idref(self, self.env, d_model, {})
|
||||
try:
|
||||
records = records.search(safe_eval(d_search, idref))
|
||||
except ValueError:
|
||||
_logger.warning('Skipping deletion for failed search `%r`', d_search, exc_info=True)
|
||||
|
||||
d_id = rec.get("id")
|
||||
if d_id:
|
||||
if d_id := rec.get("id"):
|
||||
try:
|
||||
records += records.browse(self.id_get(d_id))
|
||||
except ValueError:
|
||||
|
|
@ -365,16 +346,14 @@ form: module.record_id""" % (xml_id,)
|
|||
if not rec_id:
|
||||
return None
|
||||
|
||||
record = env['ir.model.data']._load_xmlid(xid)
|
||||
for child in rec.xpath('.//record[@id]'):
|
||||
sub_xid = child.get("id")
|
||||
self._test_xml_id(sub_xid)
|
||||
sub_xid = self.make_xml_id(sub_xid)
|
||||
sub_record = env['ir.model.data']._load_xmlid(sub_xid)
|
||||
if sub_record:
|
||||
self.idref[sub_xid] = sub_record.id
|
||||
if record := env['ir.model.data']._load_xmlid(xid):
|
||||
for child in rec.xpath('.//record[@id]'):
|
||||
sub_xid = child.get("id")
|
||||
self._test_xml_id(sub_xid)
|
||||
sub_xid = self.make_xml_id(sub_xid)
|
||||
if sub_record := env['ir.model.data']._load_xmlid(sub_xid):
|
||||
self.idref[sub_xid] = sub_record.id
|
||||
|
||||
if record:
|
||||
# if the resource already exists, don't update it but store
|
||||
# its database id (can be useful)
|
||||
self.idref[rec_id] = record.id
|
||||
|
|
@ -384,10 +363,11 @@ form: module.record_id""" % (xml_id,)
|
|||
return None
|
||||
# else create it normally
|
||||
|
||||
foreign_record_to_create = False
|
||||
if xid and xid.partition('.')[0] != self.module:
|
||||
# updating a record created by another module
|
||||
record = self.env['ir.model.data']._load_xmlid(xid)
|
||||
if not record:
|
||||
if not record and not (foreign_record_to_create := nodeattr2bool(rec, 'forcecreate')): # Allow foreign records if explicitely stated
|
||||
if self.noupdate and not nodeattr2bool(rec, 'forcecreate', True):
|
||||
# if it doesn't exist and we shouldn't create it, skip it
|
||||
return None
|
||||
|
|
@ -395,18 +375,16 @@ form: module.record_id""" % (xml_id,)
|
|||
|
||||
res = {}
|
||||
sub_records = []
|
||||
for field in rec.findall('./field'):
|
||||
for field in rec.iterchildren('field'):
|
||||
#TODO: most of this code is duplicated above (in _eval_xml)...
|
||||
f_name = field.get("name")
|
||||
f_ref = field.get("ref")
|
||||
f_search = field.get("search")
|
||||
f_model = field.get("model")
|
||||
if not f_model and f_name in model._fields:
|
||||
f_model = model._fields[f_name].comodel_name
|
||||
f_use = field.get("use",'') or 'id'
|
||||
f_val = False
|
||||
|
||||
if f_search:
|
||||
if f_search := field.get("search"):
|
||||
idref2 = _get_idref(self, env, f_model, self.idref)
|
||||
q = safe_eval(f_search, idref2)
|
||||
assert f_model, 'Define an attribute model="..." in your .XML file!'
|
||||
|
|
@ -421,7 +399,7 @@ form: module.record_id""" % (xml_id,)
|
|||
# otherwise (we are probably in a many2one field),
|
||||
# take the first element of the search
|
||||
f_val = s[0][f_use]
|
||||
elif f_ref:
|
||||
elif f_ref := field.get("ref"):
|
||||
if f_name in model._fields and model._fields[f_name].type == 'reference':
|
||||
val = self.model_id_get(f_ref)
|
||||
f_val = val[0] + ',' + str(val[1])
|
||||
|
|
@ -443,7 +421,7 @@ form: module.record_id""" % (xml_id,)
|
|||
elif field_type == 'boolean' and isinstance(f_val, str):
|
||||
f_val = str2bool(f_val)
|
||||
elif field_type == 'one2many':
|
||||
for child in field.findall('./record'):
|
||||
for child in field.iterchildren('record'):
|
||||
sub_records.append((child, model._fields[f_name].inverse_name))
|
||||
if isinstance(f_val, str):
|
||||
# We do not want to write on the field since we will write
|
||||
|
|
@ -461,6 +439,8 @@ form: module.record_id""" % (xml_id,)
|
|||
res['sequence'] = sequence
|
||||
|
||||
data = dict(xml_id=xid, values=res, noupdate=self.noupdate)
|
||||
if foreign_record_to_create:
|
||||
model = model.with_context(foreign_record_to_create=foreign_record_to_create)
|
||||
record = model._load_records([data], self.mode == 'update')
|
||||
if rec_id:
|
||||
self.idref[rec_id] = record.id
|
||||
|
|
@ -650,7 +630,7 @@ def convert_csv_import(env, module, fname, csvcontent, idref=None, mode='init',
|
|||
env = env(context=dict(env.context, lang=None))
|
||||
filename, _ext = os.path.splitext(os.path.basename(fname))
|
||||
model = filename.split('-')[0]
|
||||
reader = pycompat.csv_reader(io.BytesIO(csvcontent), quotechar='"', delimiter=',')
|
||||
reader = csv.reader(io.StringIO(csvcontent.decode()), quotechar='"', delimiter=',')
|
||||
fields = next(reader)
|
||||
|
||||
if not (mode == 'init' or 'id' in fields):
|
||||
|
|
@ -674,7 +654,12 @@ def convert_csv_import(env, module, fname, csvcontent, idref=None, mode='init',
|
|||
if any(msg['type'] == 'error' for msg in result['messages']):
|
||||
# Report failed import and abort module install
|
||||
warning_msg = "\n".join(msg['message'] for msg in result['messages'])
|
||||
raise Exception(_('Module loading %s failed: file %s could not be processed:\n %s') % (module, fname, warning_msg))
|
||||
raise Exception(env._(
|
||||
"Module loading %(module)s failed: file %(file)s could not be processed:\n%(message)s",
|
||||
module=module,
|
||||
file=fname,
|
||||
message=warning_msg,
|
||||
))
|
||||
|
||||
def convert_xml_import(env, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
|
||||
doc = etree.parse(xmlfile)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue