From 385bce6315e3fcf929e8ba18ea89a8192e404d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Tue, 1 Sep 2026 18:49:45 +0200 Subject: [PATCH] Add function to import XML files for tests Close #14627 --- trytond/CHANGELOG | 1 + trytond/doc/ref/convert.rst | 20 +++++++++++++++ trytond/doc/ref/index.rst | 1 + trytond/trytond/convert.py | 51 +++++++++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 trytond/doc/ref/convert.rst diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index 7e3c790b87..e52f127b67 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Add function to import XML files * Add support for router in Pool * Add explicit request context in URLAccessor * Retry unfinished queued tasks diff --git a/trytond/doc/ref/convert.rst b/trytond/doc/ref/convert.rst new file mode 100644 index 0000000000..26838a0daa --- /dev/null +++ b/trytond/doc/ref/convert.rst @@ -0,0 +1,20 @@ +.. _ref-convert: +.. module:: trytond.convert + +======= +Convert +======= + +The ``convert`` module provides a way to import records from XML files. + + +import_xml +---------- + +.. function:: import_xml(xml, module) + +Imports the records from ``xml`` into the database as if they were defined in +``module``. +Those records are not store in the Model Data table. +``xml`` can be a :py:class:`string `, a :py:class:`bytes ` or a +file object. diff --git a/trytond/doc/ref/index.rst b/trytond/doc/ref/index.rst index e98ff142f5..f9f9f506e6 100644 --- a/trytond/doc/ref/index.rst +++ b/trytond/doc/ref/index.rst @@ -28,4 +28,5 @@ Reference of the ``trytond`` API: filestore cache bus + convert tests diff --git a/trytond/trytond/convert.py b/trytond/trytond/convert.py index ad1fb3ae4e..45b20d6605 100644 --- a/trytond/trytond/convert.py +++ b/trytond/trytond/convert.py @@ -1,6 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime +import io import logging import os.path import re @@ -13,6 +14,8 @@ from trytond import __version__ from trytond.pyson import CONTEXT, PYSONEncoder from trytond.transaction import Transaction, inactive_records +__all__ = ['import_xml'] + logger = logging.getLogger(__name__) CDATA_START = re.compile(r'^\s*\<\!\[cdata\[', re.IGNORECASE) @@ -377,13 +380,16 @@ class FS2DBAccessor: class TrytondXmlHandler(sax.handler.ContentHandler): - def __init__(self, pool, module, module_state, modules, languages): + def __init__( + self, pool, module, module_state, modules, languages, + store_model_data=True): "Register known taghandlers, and managed tags." - sax.handler.ContentHandler.__init__(self) + super().__init__() self.pool = pool self.module = module self.ModelData = pool.get('ir.model.data') + self.store_model_data = store_model_data self.fs2db = FS2DBAccessor(self.ModelData) self.to_delete = self.populate_to_delete() self.noupdate = None @@ -474,7 +480,8 @@ class TrytondXmlHandler(sax.handler.ContentHandler): self.write_records(model, *actions) self.grouped_write.clear() if name == 'data' and self.grouped_model_data: - self.ModelData.save(self.grouped_model_data) + if self.store_model_data: + self.ModelData.save(self.grouped_model_data) self.grouped_model_data.clear() # Closing tag found, if we are in a delegation the handler @@ -531,11 +538,15 @@ class TrytondXmlHandler(sax.handler.ContentHandler): process. The records that are not encountered are deleted from the database in post_import.""" - # Fetch the data in id descending order to avoid depedendcy - # problem when the corresponding recordds will be deleted: - module_data = self.ModelData.search([ - ('module', '=', self.module), - ], order=[('id', 'DESC')]) + if self.store_model_data: + # Fetch the data in id descending order to avoid depedendcy + # problem when the corresponding recordds will be deleted: + module_data = self.ModelData.search([ + ('module', '=', self.module), + ], order=[('id', 'DESC')]) + else: + module_data = [] + return set(rec.fs_id for rec in module_data) def import_record(self, model, values, fs_id, domain=None): @@ -691,3 +702,27 @@ def post_import(pool, module, to_delete): transaction.commit() return True + + +def import_xml(xml): + from trytond.pool import Pool + + pool = Pool() + Modules = pool.get('ir.module') + modules = {m.name for m in Modules.search([ + ('state', '=', 'activated'), + ])} + Lang = pool.get('ir.lang') + langs = {l.code for l in Lang.search([ + ('translatable', '=', True), + ])} + parser = TrytondXmlHandler( + pool, None, 'to activate', modules, langs, store_model_data=False) + + if isinstance(xml, str): + stream = io.BytesIO(xml.encode('utf8')) + elif isinstance(xml, bytes): + stream = io.BytesIO(xml) + else: + stream = xml + parser.parse_xmlstream(stream)