[MERGE] [CLEAN] Part I of cleaning and improvements of the server branch of website-al, after review with our Masters. All Glory to Our Masters.

[CLEAN] res_config, res_users: cleaned call to imd.get_object() + code cleaning
- res_config: raise by default, no need to set the argument at True
- res_users: directly embedded get_user_groups_view code inside update_user_groups_view.
The try / except is still necessary, because when installing a new db, groups and users
are created before the user_groups_view view effectively exist. Due to some circular
references, the try / except is therefore necessary to install a new db.

[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
- cleaned code, removed now dead BUILTINS, removed commented code
- tools.safe_eval: added a parameter locals_builtins. This allows to copy the globals builtins in the locals. This modification 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 slightly break the globals/locals separation.

[CLEAN] Misc
- setup.py: reverted website-al unnecessary change
- oe: reverted website-al unnecessary change

[DOC] renamed misc_qweb.rst file into ir_qweb.rst

bzr revid: tde@openerp.com-20140116184144-fd5h4a94we13mm72
Thibault Delavallée 9 years ago
commit 382efffb58

@ -11,4 +11,4 @@ Miscellanous


@ -1,4 +1,4 @@
#! /usr/bin/env python
#! /usr/bin/env python2
if __name__ == '__main__':
import openerpcommand.main

@ -1,52 +1,25 @@
# -*- 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__)
'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 +28,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 = 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__')))
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="}")
return eval(expr, None, self)
locals_dict = collections.defaultdict(lambda: None)
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),
@ -257,8 +198,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 +208,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,
return self.pool[engine].render(cr, uid, id_or_xml_id, values, loader=loader, context=context)
# maybe used to print the workflow ?

@ -446,7 +446,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
ir_module = self.pool['ir.module.module']
def ref(xml_id):
mod, xml = xml_id.split('.', 1)
return ir_model_data.get_object(cr, uid, mod, xml, context=context, check_existence_and_raise=True)
return ir_model_data.get_object(cr, uid, mod, xml, context=context)
defaults, groups, modules, others = [], [], [], []
for name, field in self._columns.items():

@ -681,8 +681,13 @@ class groups_view(osv.osv):
def update_user_groups_view(self, cr, uid, context=None):
# the view with id 'base.user_groups_view' inherits the user form view,
# and introduces the reified group fields
view = self.get_user_groups_view(cr, uid, context)
if view:
# we have to try-catch this, because at first init the view does not exist
# but we are already creating some basic groups
view = self.pool['ir.model.data'].get_object(cr, SUPERUSER_ID, 'base', 'user_groups_view', context=context, check_existence_and_raise=False)
except ValueError:
view = False
if view and view.exists() and view._table_name == 'ir.ui.view':
xml1, xml2 = [], []
xml1.append(E.separator(string=_('Application'), colspan="4"))
for app, kind, gs in self.get_groups_by_application(cr, uid, context):
@ -707,14 +712,6 @@ class groups_view(osv.osv):
view.write({'arch': xml_content})
return True
def get_user_groups_view(self, cr, uid, context=None):
view = self.pool['ir.model.data'].get_object(cr, SUPERUSER_ID, 'base', 'user_groups_view', context=context, check_existence_and_raise=False)
assert view and view.exists() and view._table_name == 'ir.ui.view'
except Exception:
view = False
return view
def get_application_groups(self, cr, uid, domain=None, context=None):
return self.search(cr, uid, domain or [])

@ -111,16 +111,5 @@
"access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0
"access_res_font_all","res_res_font all","model_res_font",,1,0,0,0
"access_res_font_group_user","res_res_font group_user","model_res_font","group_user",1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
111 access_ir_mail_server ir_mail_server model_ir_mail_server group_system 1 1 1 1
112 access_ir_actions_client ir_actions_client all model_ir_actions_client 1 0 0 0
113 access_ir_needaction_mixin ir_needaction_mixin model_ir_needaction_mixin 1 1 1 1
access_ir_qweb access_ir_qweb model_ir_qweb 0 0 0 0
access_ir_qweb_field access_ir_qweb_field model_ir_qweb_field 0 0 0 0
access_ir_qweb_field_float access_ir_qweb_field_float model_ir_qweb_field_float 0 0 0 0
access_ir_qweb_field_date access_ir_qweb_field_date model_ir_qweb_field_date 0 0 0 0
access_ir_qweb_field_datetime access_ir_qweb_field_datetime model_ir_qweb_field_datetime 0 0 0 0
access_ir_qweb_field_text access_ir_qweb_field_text model_ir_qweb_field_text 0 0 0 0
access_ir_qweb_field_selection access_ir_qweb_field_selection model_ir_qweb_field_selection 0 0 0 0
access_ir_qweb_field_many2one access_ir_qweb_field_many2one model_ir_qweb_field_many2one 0 0 0 0
access_ir_qweb_field_html access_ir_qweb_field_html model_ir_qweb_field_html 0 0 0 0
access_ir_qweb_field_image access_ir_qweb_field_image model_ir_qweb_field_image 0 0 0 0
access_ir_qweb_field_monetary access_ir_qweb_field_monetary model_ir_qweb_field_monetary 0 0 0 0
114 access_res_font_all res_res_font all model_res_font 1 0 0 0
115 access_res_font_group_user res_res_font group_user model_res_font group_user 1 1 1 1

@ -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)
__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
'__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 = {}
c = test_expr(expr, _SAFE_OPCODES, mode=mode)
return eval(c, globals_dict, locals_dict)

@ -75,7 +75,7 @@ def py2exe_options():
"skip_archive": 1,
"optimize": 0, # keep the assert running, because the integrated tests rely on them.
"dist_dir": 'dist',
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml"],
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
"excludes" : ["Tkconstants","Tkinter","tcl"],