[MOV] core: put things where they belong
This commit moves some ir_ui_view specific functions into ir_ui_view.py and removes some old-api <-> new-api compatibility shims as well as removes orm.py since it has 0 to do with the Odoo ORM. closes odoo/odoo#34826 Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import collections
|
||||
import datetime
|
||||
import fnmatch
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
@@ -22,7 +23,6 @@ from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.http import request
|
||||
from odoo.modules.module import get_resource_from_path, get_resource_path
|
||||
from odoo.osv import orm
|
||||
from odoo.tools import config, graph, ConstantMapping, pycompat, apply_inheritance_specs, locate_node
|
||||
from odoo.tools.convert import _fix_multiple_roots
|
||||
from odoo.tools.json import scriptsafe as json_scriptsafe
|
||||
@@ -40,6 +40,63 @@ MOVABLE_BRANDING = ['data-oe-model', 'data-oe-id', 'data-oe-field', 'data-oe-xpa
|
||||
INHERIT_ORDER = 'priority,id'
|
||||
|
||||
|
||||
def transfer_field_to_modifiers(field, modifiers):
|
||||
default_values = {}
|
||||
state_exceptions = {}
|
||||
for attr in ('invisible', 'readonly', 'required'):
|
||||
state_exceptions[attr] = []
|
||||
default_values[attr] = bool(field.get(attr))
|
||||
for state, modifs in field.get("states",{}).items():
|
||||
for modif in modifs:
|
||||
if default_values[modif[0]] != modif[1]:
|
||||
state_exceptions[modif[0]].append(state)
|
||||
|
||||
for attr, default_value in default_values.items():
|
||||
if state_exceptions[attr]:
|
||||
modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])]
|
||||
else:
|
||||
modifiers[attr] = default_value
|
||||
|
||||
|
||||
def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False):
|
||||
# Don't deal with groups, it is done by check_group().
|
||||
# Need the context to evaluate the invisible attribute on tree views.
|
||||
# For non-tree views, the context shouldn't be given.
|
||||
if node.get('attrs'):
|
||||
modifiers.update(safe_eval(node.get('attrs')))
|
||||
|
||||
if node.get('states'):
|
||||
if 'invisible' in modifiers and isinstance(modifiers['invisible'], list):
|
||||
# TODO combine with AND or OR, use implicit AND for now.
|
||||
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
|
||||
else:
|
||||
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
|
||||
|
||||
for a in ('invisible', 'readonly', 'required'):
|
||||
if node.get(a):
|
||||
v = bool(safe_eval(node.get(a), {'context': context or {}}))
|
||||
if in_tree_view and a == 'invisible':
|
||||
# Invisible in a tree view has a specific meaning, make it a
|
||||
# new key in the modifiers attribute.
|
||||
modifiers['column_invisible'] = v
|
||||
elif v or (a not in modifiers or not isinstance(modifiers[a], list)):
|
||||
# Don't set the attribute to False if a dynamic value was
|
||||
# provided (i.e. a domain from attrs or states).
|
||||
modifiers[a] = v
|
||||
|
||||
|
||||
def simplify_modifiers(modifiers):
|
||||
for a in ('invisible', 'readonly', 'required'):
|
||||
if a in modifiers and not modifiers[a]:
|
||||
del modifiers[a]
|
||||
|
||||
|
||||
def transfer_modifiers_to_node(modifiers, node):
|
||||
if modifiers:
|
||||
simplify_modifiers(modifiers)
|
||||
node.set('modifiers', json.dumps(modifiers))
|
||||
|
||||
|
||||
def keep_query(*keep_params, **additional_params):
|
||||
"""
|
||||
Generate a query string keeping the current request querystring's parameters specified
|
||||
@@ -742,7 +799,7 @@ actual arch.
|
||||
|
||||
field = model_fields.get(node.get('name'))
|
||||
if field:
|
||||
orm.transfer_field_to_modifiers(field, modifiers)
|
||||
transfer_field_to_modifiers(field, modifiers)
|
||||
|
||||
elif node.tag == 'groupby':
|
||||
# groupby nodes should be considered as nested view because they may
|
||||
@@ -797,7 +854,7 @@ actual arch.
|
||||
|
||||
# The view architeture overrides the python model.
|
||||
# Get the attrs before they are (possibly) deleted by check_group below
|
||||
orm.transfer_node_to_modifiers(node, modifiers, self._context, in_tree_view)
|
||||
transfer_node_to_modifiers(node, modifiers, self._context, in_tree_view)
|
||||
|
||||
for f in node:
|
||||
if node.tag == 'search' and f.tag == 'searchpanel':
|
||||
@@ -806,7 +863,7 @@ actual arch.
|
||||
if children or (node.tag == 'field' and f.tag in ('filter', 'separator')):
|
||||
fields.update(self.postprocess(model, f, view_id, in_tree_view, model_fields))
|
||||
|
||||
orm.transfer_modifiers_to_node(modifiers, node)
|
||||
transfer_modifiers_to_node(modifiers, node)
|
||||
return fields
|
||||
|
||||
def add_on_change(self, model_name, arch):
|
||||
|
||||
@@ -7,10 +7,12 @@ from lxml import etree
|
||||
from lxml.builder import E
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo.osv.orm import modifiers_tests
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import common
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.addons.base.models.ir_ui_view import (
|
||||
transfer_field_to_modifiers, transfer_node_to_modifiers, simplify_modifiers,
|
||||
)
|
||||
|
||||
|
||||
class ViewXMLID(common.TransactionCase):
|
||||
@@ -1104,8 +1106,36 @@ class TestViews(ViewCase):
|
||||
))
|
||||
|
||||
def test_modifiers(self):
|
||||
# implemeted elsewhere...
|
||||
modifiers_tests()
|
||||
def _test_modifiers(what, expected):
|
||||
modifiers = {}
|
||||
if isinstance(what, str):
|
||||
node = etree.fromstring(what)
|
||||
transfer_node_to_modifiers(node, modifiers)
|
||||
simplify_modifiers(modifiers)
|
||||
assert modifiers == expected, "%s != %s" % (modifiers, expected)
|
||||
elif isinstance(what, dict):
|
||||
transfer_field_to_modifiers(what, modifiers)
|
||||
simplify_modifiers(modifiers)
|
||||
assert modifiers == expected, "%s != %s" % (modifiers, expected)
|
||||
_test_modifiers('<field name="a"/>', {})
|
||||
_test_modifiers('<field name="a" invisible="1"/>', {"invisible": True})
|
||||
_test_modifiers('<field name="a" readonly="1"/>', {"readonly": True})
|
||||
_test_modifiers('<field name="a" required="1"/>', {"required": True})
|
||||
_test_modifiers('<field name="a" invisible="0"/>', {})
|
||||
_test_modifiers('<field name="a" readonly="0"/>', {})
|
||||
_test_modifiers('<field name="a" required="0"/>', {})
|
||||
# TODO: Order is not guaranteed
|
||||
_test_modifiers('<field name="a" invisible="1" required="1"/>',
|
||||
{"invisible": True, "required": True})
|
||||
_test_modifiers('<field name="a" invisible="1" required="0"/>', {"invisible": True})
|
||||
_test_modifiers('<field name="a" invisible="0" required="1"/>', {"required": True})
|
||||
_test_modifiers("""<field name="a" attrs="{'invisible': [['b', '=', 'c']]}"/>""",
|
||||
{"invisible": [["b", "=", "c"]]})
|
||||
|
||||
# The dictionary is supposed to be the result of fields_get().
|
||||
_test_modifiers({}, {})
|
||||
_test_modifiers({"invisible": True}, {"invisible": True})
|
||||
_test_modifiers({"invisible": False}, {})
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
def test_invalid_field(self):
|
||||
|
||||
-157
@@ -1,157 +0,0 @@
|
||||
import json
|
||||
import warnings
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from ..exceptions import except_orm
|
||||
from ..models import (
|
||||
MetaModel,
|
||||
BaseModel,
|
||||
Model, TransientModel, AbstractModel,
|
||||
|
||||
MAGIC_COLUMNS,
|
||||
LOG_ACCESS_COLUMNS,
|
||||
)
|
||||
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
# extra definitions for backward compatibility
|
||||
browse_record_list = BaseModel
|
||||
|
||||
class BRM(type):
|
||||
def __instancecheck__(self, inst):
|
||||
warnings.warn(DeprecationWarning(
|
||||
"browse_record is a deprecated concept and should not be used "
|
||||
"anymore, you can replace `isinstance(o, browse_record)` by "
|
||||
"`isinstance(o, BaseModel)`"
|
||||
))
|
||||
return isinstance(inst, BaseModel) and len(inst) <= 1
|
||||
browse_record = BRM('browse_record', (object,), {})
|
||||
|
||||
class NBM(type):
|
||||
def __instancecheck__(self, inst):
|
||||
warnings.warn(DeprecationWarning(
|
||||
"browse_record is a deprecated concept and should not be used "
|
||||
"anymore, you can replace `isinstance(o, browse_null)` by "
|
||||
"`isinstance(o, BaseModel) and not o`"
|
||||
))
|
||||
return isinstance(inst, BaseModel) and not inst
|
||||
browse_null = NBM('browse_null', (object,), {})
|
||||
|
||||
|
||||
def transfer_field_to_modifiers(field, modifiers):
|
||||
default_values = {}
|
||||
state_exceptions = {}
|
||||
for attr in ('invisible', 'readonly', 'required'):
|
||||
state_exceptions[attr] = []
|
||||
default_values[attr] = bool(field.get(attr))
|
||||
for state, modifs in field.get("states",{}).items():
|
||||
for modif in modifs:
|
||||
if default_values[modif[0]] != modif[1]:
|
||||
state_exceptions[modif[0]].append(state)
|
||||
|
||||
for attr, default_value in default_values.items():
|
||||
if state_exceptions[attr]:
|
||||
modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])]
|
||||
else:
|
||||
modifiers[attr] = default_value
|
||||
|
||||
|
||||
# Don't deal with groups, it is done by check_group().
|
||||
# Need the context to evaluate the invisible attribute on tree views.
|
||||
# For non-tree views, the context shouldn't be given.
|
||||
def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False):
|
||||
if node.get('attrs'):
|
||||
modifiers.update(safe_eval(node.get('attrs')))
|
||||
|
||||
if node.get('states'):
|
||||
if 'invisible' in modifiers and isinstance(modifiers['invisible'], list):
|
||||
# TODO combine with AND or OR, use implicit AND for now.
|
||||
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
|
||||
else:
|
||||
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
|
||||
|
||||
for a in ('invisible', 'readonly', 'required'):
|
||||
if node.get(a):
|
||||
v = bool(safe_eval(node.get(a), {'context': context or {}}))
|
||||
if in_tree_view and a == 'invisible':
|
||||
# Invisible in a tree view has a specific meaning, make it a
|
||||
# new key in the modifiers attribute.
|
||||
modifiers['column_invisible'] = v
|
||||
elif v or (a not in modifiers or not isinstance(modifiers[a], list)):
|
||||
# Don't set the attribute to False if a dynamic value was
|
||||
# provided (i.e. a domain from attrs or states).
|
||||
modifiers[a] = v
|
||||
|
||||
|
||||
def simplify_modifiers(modifiers):
|
||||
for a in ('invisible', 'readonly', 'required'):
|
||||
if a in modifiers and not modifiers[a]:
|
||||
del modifiers[a]
|
||||
|
||||
|
||||
def transfer_modifiers_to_node(modifiers, node):
|
||||
if modifiers:
|
||||
simplify_modifiers(modifiers)
|
||||
node.set('modifiers', json.dumps(modifiers))
|
||||
|
||||
def setup_modifiers(node, field=None, context=None, in_tree_view=False):
|
||||
""" Processes node attributes and field descriptors to generate
|
||||
the ``modifiers`` node attribute and set it on the provided node.
|
||||
|
||||
Alters its first argument in-place.
|
||||
|
||||
:param node: ``field`` node from an OpenERP view
|
||||
:type node: lxml.etree._Element
|
||||
:param dict field: field descriptor corresponding to the provided node
|
||||
:param dict context: execution context used to evaluate node attributes
|
||||
:param bool in_tree_view: triggers the ``column_invisible`` code
|
||||
path (separate from ``invisible``): in
|
||||
tree view there are two levels of
|
||||
invisibility, cell content (a column is
|
||||
present but the cell itself is not
|
||||
displayed) with ``invisible`` and column
|
||||
invisibility (the whole column is
|
||||
hidden) with ``column_invisible``.
|
||||
:returns: nothing
|
||||
"""
|
||||
modifiers = {}
|
||||
if field is not None:
|
||||
transfer_field_to_modifiers(field, modifiers)
|
||||
transfer_node_to_modifiers(
|
||||
node, modifiers, context=context, in_tree_view=in_tree_view)
|
||||
transfer_modifiers_to_node(modifiers, node)
|
||||
|
||||
def test_modifiers(what, expected):
|
||||
modifiers = {}
|
||||
if isinstance(what, str):
|
||||
node = etree.fromstring(what)
|
||||
transfer_node_to_modifiers(node, modifiers)
|
||||
simplify_modifiers(modifiers)
|
||||
assert modifiers == expected, "%s != %s" % (modifiers, expected)
|
||||
elif isinstance(what, dict):
|
||||
transfer_field_to_modifiers(what, modifiers)
|
||||
simplify_modifiers(modifiers)
|
||||
assert modifiers == expected, "%s != %s" % (modifiers, expected)
|
||||
|
||||
|
||||
# To use this test:
|
||||
# import odoo
|
||||
# odoo.osv.orm.modifiers_tests()
|
||||
def modifiers_tests():
|
||||
test_modifiers('<field name="a"/>', {})
|
||||
test_modifiers('<field name="a" invisible="1"/>', {"invisible": True})
|
||||
test_modifiers('<field name="a" readonly="1"/>', {"readonly": True})
|
||||
test_modifiers('<field name="a" required="1"/>', {"required": True})
|
||||
test_modifiers('<field name="a" invisible="0"/>', {})
|
||||
test_modifiers('<field name="a" readonly="0"/>', {})
|
||||
test_modifiers('<field name="a" required="0"/>', {})
|
||||
test_modifiers('<field name="a" invisible="1" required="1"/>', {"invisible": True, "required": True}) # TODO order is not guaranteed
|
||||
test_modifiers('<field name="a" invisible="1" required="0"/>', {"invisible": True})
|
||||
test_modifiers('<field name="a" invisible="0" required="1"/>', {"required": True})
|
||||
test_modifiers("""<field name="a" attrs="{'invisible': [['b', '=', 'c']]}"/>""", {"invisible": [["b", "=", "c"]]})
|
||||
|
||||
# The dictionary is supposed to be the result of fields_get().
|
||||
test_modifiers({}, {})
|
||||
test_modifiers({"invisible": True}, {"invisible": True})
|
||||
test_modifiers({"invisible": False}, {})
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from ..exceptions import except_orm
|
||||
from .orm import Model, TransientModel, AbstractModel
|
||||
from ..models import Model, TransientModel, AbstractModel
|
||||
|
||||
# Deprecated, kept for backward compatibility.
|
||||
# openerp.exceptions.Warning should be used instead.
|
||||
|
||||
Reference in New Issue
Block a user