details: https://code.tryton.org/tryton/commit/d7e0dcf4ac45
branch: default
user: Cédric Krier <[email protected]>
date: Mon Mar 30 11:29:57 2026 +0200
description:
Fallback to Compact Syntax if RelaxNG files are not present
lxml can load compact syntax if rnc2rng package is installed.
This avoid the need to generate the RelaxNG files in development.
diffstat:
trytond/trytond/ir/action.py | 2 +-
trytond/trytond/ir/ui/view.py | 52 +++++++++++++++++---------------
trytond/trytond/model/modelview.py | 2 +-
trytond/trytond/tests/test_modelview.py | 2 +-
trytond/trytond/tests/test_tryton.py | 22 +++++++------
5 files changed, 43 insertions(+), 37 deletions(-)
diffs (194 lines):
diff -r 515ce1acffef -r d7e0dcf4ac45 trytond/trytond/ir/action.py
--- a/trytond/trytond/ir/action.py Thu Mar 26 22:39:31 2026 +0100
+++ b/trytond/trytond/ir/action.py Mon Mar 30 11:29:57 2026 +0200
@@ -935,7 +935,7 @@
action=action.rec_name)) from exception
def get_views(self, name):
- return [(view.view.id, view.view.rng_type)
+ return [(view.view.id, view.view.real_type)
for view in self.act_window_views]
def get_domains(self, name):
diff -r 515ce1acffef -r d7e0dcf4ac45 trytond/trytond/ir/ui/view.py
--- a/trytond/trytond/ir/ui/view.py Thu Mar 26 22:39:31 2026 +0100
+++ b/trytond/trytond/ir/ui/view.py Mon Mar 30 11:29:57 2026 +0200
@@ -6,6 +6,7 @@
import logging
import os
from collections import defaultdict
+from pathlib import Path
from lxml import etree
from sql import Literal, Null
@@ -113,7 +114,7 @@
domain = fields.Char('Domain', states={
'invisible': ~Eval('inherit'),
}, depends=['inherit'])
- _get_rng_cache = MemoryCache('ir_ui_view.get_rng', context=False)
+ _get_validator_cache = MemoryCache('ir_ui_view.validator', context=False)
_view_get_cache = Cache('ir_ui_view.view_get')
__module_index = None
@@ -196,23 +197,27 @@
def show(cls, views):
pass
+ def validator(self):
+ return self._validator(self.real_type)
+
@classmethod
- def get_rng(cls, type_):
- key = (cls.__name__, type_)
- rng = cls._get_rng_cache.get(key)
- if rng is None:
- if type_ == 'list-form':
- type_ = 'form'
- rng_name = os.path.join(os.path.dirname(__file__), type_ + '.rng')
- with open(rng_name, 'rb') as fp:
- rng = etree.fromstring(fp.read())
- rng = cls._get_rng_cache.set(key, rng)
- return rng
+ def _validator(cls, type):
+ key = (cls.__name__, type)
+ validator = cls._get_validator_cache.get(key)
+ if validator is None:
+ if type == 'list-form':
+ type = 'form'
+ filepath = Path(os.path.dirname(__file__), f'{type}.rng')
+ if not filepath.exists():
+ filepath = filepath.with_suffix('.rnc')
+ validator = etree.RelaxNG(file=str(filepath))
+ validator = cls._get_validator_cache.set(key, validator)
+ return validator
@property
- def rng_type(self):
+ def real_type(self):
if self.inherit:
- return self.inherit.rng_type
+ return self.inherit.real_type
return self.type
@classmethod
@@ -231,14 +236,13 @@
continue
tree = etree.fromstring(xml)
- if hasattr(etree, 'RelaxNG'):
- validator = etree.RelaxNG(etree=cls.get_rng(view.rng_type))
- if not validator.validate(tree):
- error_log = '\n'.join(map(str,
- validator.error_log.filter_from_errors()))
- raise XMLError(
- gettext('ir.msg_view_invalid_xml', name=view.rec_name),
- error_log)
+ validator = view.validator()
+ if not validator.validate(tree):
+ error_log = '\n'.join(map(str,
+ validator.error_log.filter_from_errors()))
+ raise XMLError(
+ gettext('ir.msg_view_invalid_xml', name=view.rec_name),
+ error_log)
root_element = tree.getroottree().getroot()
# validate pyson attributes
@@ -340,7 +344,7 @@
self._translate(root, model, Transaction().language)
arch = etree.tostring(tree, encoding='utf-8').decode('utf-8')
result = {
- 'type': self.rng_type,
+ 'type': self.real_type,
'view_id': self.id,
'arch': arch,
'field_childs': self.field_childs,
@@ -712,7 +716,7 @@
if fields_names and 'view' not in fields_names:
return
for record in records:
- if record.view and record.view.rng_type != 'tree':
+ if record.view and record.view.real_type != 'tree':
raise ViewError(gettext(
'ir.msg_view_tree_optional_type',
view=record.view.rec_name))
diff -r 515ce1acffef -r d7e0dcf4ac45 trytond/trytond/model/modelview.py
--- a/trytond/trytond/model/modelview.py Thu Mar 26 22:39:31 2026 +0100
+++ b/trytond/trytond/model/modelview.py Mon Mar 30 11:29:57 2026 +0200
@@ -288,7 +288,7 @@
],
]
views = View.search(domain)
- views = [v for v in views if v.rng_type == view_type]
+ views = [v for v in views if v.real_type == view_type]
if views:
view = views[0]
view_id = view.id
diff -r 515ce1acffef -r d7e0dcf4ac45 trytond/trytond/tests/test_modelview.py
--- a/trytond/trytond/tests/test_modelview.py Thu Mar 26 22:39:31 2026 +0100
+++ b/trytond/trytond/tests/test_modelview.py Mon Mar 30 11:29:57 2026 +0200
@@ -707,7 +707,7 @@
arch = TestModel.fields_view_get()['arch']
parser = etree.XMLParser()
tree = etree.fromstring(arch, parser=parser)
- validator = etree.RelaxNG(etree=UIView.get_rng('form'))
+ validator = UIView._validator('form')
self.assertTrue(validator.validate(tree))
diff -r 515ce1acffef -r d7e0dcf4ac45 trytond/trytond/tests/test_tryton.py
--- a/trytond/trytond/tests/test_tryton.py Thu Mar 26 22:39:31 2026 +0100
+++ b/trytond/trytond/tests/test_tryton.py Mon Mar 30 11:29:57 2026 +0200
@@ -37,7 +37,7 @@
from trytond.pool import Pool, isregisteredby
from trytond.protocols.wrappers import Response
from trytond.pyson import PYSON, PYSONDecoder, PYSONEncoder
-from trytond.tools import file_open, find_dir, is_instance_method
+from trytond.tools import file_open, find_dir, find_path, is_instance_method
from trytond.transaction import Transaction, TransactionError
from trytond.wizard import StateAction, StateView
from trytond.wsgi import app
@@ -471,11 +471,11 @@
view_id = view.id
model = view.model
Model = pool.get(model)
- view = Model.fields_view_get(view_id)
- self.assertEqual(view['model'], model)
- tree = etree.fromstring(view['arch'])
+ fields_view = Model.fields_view_get(view_id)
+ self.assertEqual(fields_view['model'], model)
+ tree = etree.fromstring(fields_view['arch'])
- validator = etree.RelaxNG(etree=View.get_rng(view['type']))
+ validator = view.validator()
validator.assertValid(tree)
tree_root = tree.getroottree().getroot()
@@ -520,11 +520,11 @@
button_name, Model.__name__))
for field in fields_to_check:
- self.assertIn(field, view['fields'].keys(),
+ self.assertIn(field, fields_view['fields'].keys(),
msg="Missing field %r in %r" % (
field, Model.__name__))
for field, t_fields in target_fields_to_check.items():
- for t_view in view['fields'][field].get(
+ for t_view in fields_view['fields'][field].get(
'views', {}).values():
for t_field in t_fields:
self.assertIn(
@@ -1186,9 +1186,11 @@
config.read_file(fp)
if not config.has_option('tryton', 'xml'):
return
- with file_open('tryton.rng', subdir='', mode='rb') as fp:
- rng = etree.parse(fp)
- validator = etree.RelaxNG(etree=rng)
+ try:
+ filepath = find_path('tryton.rng', subdir='')
+ except FileNotFoundError:
+ filepath = find_path('tryton.rnc', subdir='')
+ validator = etree.RelaxNG(file=filepath)
for xml_file in filter(None, config.get('tryton', 'xml').splitlines()):
with self.subTest(xml=xml_file):
with file_open('%s/%s' % (self.module, xml_file),