gooderp18绿色标准版
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

318 lines
11KB

  1. """ View validation code (using assertions, not the RNG schema). """
  2. import ast
  3. import collections
  4. import logging
  5. import os
  6. import re
  7. from lxml import etree
  8. from odoo import tools
  9. from odoo.osv.expression import DOMAIN_OPERATORS
  10. _logger = logging.getLogger(__name__)
  11. _validators = collections.defaultdict(list)
  12. _relaxng_cache = {}
  13. READONLY = re.compile(r"\breadonly\b")
  14. # predefined symbols for evaluating attributes (invisible, readonly...)
  15. IGNORED_IN_EXPRESSION = {
  16. 'True', 'False', 'None', # those are identifiers in Python 2.7
  17. 'self',
  18. 'uid',
  19. 'context',
  20. 'context_today',
  21. 'allowed_company_ids',
  22. 'current_company_id',
  23. 'time',
  24. 'datetime',
  25. 'relativedelta',
  26. 'current_date',
  27. 'today',
  28. 'now',
  29. 'abs',
  30. 'len',
  31. 'bool',
  32. 'float',
  33. 'str',
  34. 'unicode',
  35. 'set',
  36. }
  37. def get_domain_value_names(domain):
  38. """ Return all field name used by this domain
  39. eg: [
  40. ('id', 'in', [1, 2, 3]),
  41. ('field_a', 'in', parent.truc),
  42. ('field_b', 'in', context.get('b')),
  43. (1, '=', 1),
  44. bool(context.get('c')),
  45. ]
  46. returns {'id', 'field_a', 'field_b'}, {'parent', 'parent.truc', 'context'}
  47. :param domain: list(tuple) or str
  48. :return: set(str), set(str)
  49. """
  50. contextual_values = set()
  51. field_names = set()
  52. try:
  53. if isinstance(domain, list):
  54. for leaf in domain:
  55. if leaf in DOMAIN_OPERATORS or leaf in (True, False):
  56. # "&", "|", "!", True, False
  57. continue
  58. left, _operator, _right = leaf
  59. if isinstance(left, str):
  60. field_names.add(left)
  61. elif left not in (1, 0):
  62. # deprecate: True leaf and False leaf
  63. raise ValueError()
  64. elif isinstance(domain, str):
  65. def extract_from_domain(ast_domain):
  66. if isinstance(ast_domain, ast.IfExp):
  67. # [] if condition else []
  68. extract_from_domain(ast_domain.body)
  69. extract_from_domain(ast_domain.orelse)
  70. return
  71. if isinstance(ast_domain, ast.BoolOp):
  72. # condition and []
  73. # this formating don't check returned domain syntax
  74. for value in ast_domain.values:
  75. if isinstance(value, (ast.List, ast.IfExp, ast.BoolOp, ast.BinOp)):
  76. extract_from_domain(value)
  77. else:
  78. contextual_values.update(_get_expression_contextual_values(value))
  79. return
  80. if isinstance(ast_domain, ast.BinOp):
  81. # [] + []
  82. # this formating don't check returned domain syntax
  83. if isinstance(ast_domain.left, (ast.List, ast.IfExp, ast.BoolOp, ast.BinOp)):
  84. extract_from_domain(ast_domain.left)
  85. else:
  86. contextual_values.update(_get_expression_contextual_values(ast_domain.left))
  87. if isinstance(ast_domain.right, (ast.List, ast.IfExp, ast.BoolOp, ast.BinOp)):
  88. extract_from_domain(ast_domain.right)
  89. else:
  90. contextual_values.update(_get_expression_contextual_values(ast_domain.right))
  91. return
  92. for ast_item in ast_domain.elts:
  93. if isinstance(ast_item, ast.Constant):
  94. # "&", "|", "!", True, False
  95. if ast_item.value not in DOMAIN_OPERATORS and ast_item.value not in (True, False):
  96. raise ValueError()
  97. elif isinstance(ast_item, (ast.List, ast.Tuple)):
  98. left, _operator, right = ast_item.elts
  99. contextual_values.update(_get_expression_contextual_values(right))
  100. if isinstance(left, ast.Constant) and isinstance(left.value, str):
  101. field_names.add(left.value)
  102. elif isinstance(left, ast.Constant) and left.value in (1, 0):
  103. # deprecate: True leaf (1, '=', 1) and False leaf (0, '=', 1)
  104. pass
  105. elif isinstance(right, ast.Constant) and right.value == 1:
  106. # deprecate: True/False leaf (py expression, '=', 1)
  107. contextual_values.update(_get_expression_contextual_values(left))
  108. else:
  109. raise ValueError()
  110. else:
  111. raise ValueError()
  112. expr = domain.strip()
  113. item_ast = ast.parse(f"({expr})", mode='eval').body
  114. if isinstance(item_ast, ast.Name):
  115. # domain="other_field_domain"
  116. contextual_values.update(_get_expression_contextual_values(item_ast))
  117. else:
  118. extract_from_domain(item_ast)
  119. except ValueError:
  120. raise ValueError("Wrong domain formatting.") from None
  121. value_names = set()
  122. for name in contextual_values:
  123. if name == 'parent':
  124. continue
  125. root = name.split('.')[0]
  126. if root not in IGNORED_IN_EXPRESSION:
  127. value_names.add(name if root == 'parent' else root)
  128. return field_names, value_names
  129. def _get_expression_contextual_values(item_ast):
  130. """ Return all contextual value this ast
  131. eg: ast from '''(
  132. id in [1, 2, 3]
  133. and field_a in parent.truc
  134. and field_b in context.get('b')
  135. or (
  136. True
  137. and bool(context.get('c'))
  138. )
  139. )
  140. returns {'parent', 'parent.truc', 'context', 'bool'}
  141. :param item_ast: ast
  142. :return: set(str)
  143. """
  144. if isinstance(item_ast, ast.Constant):
  145. return set()
  146. if isinstance(item_ast, (ast.List, ast.Tuple)):
  147. values = set()
  148. for item in item_ast.elts:
  149. values |= _get_expression_contextual_values(item)
  150. return values
  151. if isinstance(item_ast, ast.Name):
  152. return {item_ast.id}
  153. if isinstance(item_ast, ast.Attribute):
  154. values = _get_expression_contextual_values(item_ast.value)
  155. if len(values) == 1:
  156. path = sorted(list(values)).pop()
  157. values = {f"{path}.{item_ast.attr}"}
  158. return values
  159. return values
  160. if isinstance(item_ast, ast.Index): # deprecated python ast class for Subscript key
  161. return _get_expression_contextual_values(item_ast.value)
  162. if isinstance(item_ast, ast.Subscript):
  163. values = _get_expression_contextual_values(item_ast.value)
  164. values |= _get_expression_contextual_values(item_ast.slice)
  165. return values
  166. if isinstance(item_ast, ast.Compare):
  167. values = _get_expression_contextual_values(item_ast.left)
  168. for sub_ast in item_ast.comparators:
  169. values |= _get_expression_contextual_values(sub_ast)
  170. return values
  171. if isinstance(item_ast, ast.BinOp):
  172. values = _get_expression_contextual_values(item_ast.left)
  173. values |= _get_expression_contextual_values(item_ast.right)
  174. return values
  175. if isinstance(item_ast, ast.BoolOp):
  176. values = set()
  177. for ast_value in item_ast.values:
  178. values |= _get_expression_contextual_values(ast_value)
  179. return values
  180. if isinstance(item_ast, ast.UnaryOp):
  181. return _get_expression_contextual_values(item_ast.operand)
  182. if isinstance(item_ast, ast.Call):
  183. values = _get_expression_contextual_values(item_ast.func)
  184. for ast_arg in item_ast.args:
  185. values |= _get_expression_contextual_values(ast_arg)
  186. return values
  187. if isinstance(item_ast, ast.IfExp):
  188. values = _get_expression_contextual_values(item_ast.test)
  189. values |= _get_expression_contextual_values(item_ast.body)
  190. values |= _get_expression_contextual_values(item_ast.orelse)
  191. return values
  192. if isinstance(item_ast, ast.Dict):
  193. values = set()
  194. for item in item_ast.keys:
  195. values |= _get_expression_contextual_values(item)
  196. for item in item_ast.values:
  197. values |= _get_expression_contextual_values(item)
  198. return values
  199. raise ValueError(f"Undefined item {item_ast!r}.")
  200. def get_expression_field_names(expression):
  201. """ Return all field name used by this expression
  202. eg: expression = '''(
  203. id in [1, 2, 3]
  204. and field_a in parent.truc.id
  205. and field_b in context.get('b')
  206. or (True and bool(context.get('c')))
  207. )
  208. returns {'parent', 'parent.truc', 'parent.truc.id', 'context', 'context.get'}
  209. :param expression: str
  210. :param ignored: set contains the value name to ignore.
  211. Add '.' to ignore attributes (eg: {'parent.'} will
  212. ignore 'parent.truc' and 'parent.truc.id')
  213. :return: set(str)
  214. """
  215. if not expression:
  216. return set()
  217. item_ast = ast.parse(expression.strip(), mode='eval').body
  218. contextual_values = _get_expression_contextual_values(item_ast)
  219. value_names = set()
  220. for name in contextual_values:
  221. if name == 'parent':
  222. continue
  223. root = name.split('.')[0]
  224. if root not in IGNORED_IN_EXPRESSION:
  225. value_names.add(name if root == 'parent' else root)
  226. return value_names
  227. def get_dict_asts(expr):
  228. """ Check that the given string or AST node represents a dict expression
  229. where all keys are string literals, and return it as a dict mapping string
  230. keys to the AST of values.
  231. """
  232. if isinstance(expr, str):
  233. expr = ast.parse(expr.strip(), mode='eval').body
  234. if not isinstance(expr, ast.Dict):
  235. raise ValueError("Non-dict expression")
  236. if not all((isinstance(key, ast.Constant) and isinstance(key.value, str)) for key in expr.keys):
  237. raise ValueError("Non-string literal dict key")
  238. return {key.value: val for key, val in zip(expr.keys, expr.values)}
  239. def _check(condition, explanation):
  240. if not condition:
  241. raise ValueError("Expression is not a valid domain: %s" % explanation)
  242. def valid_view(arch, **kwargs):
  243. for pred in _validators[arch.tag]:
  244. check = pred(arch, **kwargs)
  245. if not check:
  246. _logger.warning("Invalid XML: %s", pred.__doc__)
  247. return False
  248. return True
  249. def validate(*view_types):
  250. """ Registers a view-validation function for the specific view types
  251. """
  252. def decorator(fn):
  253. for arch in view_types:
  254. _validators[arch].append(fn)
  255. return fn
  256. return decorator
  257. def relaxng(view_type):
  258. """ Return a validator for the given view type, or None. """
  259. if view_type not in _relaxng_cache:
  260. with tools.file_open(os.path.join('base', 'rng', '%s_view.rng' % view_type)) as frng:
  261. try:
  262. relaxng_doc = etree.parse(frng)
  263. _relaxng_cache[view_type] = etree.RelaxNG(relaxng_doc)
  264. except Exception:
  265. _logger.exception('Failed to load RelaxNG XML schema for views validation')
  266. _relaxng_cache[view_type] = None
  267. return _relaxng_cache[view_type]
  268. @validate('calendar', 'graph', 'pivot', 'search', 'list', 'activity')
  269. def schema_valid(arch, **kwargs):
  270. """ Get RNG validator and validate RNG file."""
  271. validator = relaxng(arch.tag)
  272. if validator and not validator.validate(arch):
  273. for error in validator.error_log:
  274. _logger.warning("%s", error)
  275. return False
  276. return True
上海开阖软件有限公司 沪ICP备12045867号-1