[CLEAN] ir_qweb: simplified and cleaned QWebcontext implementation
- now using openerp.tools.safe_eval, instead of a custom eval with custom builtins - removed undefined_handler, hardcoded to a lambda function that returns None for a missing attribute tools.safe_eval: added a parameter locals_builtins. This allows to copy the builtins in the locals. This hack is due to the fact that the locals always returns None, allowing to simplify templates. Otherwise we would have to test the existence of each variable before actually using it. However as the locals always return None for every key, the globals are never checked. Copying the builtins inside the local allows to have a complete locals, but sligtly break the globals/locals separation. To be reviewed and approved. bzr revid: tde@openerp.com-20140116182750-1rnw8iljt5a9gb4u
This commit is contained in:
parent
8af1afb1a0
commit
c76a3aba8f
|
@ -1,52 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import collections
|
||||
import cStringIO
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
import urllib
|
||||
import xml # FIXME use lxml and etree
|
||||
import xml # FIXME use lxml and etree
|
||||
|
||||
import babel
|
||||
import babel.dates
|
||||
import dateutil.relativedelta
|
||||
import werkzeug.utils
|
||||
from PIL import Image
|
||||
|
||||
import openerp.tools
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.osv import osv, orm, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
BUILTINS = {
|
||||
'False': False,
|
||||
'None': None,
|
||||
'True': True,
|
||||
'abs': abs,
|
||||
'bool': bool,
|
||||
'dict': dict,
|
||||
'filter': filter,
|
||||
'len': len,
|
||||
'list': list,
|
||||
'map': map,
|
||||
'max': max,
|
||||
'min': min,
|
||||
'reduce': reduce,
|
||||
'repr': repr,
|
||||
'round': round,
|
||||
'set': set,
|
||||
'str': str,
|
||||
'tuple': tuple,
|
||||
'quote': urllib.quote,
|
||||
'urlencode': urllib.urlencode,
|
||||
'datetime': datetime,
|
||||
# dateutil.relativedelta is an old-style class and cannot be directly
|
||||
# instanciated wihtin a jinja2 expression, so a lambda "proxy" is
|
||||
# is needed, apparently.
|
||||
'relativedelta': lambda *a, **kw : dateutil.relativedelta.relativedelta(*a, **kw),
|
||||
}
|
||||
|
||||
# BUILTINS = {
|
||||
# 'False': False,
|
||||
# 'None': None,
|
||||
# 'True': True,
|
||||
# 'abs': abs,
|
||||
# 'bool': bool,
|
||||
# 'dict': dict,
|
||||
# 'filter': filter,
|
||||
# 'len': len,
|
||||
# 'list': list,
|
||||
# 'map': map,
|
||||
# 'max': max,
|
||||
# 'min': min,
|
||||
# 'reduce': reduce,
|
||||
# 'repr': repr,
|
||||
# 'round': round,
|
||||
# 'set': set,
|
||||
# 'str': str,
|
||||
# 'tuple': tuple,
|
||||
# # 'quote': urllib.quote,
|
||||
# # 'urlencode': urllib.urlencode,
|
||||
# 'datetime': datetime,
|
||||
# # dateutil.relativedelta is an old-style class and cannot be directly
|
||||
# # instanciated wihtin a jinja2 expression, so a lambda "proxy" is
|
||||
# # is needed, apparently.
|
||||
# # 'relativedelta': lambda *a, **kw : dateutil.relativedelta.relativedelta(*a, **kw),
|
||||
# }
|
||||
|
||||
|
||||
class QWebException(Exception):
|
||||
def __init__(self, message, template=None, node=None, attribute=None):
|
||||
|
@ -55,59 +57,27 @@ class QWebException(Exception):
|
|||
self.node = node
|
||||
self.attribute = attribute
|
||||
|
||||
## We use a jinja2 sandboxed environment to render qWeb templates.
|
||||
#from openerp.tools.safe_eval import safe_eval as eval
|
||||
#from jinja2.sandbox import SandboxedEnvironment
|
||||
#from jinja2.exceptions import SecurityError, UndefinedError
|
||||
#UNSAFE = ["browse", "search", "read", "unlink", "read_group"]
|
||||
#SAFE = ["_name"]
|
||||
|
||||
class QWebContext(dict):
|
||||
def __init__(self, cr, uid, data, undefined_handler=None, loader=None,
|
||||
templates=None, context=None):
|
||||
def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
|
||||
self.cr = cr
|
||||
self.uid = uid
|
||||
self.loader = loader
|
||||
self.undefined_handler = undefined_handler
|
||||
self.templates = templates or {}
|
||||
self.context = context
|
||||
dic = BUILTINS.copy()
|
||||
dic.update(data)
|
||||
dic = dict(data)
|
||||
super(QWebContext, self).__init__(dic)
|
||||
self['defined'] = lambda key: key in self
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self:
|
||||
return self.get(key)
|
||||
elif not self.undefined_handler:
|
||||
raise NameError("QWeb: name %r is not defined while rendering template %r" % (key, self.get('__template__')))
|
||||
else:
|
||||
return self.get(key, self.undefined_handler(key, self))
|
||||
|
||||
def safe_eval(self, expr):
|
||||
# This is too slow, we should cached compiled expressions attribute of
|
||||
# qweb to will be changed into a model object ir.qweb.
|
||||
#
|
||||
# The cache should be on qweb, and qweb context contructor take qweb as
|
||||
# argument to store the cache.
|
||||
#
|
||||
#class QWebSandboxedEnvironment(SandboxedEnvironment):
|
||||
# def is_safe_attribute(self, obj, attr, value):
|
||||
# if str(attr) in SAFE:
|
||||
# res = True
|
||||
# else:
|
||||
# res = super(QWebSandboxedEnvironment, self).is_safe_attribute(obj, attr, value)
|
||||
# if str(attr) in UNSAFE or not res:
|
||||
# raise SecurityError("access to attribute '%s' of '%s' object is unsafe." % (attr,obj))
|
||||
# return res
|
||||
#env = qWebSandboxedEnvironment(variable_start_string="${", variable_end_string="}")
|
||||
#env.globals.update(context)
|
||||
#env.compile_expression(expr)()
|
||||
return eval(expr, None, self)
|
||||
locals_dict = collections.defaultdict(lambda: None)
|
||||
locals_dict.update(self)
|
||||
locals_dict.pop('cr', None)
|
||||
locals_dict.pop('loader', None)
|
||||
return eval(expr, None, locals_dict, nocopy=True, locals_builtins=True)
|
||||
|
||||
def copy(self):
|
||||
return QWebContext(self.cr, self.uid, dict.copy(self),
|
||||
undefined_handler=self.undefined_handler,
|
||||
loader=self.loader,
|
||||
templates=self.templates,
|
||||
context=self.context)
|
||||
|
@ -257,8 +227,7 @@ class QWeb(orm.AbstractModel):
|
|||
xmlid = imd.search_read(cr, uid, domain, ['module', 'name'])[0]
|
||||
return '%s.%s' % (xmlid['module'], xmlid['name'])
|
||||
|
||||
def render(self, cr, uid, id_or_xml_id, v=None, loader=None,
|
||||
undefined_handler=None, context=None):
|
||||
def render(self, cr, uid, id_or_xml_id, v=None, loader=None, context=None):
|
||||
if isinstance(id_or_xml_id, list):
|
||||
id_or_xml_id = id_or_xml_id[0]
|
||||
tname = id_or_xml_id
|
||||
|
@ -268,8 +237,7 @@ class QWeb(orm.AbstractModel):
|
|||
if v is None:
|
||||
v = {}
|
||||
if not isinstance(v, QWebContext):
|
||||
v = QWebContext(cr, uid, v, undefined_handler=undefined_handler,
|
||||
loader=loader, context=context)
|
||||
v = QWebContext(cr, uid, v, loader=loader, context=context)
|
||||
v['__template__'] = tname
|
||||
stack = v.get('__stack__', [])
|
||||
if stack:
|
||||
|
|
|
@ -795,10 +795,7 @@ class view(osv.osv):
|
|||
def loader(name):
|
||||
return self.read_template(cr, uid, name, context=context)
|
||||
|
||||
return self.pool[engine].render(
|
||||
cr, uid, id_or_xml_id, values,
|
||||
loader=loader, undefined_handler=lambda key, v: None,
|
||||
context=context)
|
||||
return self.pool[engine].render(cr, uid, id_or_xml_id, values, loader=loader, context=context)
|
||||
|
||||
# maybe used to print the workflow ?
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ def _import(name, globals=None, locals=None, fromlist=None, level=-1):
|
|||
return __import__(name, globals, locals, level)
|
||||
raise ImportError(name)
|
||||
|
||||
def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False):
|
||||
def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False, locals_builtins=False):
|
||||
"""safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
|
||||
|
||||
System-restricted Python expression evaluation
|
||||
|
@ -218,29 +218,34 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
|||
locals_dict = dict(locals_dict)
|
||||
|
||||
globals_dict.update(
|
||||
__builtins__ = {
|
||||
'__import__': _import,
|
||||
'True': True,
|
||||
'False': False,
|
||||
'None': None,
|
||||
'str': str,
|
||||
'globals': locals,
|
||||
'locals': locals,
|
||||
'bool': bool,
|
||||
'dict': dict,
|
||||
'list': list,
|
||||
'tuple': tuple,
|
||||
'map': map,
|
||||
'abs': abs,
|
||||
'min': min,
|
||||
'max': max,
|
||||
'reduce': reduce,
|
||||
'filter': filter,
|
||||
'round': round,
|
||||
'len': len,
|
||||
'set' : set
|
||||
}
|
||||
__builtins__={
|
||||
'__import__': _import,
|
||||
'True': True,
|
||||
'False': False,
|
||||
'None': None,
|
||||
'str': str,
|
||||
'globals': locals,
|
||||
'locals': locals,
|
||||
'bool': bool,
|
||||
'dict': dict,
|
||||
'list': list,
|
||||
'tuple': tuple,
|
||||
'map': map,
|
||||
'abs': abs,
|
||||
'min': min,
|
||||
'max': max,
|
||||
'reduce': reduce,
|
||||
'filter': filter,
|
||||
'round': round,
|
||||
'len': len,
|
||||
'set': set,
|
||||
'repr': repr,
|
||||
}
|
||||
)
|
||||
if locals_builtins:
|
||||
if locals_dict is None:
|
||||
locals_dict = {}
|
||||
locals_dict.update(globals_dict.get('__builtins__'))
|
||||
c = test_expr(expr, _SAFE_OPCODES, mode=mode)
|
||||
try:
|
||||
return eval(c, globals_dict, locals_dict)
|
||||
|
|
Loading…
Reference in New Issue