17.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:05:14 +02:00
parent 2e65bf056a
commit df627a6bba
328 changed files with 578149 additions and 759311 deletions

View file

@ -28,7 +28,7 @@ except ImportError:
import odoo
from . import pycompat
from .config import config
from .misc import file_open, unquote, ustr, SKIPPED_ELEMENT_TYPES
from .misc import file_open, file_path, SKIPPED_ELEMENT_TYPES
from .translate import _
from odoo import SUPERUSER_ID, api
from odoo.exceptions import ValidationError
@ -155,9 +155,10 @@ def _eval_xml(self, node, env):
# after that, only text content makes sense
data = pycompat.to_text(data)
if t == 'file':
from ..modules import module
path = data.strip()
if not module.get_module_resource(self.module, path):
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)
@ -229,7 +230,7 @@ class xml_import(object):
**safe_eval(context, {
'ref': self.id_get,
**(eval_context or {})
})
}),
}
)
return self.env
@ -272,160 +273,12 @@ form: module.record_id""" % (xml_id,)
if records:
records.unlink()
def _tag_report(self, rec):
res = {}
for dest,f in (('name','string'),('model','model'),('report_name','name')):
res[dest] = rec.get(f)
assert res[dest], "Attribute %s of report is empty !" % (f,)
for field, dest in (('attachment', 'attachment'),
('attachment_use', 'attachment_use'),
('usage', 'usage'),
('file', 'report_file'),
('report_type', 'report_type'),
('parser', 'parser'),
('print_report_name', 'print_report_name'),
):
if rec.get(field):
res[dest] = rec.get(field)
if rec.get('auto'):
res['auto'] = safe_eval(rec.get('auto','False'))
if rec.get('header'):
res['header'] = safe_eval(rec.get('header','False'))
res['multi'] = rec.get('multi') and safe_eval(rec.get('multi','False'))
xml_id = rec.get('id','')
self._test_xml_id(xml_id)
warnings.warn(f"The <report> tag is deprecated, use a <record> tag for {xml_id!r}.", DeprecationWarning)
if rec.get('groups'):
g_names = rec.get('groups','').split(',')
groups_value = []
for group in g_names:
if group.startswith('-'):
group_id = self.id_get(group[1:])
groups_value.append(odoo.Command.unlink(group_id))
else:
group_id = self.id_get(group)
groups_value.append(odoo.Command.link(group_id))
res['groups_id'] = groups_value
if rec.get('paperformat'):
pf_name = rec.get('paperformat')
pf_id = self.id_get(pf_name)
res['paperformat_id'] = pf_id
xid = self.make_xml_id(xml_id)
data = dict(xml_id=xid, values=res, noupdate=self.noupdate)
report = self.env['ir.actions.report']._load_records([data], self.mode == 'update')
self.idref[xml_id] = report.id
if not rec.get('menu') or safe_eval(rec.get('menu','False')):
report.create_action()
elif self.mode=='update' and safe_eval(rec.get('menu','False'))==False:
# Special check for report having attribute menu=False on update
report.unlink_action()
return report.id
def _tag_function(self, rec):
if self.noupdate and self.mode != 'init':
return
env = self.get_env(rec)
_eval_xml(self, rec, env)
def _tag_act_window(self, rec):
name = rec.get('name')
xml_id = rec.get('id','')
self._test_xml_id(xml_id)
warnings.warn(f"The <act_window> tag is deprecated, use a <record> for {xml_id!r}.", DeprecationWarning)
view_id = False
if rec.get('view_id'):
view_id = self.id_get(rec.get('view_id'))
domain = rec.get('domain') or '[]'
res_model = rec.get('res_model')
binding_model = rec.get('binding_model')
view_mode = rec.get('view_mode') or 'tree,form'
usage = rec.get('usage')
limit = rec.get('limit')
uid = self.env.user.id
# Act_window's 'domain' and 'context' contain mostly literals
# but they can also refer to the variables provided below
# in eval_context, so we need to eval() them before storing.
# Among the context variables, 'active_id' refers to
# the currently selected items in a list view, and only
# takes meaning at runtime on the client side. For this
# reason it must remain a bare variable in domain and context,
# even after eval() at server-side. We use the special 'unquote'
# class to achieve this effect: a string which has itself, unquoted,
# as representation.
active_id = unquote("active_id")
active_ids = unquote("active_ids")
active_model = unquote("active_model")
# Include all locals() in eval_context, for backwards compatibility
eval_context = {
'name': name,
'xml_id': xml_id,
'type': 'ir.actions.act_window',
'view_id': view_id,
'domain': domain,
'res_model': res_model,
'src_model': binding_model,
'view_mode': view_mode,
'usage': usage,
'limit': limit,
'uid': uid,
'active_id': active_id,
'active_ids': active_ids,
'active_model': active_model,
}
context = self.get_env(rec, eval_context).context
try:
domain = safe_eval(domain, eval_context)
except (ValueError, NameError):
# Some domains contain references that are only valid at runtime at
# client-side, so in that case we keep the original domain string
# as it is. We also log it, just in case.
_logger.debug('Domain value (%s) for element with id "%s" does not parse '\
'at server-side, keeping original string, in case it\'s meant for client side only',
domain, xml_id or 'n/a', exc_info=True)
res = {
'name': name,
'type': 'ir.actions.act_window',
'view_id': view_id,
'domain': domain,
'context': context,
'res_model': res_model,
'view_mode': view_mode,
'usage': usage,
'limit': limit,
}
if rec.get('groups'):
g_names = rec.get('groups','').split(',')
groups_value = []
for group in g_names:
if group.startswith('-'):
group_id = self.id_get(group[1:])
groups_value.append(odoo.Command.unlink(group_id))
else:
group_id = self.id_get(group)
groups_value.append(odoo.Command.link(group_id))
res['groups_id'] = groups_value
if rec.get('target'):
res['target'] = rec.get('target','')
if binding_model:
res['binding_model_id'] = self.env['ir.model']._get(binding_model).id
res['binding_type'] = rec.get('binding_type') or 'action'
views = rec.get('binding_views')
if views is not None:
res['binding_view_types'] = views
xid = self.make_xml_id(xml_id)
data = dict(xml_id=xid, values=res, noupdate=self.noupdate)
self.env['ir.actions.act_window']._load_records([data], self.mode == 'update')
def _tag_menuitem(self, rec, parent=None):
rec_id = rec.attrib["id"]
self._test_xml_id(rec_id)
@ -556,7 +409,7 @@ form: module.record_id""" % (xml_id,)
if f_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 !'
assert f_model, 'Define an attribute model="..." in your .XML file!'
# browse the objects searched
s = env[f_model].search(q)
# column definitions of the "local" object
@ -602,6 +455,10 @@ form: module.record_id""" % (xml_id,)
res[f_name] = f_val
if extra_vals:
res.update(extra_vals)
if 'sequence' not in res and 'sequence' in model._fields:
sequence = self.next_sequence()
if sequence:
res['sequence'] = sequence
data = dict(xml_id=xid, values=res, noupdate=self.noupdate)
record = model._load_records([data], self.mode == 'update')
@ -702,6 +559,7 @@ form: module.record_id""" % (xml_id,)
self.envs.append(self.get_env(el))
self._noupdate.append(nodeattr2bool(el, 'noupdate', self.noupdate))
self._sequences.append(0 if nodeattr2bool(el, 'auto_sequence', False) else None)
try:
f(rec)
except ParseError:
@ -724,6 +582,7 @@ form: module.record_id""" % (xml_id,)
finally:
self._noupdate.pop()
self.envs.pop()
self._sequences.pop()
@property
def env(self):
@ -733,12 +592,19 @@ form: module.record_id""" % (xml_id,)
def noupdate(self):
return self._noupdate[-1]
def __init__(self, cr, module, idref, mode, noupdate=False, xml_filename=None):
def next_sequence(self):
value = self._sequences[-1]
if value is not None:
value = self._sequences[-1] = value + 10
return value
def __init__(self, env, module, idref, mode, noupdate=False, xml_filename=None):
self.mode = mode
self.module = module
self.envs = [odoo.api.Environment(cr, SUPERUSER_ID, {})]
self.envs = [env(context=dict(env.context, lang=None))]
self.idref = {} if idref is None else idref
self._noupdate = [noupdate]
self._sequences = []
self.xml_filename = xml_filename
self._tags = {
'record': self._tag_record,
@ -746,8 +612,6 @@ form: module.record_id""" % (xml_id,)
'function': self._tag_function,
'menuitem': self._tag_menuitem,
'template': self._tag_template,
'report': self._tag_report,
'act_window': self._tag_act_window,
**dict.fromkeys(self.DATA_ROOTS, self._tag_root)
}
@ -757,32 +621,33 @@ form: module.record_id""" % (xml_id,)
self._tag_root(de)
DATA_ROOTS = ['odoo', 'data', 'openerp']
def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kind=None, pathname=None):
def convert_file(env, module, filename, idref, mode='update', noupdate=False, kind=None, pathname=None):
if pathname is None:
pathname = os.path.join(module, filename)
ext = os.path.splitext(filename)[1].lower()
with file_open(pathname, 'rb') as fp:
if ext == '.csv':
convert_csv_import(cr, module, pathname, fp.read(), idref, mode, noupdate)
convert_csv_import(env, module, pathname, fp.read(), idref, mode, noupdate)
elif ext == '.sql':
convert_sql_import(cr, fp)
convert_sql_import(env, fp)
elif ext == '.xml':
convert_xml_import(cr, module, fp, idref, mode, noupdate)
convert_xml_import(env, module, fp, idref, mode, noupdate)
elif ext == '.js':
pass # .js files are valid but ignored here.
else:
raise ValueError("Can't load unknown file type %s.", filename)
def convert_sql_import(cr, fp):
cr.execute(fp.read()) # pylint: disable=sql-injection
def convert_sql_import(env, fp):
env.cr.execute(fp.read()) # pylint: disable=sql-injection
def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
def convert_csv_import(env, module, fname, csvcontent, idref=None, mode='init',
noupdate=False):
'''Import csv file :
quote: "
delimiter: ,
encoding: utf-8'''
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=',')
@ -805,21 +670,20 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
'install_filename': fname,
'noupdate': noupdate,
}
env = odoo.api.Environment(cr, SUPERUSER_ID, context)
result = env[model].load(fields, datas)
result = env[model].with_context(**context).load(fields, datas)
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))
def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
def convert_xml_import(env, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
doc = etree.parse(xmlfile)
schema = os.path.join(config['root_path'], 'import_xml.rng')
relaxng = etree.RelaxNG(etree.parse(schema))
try:
relaxng.assert_(doc)
except Exception:
_logger.exception("The XML file '%s' does not fit the required schema !", xmlfile.name)
_logger.exception("The XML file '%s' does not fit the required schema!", xmlfile.name)
if jingtrang:
p = subprocess.run(['pyjing', schema, xmlfile.name], stdout=subprocess.PIPE)
_logger.warning(p.stdout.decode())
@ -833,5 +697,5 @@ def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=Fa
xml_filename = xmlfile
else:
xml_filename = xmlfile.name
obj = xml_import(cr, module, idref, mode, noupdate=noupdate, xml_filename=xml_filename)
obj = xml_import(env, module, idref, mode, noupdate=noupdate, xml_filename=xml_filename)
obj.parse(doc.getroot())