From f566d20c74de365020ea06ec581ced85d14b8484 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 23 Jan 2014 13:46:51 +0100 Subject: [PATCH 1/7] [REF] Replace g_att -> generated_att, t_att template_att, an -> att_name, av -> att_value, v = qcontext bzr revid: stw@openerp.com-20140123124651-wi4l32q0x4cy5o6s --- openerp/addons/base/ir/ir_qweb.py | 326 +++++++++++++++--------------- 1 file changed, 163 insertions(+), 163 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 77a3c8435d6..c0b68d463b3 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -116,30 +116,32 @@ class QWeb(orm.AbstractModel): :param str prefix: :return: dict """ + n_prefix = len(prefix) return dict( - (name[len(prefix):].replace('_', '-'), getattr(type(self), name)) + (name[n_prefix:].replace('_', '-'), getattr(type(self), name)) for name in dir(self) - if name.startswith(prefix)) + if name.startswith(prefix) + ) def register_tag(self, tag, func): self._render_tag[tag] = func - def load_document(self, x, qcontext): + def load_document(self, document, qcontext): """ Loads an XML document and installs any contained template in the engine """ - if hasattr(x, 'documentElement'): - dom = x - elif x.startswith("%s" % tuple( - v if isinstance(v, str) else v.encode('utf-8') - for v in (name, g_att, inner, name)) + qwebcontext if isinstance(qwebcontext, str) else qwebcontext.encode('utf-8') + for qwebcontext in (name, generated_attributes, inner, name) + ) else: - return "<%s%s/>" % (name, g_att) + return "<%s%s/>" % (name, generated_attributes) # Attributes - def render_att_att(self, e, an, av, v): - if an.startswith("t-attf-"): - att, val = an[7:], self.eval_format(av, v) - elif an.startswith("t-att-"): - att, val = an[6:], self.eval(av, v) + def render_att_att(self, element, attribute_name, attribute_value, qwebcontext): + if attribute_name.startswith("t-attf-"): + att, val = attribute_name[7:], self.eval_format(attribute_value, qwebcontext) + elif attribute_name.startswith("t-att-"): + att, val = attribute_name[6:], self.eval(attribute_value, qwebcontext) if isinstance(val, unicode): val = val.encode("utf8") else: - att, val = self.eval_object(av, v) + att, val = self.eval_object(attribute_value, qwebcontext) return val and ' %s="%s"' % (att, werkzeug.utils.escape(val)) or " " - def render_att_href(self, e, an, av, v): - return self.url_for(e, an, av, v) + def render_att_href(self, element, attribute_name, attribute_value, qwebcontext): + return self.url_for(element, attribute_name, attribute_value, qwebcontext) - def render_att_src(self, e, an, av, v): - return self.url_for(e, an, av, v) + def render_att_src(self, element, attribute_name, attribute_value, qwebcontext): + return self.url_for(element, attribute_name, attribute_value, qwebcontext) - def render_att_action(self, e, an, av, v): - return self.url_for(e, an, av, v) + def render_att_action(self, element, attribute_name, attribute_value, qwebcontext): + return self.url_for(element, attribute_name, attribute_value, qwebcontext) - def url_for(self, e, an, av, v): - if 'url_for' not in v: + def url_for(self, element, attribute_name, attribute_value, qwebcontext): + if 'url_for' not in qwebcontext: raise KeyError("qweb: no 'url_for' found in context") # Temporary implementation of t-keep-query until qweb py v2 - keep_query = e.attributes.get('t-keep-query') + keep_query = element.attributes.get('t-keep-query') if keep_query: - params = self.eval_format(keep_query.value, v) + params = self.eval_format(keep_query.value, qwebcontext) keep_query = [q.strip() for q in params.split(',')] - path = str(v['url_for'](self.eval_format(av, v), keep_query=keep_query)) - return ' %s="%s"' % (an[2:], werkzeug.utils.escape(path)) + path = str(qwebcontext['url_for'](self.eval_format(attribute_value, qwebcontext), keep_query=keep_query)) + return ' %s="%s"' % (attribute_name[2:], werkzeug.utils.escape(path)) # Tags - def render_tag_raw(self, e, t_att, g_att, v): - inner = self.eval_str(t_att["raw"], v) - return self.render_element(e, t_att, g_att, v, inner) + def render_tag_raw(self, element, template_attributes, generated_attributes, qwebcontext): + inner = self.eval_str(template_attributes["raw"], qwebcontext) + return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner) - def render_tag_esc(self, e, t_att, g_att, v): - inner = werkzeug.utils.escape(self.eval_str(t_att["esc"], v)) - return self.render_element(e, t_att, g_att, v, inner) + def render_tag_esc(self, element, template_attributes, generated_attributes, qwebcontext): + inner = werkzeug.utils.escape(self.eval_str(template_attributes["esc"], qwebcontext)) + return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner) - def render_tag_foreach(self, e, t_att, g_att, v): - expr = t_att["foreach"] - enum = self.eval_object(expr, v) + def render_tag_foreach(self, element, template_attributes, generated_attributes, qwebcontext): + expr = template_attributes["foreach"] + enum = self.eval_object(expr, qwebcontext) if enum is not None: - var = t_att.get('as', expr).replace('.', '_') - d = v.copy() + var = template_attributes.get('as', expr).replace('.', '_') + copy_qwebcontext = qwebcontext.copy() size = -1 if isinstance(enum, (list, tuple)): size = len(enum) elif hasattr(enum, 'count'): size = enum.count() - d["%s_size" % var] = size - d["%s_all" % var] = enum + copy_qwebcontext["%s_size" % var] = size + copy_qwebcontext["%s_all" % var] = enum index = 0 ru = [] for i in enum: - d["%s_value" % var] = i - d["%s_index" % var] = index - d["%s_first" % var] = index == 0 - d["%s_even" % var] = index % 2 - d["%s_odd" % var] = (index + 1) % 2 - d["%s_last" % var] = index + 1 == size + copy_qwebcontext["%s_value" % var] = i + copy_qwebcontext["%s_index" % var] = index + copy_qwebcontext["%s_first" % var] = index == 0 + copy_qwebcontext["%s_even" % var] = index % 2 + copy_qwebcontext["%s_odd" % var] = (index + 1) % 2 + copy_qwebcontext["%s_last" % var] = index + 1 == size if index % 2: - d["%s_parity" % var] = 'odd' + copy_qwebcontext["%s_parity" % var] = 'odd' else: - d["%s_parity" % var] = 'even' - if 'as' in t_att: - d[var] = i + copy_qwebcontext["%s_parity" % var] = 'even' + if 'as' in template_attributes: + copy_qwebcontext[var] = i elif isinstance(i, dict): - d.update(i) - ru.append(self.render_element(e, t_att, g_att, d)) + copy_qwebcontext.update(i) + ru.append(self.render_element(element, template_attributes, generated_attributes, copy_qwebcontext)) index += 1 return "".join(ru) else: - template = v.get('__template__') - e = NameError("QWeb: foreach enumerator %r is not defined while rendering template %r" % (expr, template)) - e.qweb_template = template - raise e + template = qwebcontext.get('__template__') + exception_to_raise = NameError("QWeb: foreach enumerator %r is not defined while rendering template %r" % (expr, template)) + exception_to_raise.qweb_template = template + raise exception_to_raise - def render_tag_if(self, e, t_att, g_att, v): - if self.eval_bool(t_att["if"], v): - return self.render_element(e, t_att, g_att, v) - else: - return "" - - def render_tag_call(self, e, t_att, g_att, v): - d = v.copy() - d[0] = self.render_element(e, t_att, g_att, d) - - return self.render(None, None, self.eval_format(t_att["call"], d), d) - - def render_tag_set(self, e, t_att, g_att, v): - if "value" in t_att: - v[t_att["set"]] = self.eval_object(t_att["value"], v) - elif "valuef" in t_att: - v[t_att["set"]] = self.eval_format(t_att["valuef"], v) - else: - v[t_att["set"]] = self.render_element(e, t_att, g_att, v) + def render_tag_if(self, element, template_attributes, generated_attributes, qwebcontext): + if self.eval_bool(template_attributes["if"], qwebcontext): + return self.render_element(element, template_attributes, generated_attributes, qwebcontext) return "" - def render_tag_field(self, e, t_att, g_att, v): + def render_tag_call(self, element, template_attributes, generated_attributes, qwebcontext): + d = qwebcontext.copy() + d[0] = self.render_element(element, template_attributes, generated_attributes, d) + + return self.render(None, None, self.eval_format(template_attributes["call"], d), d) + + def render_tag_set(self, element, template_attributes, generated_attributes, qwebcontext): + if "value" in template_attributes: + qwebcontext[template_attributes["set"]] = self.eval_object(template_attributes["value"], qwebcontext) + elif "valuef" in template_attributes: + qwebcontext[template_attributes["set"]] = self.eval_format(template_attributes["valuef"], qwebcontext) + else: + qwebcontext[template_attributes["set"]] = self.render_element(element, template_attributes, generated_attributes, qwebcontext) + return "" + + def render_tag_field(self, element, template_attributes, generated_attributes, qwebcontext): """ eg: +1 555 555 8069""" - node_name = e.nodeName + node_name = element.nodeName assert node_name not in ("table", "tbody", "thead", "tfoot", "tr", "td", "li", "ul", "ol", "dl", "dt", "dd"),\ "RTE widgets do not work correctly on %r elements" % node_name assert node_name != 't',\ "t-field can not be used on a t element, provide an actual HTML node" - record, field_name = t_att["field"].rsplit('.', 1) - record = self.eval_object(record, v) + record, field_name = template_attributes["field"].rsplit('.', 1) + record = self.eval_object(record, qwebcontext) column = record._model._all_columns[field_name].column - options = json.loads(t_att.get('field-options') or '{}') + options = json.loads(template_attributes.get('field-options') or '{}') field_type = get_field_type(column, options) converter = self.get_converter_for(field_type) - return converter.to_html(v.cr, v.uid, field_name, record, options, - e, t_att, g_att, v, context=v.context) + return converter.to_html(qwebcontext.cr, qwebcontext.uid, field_name, record, options, + element, template_attributes, generated_attributes, qwebcontext, context=qwebcontext.context) def get_converter_for(self, field_type): return self.pool.get('ir.qweb.field.' + field_type, From cecd748369eb27f417263f10b9b6b47d932c3d77 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 23 Jan 2014 15:40:22 +0100 Subject: [PATCH 2/7] [IMP] res_config_settings: in group fields, allow several groups as targets bzr revid: rco@openerp.com-20140123144022-6gu8s5kn22bw70ng --- openerp/addons/base/res/res_config.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 2dedaccbc12..c9d32c6bffa 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -415,6 +415,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): * For a boolean field like 'group_XXX', ``execute`` adds/removes 'implied_group' to/from the implied groups of 'group', depending on the field's value. By default 'group' is the group Employee. Groups are given by their xml id. + The attribute 'group' may contain several xml ids, separated by commas. * For a boolean field like 'module_XXX', ``execute`` triggers the immediate installation of the module named 'XXX' if the field has value ``True``. @@ -437,7 +438,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): """ return a dictionary with the fields classified by category:: { 'default': [('default_foo', 'model', 'foo'), ...], - 'group': [('group_bar', browse_group, browse_implied_group), ...], + 'group': [('group_bar', [browse_group], browse_implied_group), ...], 'module': [('module_baz', browse_module), ...], 'other': ['other_field', ...], } @@ -453,8 +454,8 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): if name.startswith('default_') and hasattr(field, 'default_model'): defaults.append((name, field.default_model, name[8:])) elif name.startswith('group_') and isinstance(field, fields.boolean) and hasattr(field, 'implied_group'): - field_group = getattr(field, 'group', 'base.group_user') - groups.append((name, ref(field_group), ref(field.implied_group))) + field_groups = getattr(field, 'group', 'base.group_user').split(',') + groups.append((name, map(ref, field_groups), ref(field.implied_group))) elif name.startswith('module_') and isinstance(field, fields.boolean): mod_ids = ir_module.search(cr, uid, [('name', '=', name[7:])]) record = ir_module.browse(cr, uid, mod_ids[0], context) if mod_ids else None @@ -477,8 +478,8 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): res[name] = value # groups: which groups are implied by the group Employee - for name, group, implied_group in classified['group']: - res[name] = implied_group in group.implied_ids + for name, groups, implied_group in classified['group']: + res[name] = all(implied_group in group.implied_ids for group in groups) # modules: which modules are installed/to install for name, module in classified['module']: @@ -497,6 +498,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): ir_values = self.pool['ir.values'] ir_module = self.pool['ir.module.module'] + res_groups = self.pool['res.groups'] classified = self._get_classified_fields(cr, uid, context) @@ -507,12 +509,16 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): ir_values.set_default(cr, SUPERUSER_ID, model, field, config[name]) # group fields: modify group / implied groups - for name, group, implied_group in classified['group']: + for name, groups, implied_group in classified['group']: + gids = map(int, groups) if config[name]: - group.write({'implied_ids': [(4, implied_group.id)]}) + res_groups.write(cr, uid, gids, {'implied_ids': [(4, implied_group.id)]}, context=context) else: - group.write({'implied_ids': [(3, implied_group.id)]}) - implied_group.write({'users': [(3, u.id) for u in group.users]}) + res_groups.write(cr, uid, gids, {'implied_ids': [(3, implied_group.id)]}, context=context) + uids = set() + for group in groups: + uids.update(map(int, group.users)) + implied_group.write({'users': [(3, uid) for uid in uids]}) # other fields: execute all methods that start with 'set_' for method in dir(self): From ca0a49ccfd21e738da6ca3b04f4c10f704b77cf5 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 23 Jan 2014 15:57:56 +0100 Subject: [PATCH 3/7] [FIX] res_config_settings: stupid bug, I was reassigning uid into a method! bzr revid: rco@openerp.com-20140123145756-9ggykftlva4v1ar1 --- openerp/addons/base/res/res_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index c9d32c6bffa..4fe6ff1989e 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -518,7 +518,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): uids = set() for group in groups: uids.update(map(int, group.users)) - implied_group.write({'users': [(3, uid) for uid in uids]}) + implied_group.write({'users': [(3, u) for u in uids]}) # other fields: execute all methods that start with 'set_' for method in dir(self): From 35050c4ba0de4fc3d2a3db8bd4b534ced3069038 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 23 Jan 2014 16:12:07 +0100 Subject: [PATCH 4/7] IMP] Launch a 404 when ModelConverter's browse records can't resolve _rec_name. Also serves as automatic preload bzr revid: fme@openerp.com-20140123151207-3wp77801g44b2822 --- openerp/addons/base/ir/ir_http.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index 7d0c4be8bc1..a4eaf216381 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -15,9 +15,7 @@ from openerp.osv import osv, orm _logger = logging.getLogger(__name__) - -# FIXME: replace by proxy on request.uid? -_uid = object() +UID_PLACEHOLDER = object() class ModelConverter(werkzeug.routing.BaseConverter): @@ -29,7 +27,7 @@ class ModelConverter(werkzeug.routing.BaseConverter): def to_python(self, value): m = re.match(self.regex, value) return request.registry[self.model].browse( - request.cr, _uid, int(m.group(1)), context=request.context) + request.cr, UID_PLACEHOLDER, int(m.group(1)), context=request.context) def to_url(self, value): return value.id @@ -46,7 +44,7 @@ class ModelsConverter(werkzeug.routing.BaseConverter): # TODO: # - raise routing.ValidationError() if no browse record can be createdm # - support slug - return request.registry[self.model].browse(request.cr, _uid, [int(i) for i in value.split(',')], context=request.context) + return request.registry[self.model].browse(request.cr, UID_PLACEHOLDER, [int(i) for i in value.split(',')], context=request.context) def to_url(self, value): return ",".join(i.id for i in value) @@ -108,8 +106,12 @@ class ir_http(osv.AbstractModel): # post process arg to set uid on browse records for arg in arguments.itervalues(): - if isinstance(arg, orm.browse_record) and arg._uid is _uid: + if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER: arg._uid = request.uid + try: + arg[arg._rec_name] + except KeyError: + return self._handle_exception(werkzeug.exceptions.NotFound()) # set and execute handler try: From 3cad7c9812b6c9347cf4f1655144b7e459eb76d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 23 Jan 2014 17:25:52 +0100 Subject: [PATCH 5/7] [FIX] ir_qweb: fixed a forgotten av -> attribute_value bzr revid: tde@openerp.com-20140123162552-akydzhiu0ckushy2 --- openerp/addons/base/ir/ir_qweb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index c0b68d463b3..56cb50ec341 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -239,11 +239,11 @@ class QWeb(orm.AbstractModel): t_render = attribute_name[2:] template_attributes[attribute_name[2:]] = attribute_value else: - generated_attributes += ' %s="%s"' % (attribute_name, werkzeug.utils.escape(av)) + generated_attributes += ' %s="%s"' % (attribute_name, werkzeug.utils.escape(attribute_value)) if 'debug' in template_attributes: debugger = template_attributes.get('debug', 'pdb') - __import__(debugger).set_trace() # pdb, ipdb, pudb, ... + __import__(debugger).set_trace() # pdb, ipdb, pudb, ... if t_render: result = self._render_tag[t_render](self, element, template_attributes, generated_attributes, qwebcontext) else: From 2b6ac77f11391d2ed3e9c2c19baa61c784875882 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Fri, 24 Jan 2014 11:35:46 +0100 Subject: [PATCH 6/7] [IMP] move dispatch()'s post processing code into a method bzr revid: fme@openerp.com-20140124103546-h8an7dehqdc412cn --- openerp/addons/base/ir/ir_http.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index a4eaf216381..4676614c3a7 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -104,14 +104,7 @@ class ir_http(osv.AbstractModel): convert_exception_to( werkzeug.exceptions.Forbidden)) - # post process arg to set uid on browse records - for arg in arguments.itervalues(): - if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER: - arg._uid = request.uid - try: - arg[arg._rec_name] - except KeyError: - return self._handle_exception(werkzeug.exceptions.NotFound()) + self._postprocess_args(arguments) # set and execute handler try: @@ -124,6 +117,16 @@ class ir_http(osv.AbstractModel): return result + def _postprocess_args(self, arguments): + """ post process arg to set uid on browse records """ + for arg in arguments.itervalues(): + if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER: + arg._uid = request.uid + try: + arg[arg._rec_name] + except KeyError: + return self._handle_exception(werkzeug.exceptions.NotFound()) + def routing_map(self): if not hasattr(self, '_routing_map'): _logger.info("Generating routing map") From 76144806afe2accc91fb9eb8275c3a0492c03356 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Fri, 24 Jan 2014 14:21:37 +0100 Subject: [PATCH 7/7] [IMP] If t-field can't get/use the resource, set it's content to None bzr revid: fme@openerp.com-20140124132137-7m8840py82nlib40 --- openerp/addons/base/ir/ir_qweb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 56cb50ec341..2dcf3002204 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -500,6 +500,7 @@ class FieldConverter(osv.AbstractModel): except Exception: _logger.warning("Could not get field %s for model %s", field_name, record._model._name, exc_info=True) + content = None g_att += ''.join( ' %s="%s"' % (name, werkzeug.utils.escape(value))