From c08513763cec5b73fce895a628c73cc5fbe172ff Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 24 Apr 2013 15:58:25 +0200 Subject: [PATCH] [NOTSURE] move __view_look_dom_arch into ir.ui.view Also alter BaseModel._view_look_dom_arch to proxy to method above. bzr revid: xmo@openerp.com-20130424135825-grjfgbjuc4ozzdqy --- openerp/addons/base/ir/ir_ui_view.py | 266 +++++++++++++++++++++++++- openerp/osv/orm.py | 268 +-------------------------- 2 files changed, 269 insertions(+), 265 deletions(-) diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index c91245930b0..db6447bdeac 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -24,9 +24,10 @@ import logging import itertools from lxml import etree import os +import time from openerp import tools -from openerp.osv import fields,osv +from openerp.osv import fields, osv, orm from openerp.tools import graph, SKIPPED_ELEMENT_TYPES from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.translate import _ @@ -539,6 +540,269 @@ class view(osv.osv): 'blank_nodes': blank_nodes, 'node_parent_field': _Model_Field,} + # this really needs to be refixtored + def __view_look_dom(self, cr, user, model, node, view_id, in_tree_view, model_fields, context=None): + """Return the description of the fields in the node. + + In a normal call to this method, node is a complete view architecture + but it is actually possible to give some sub-node (this is used so + that the method can call itself recursively). + + Originally, the field descriptions are drawn from the node itself. + But there is now some code calling fields_get() in order to merge some + of those information in the architecture. + + """ + if context is None: + context = {} + result = False + fields = {} + children = True + + modifiers = {} + Model = self.pool[model] + + def encode(s): + if isinstance(s, unicode): + return s.encode('utf8') + return s + + def check_group(node): + """Apply group restrictions, may be set at view level or model level:: + * at view level this means the element should be made invisible to + people who are not members + * at model level (exclusively for fields, obviously), this means + the field should be completely removed from the view, as it is + completely unavailable for non-members + + :return: True if field should be included in the result of fields_view_get + """ + if node.tag == 'field' and node.get('name') in Model._all_columns: + column = Model._all_columns[node.get('name')].column + if column.groups and not self.user_has_groups(cr, user, + groups=column.groups, + context=context): + node.getparent().remove(node) + fields.pop(node.get('name'), None) + # no point processing view-level ``groups`` anymore, return + return False + if node.get('groups'): + can_see = self.user_has_groups(cr, user, + groups=node.get('groups'), + context=context) + if not can_see: + node.set('invisible', '1') + modifiers['invisible'] = True + if 'attrs' in node.attrib: + del(node.attrib['attrs']) #avoid making field visible later + del(node.attrib['groups']) + return True + + if node.tag in ('field', 'node', 'arrow'): + if node.get('object'): + attrs = {} + views = {} + xml = "
" + for f in node: + if f.tag == 'field': + xml += etree.tostring(f, encoding="utf-8") + xml += "
" + new_xml = etree.fromstring(encode(xml)) + ctx = context.copy() + ctx['base_model_name'] = model + xarch, xfields = self.__view_look_dom_arch(cr, user, node.get('object'), new_xml, view_id, ctx) + views['form'] = { + 'arch': xarch, + 'fields': xfields + } + attrs = {'views': views} + fields = xfields + if node.get('name'): + attrs = {} + try: + if node.get('name') in Model._columns: + column = Model._columns[node.get('name')] + else: + column = Model._inherit_fields[node.get('name')][2] + except Exception: + column = False + + if column: + relation = self.pool[column._obj] if column._obj else None + + children = False + views = {} + for f in node: + if f.tag in ('form', 'tree', 'graph', 'kanban'): + node.remove(f) + ctx = context.copy() + ctx['base_model_name'] = Model + xarch, xfields = self.__view_look_dom_arch(cr, user, column._obj or None, f, view_id, ctx) + views[str(f.tag)] = { + 'arch': xarch, + 'fields': xfields + } + attrs = {'views': views} + if node.get('widget') and node.get('widget') == 'selection': + # Prepare the cached selection list for the client. This needs to be + # done even when the field is invisible to the current user, because + # other events could need to change its value to any of the selectable ones + # (such as on_change events, refreshes, etc.) + + # If domain and context are strings, we keep them for client-side, otherwise + # we evaluate them server-side to consider them when generating the list of + # possible values + # TODO: find a way to remove this hack, by allow dynamic domains + dom = [] + if column._domain and not isinstance(column._domain, basestring): + dom = list(column._domain) + dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time}) + search_context = dict(context) + if column._context and not isinstance(column._context, basestring): + search_context.update(column._context) + attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1) + if (node.get('required') and not int(node.get('required'))) or not column.required: + attrs['selection'].append((False, '')) + fields[node.get('name')] = attrs + + field = model_fields.get(node.get('name')) + if field: + orm.transfer_field_to_modifiers(field, modifiers) + + + elif node.tag in ('form', 'tree'): + result = Model.view_header_get(cr, user, False, node.tag, context) + if result: + node.set('string', result) + in_tree_view = node.tag == 'tree' + + elif node.tag == 'calendar': + for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'): + if node.get(additional_field): + fields[node.get(additional_field)] = {} + + if not check_group(node): + # node must be removed, no need to proceed further with its children + return fields + + # 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, context, in_tree_view) + + # TODO remove attrs couterpart in modifiers when invisible is true ? + + # translate view + if 'lang' in context: + Translations = self.pool['ir.translation'] + if node.text and node.text.strip(): + trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.text.strip()) + if trans: + node.text = node.text.replace(node.text.strip(), trans) + if node.tail and node.tail.strip(): + trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.tail.strip()) + if trans: + node.tail = node.tail.replace(node.tail.strip(), trans) + + if node.get('string') and not result: + trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.get('string')) + if trans == node.get('string') and ('base_model_name' in context): + # If translation is same as source, perhaps we'd have more luck with the alternative model name + # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model + trans = Translations._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string')) + if trans: + node.set('string', trans) + + for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'): + attr_value = node.get(attr_name) + if attr_value: + trans = Translations._get_source(cr, user, model, 'view', context['lang'], attr_value) + if trans: + node.set(attr_name, trans) + + for f in node: + if children or (node.tag == 'field' and f.tag in ('filter','separator')): + fields.update(self.__view_look_dom(cr, user, model, f, view_id, in_tree_view, model_fields, context)) + + orm.transfer_modifiers_to_node(modifiers, node) + return fields + + def _disable_workflow_buttons(self, cr, user, model, node): + """ Set the buttons in node to readonly if the user can't activate them. """ + if user == 1: + # admin user can always activate workflow buttons + return node + + # TODO handle the case of more than one workflow for a model or multiple + # transitions with different groups and same signal + usersobj = self.pool.get('res.users') + buttons = (n for n in node.getiterator('button') if n.get('type') != 'object') + for button in buttons: + user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id'] + cr.execute("""SELECT DISTINCT t.group_id + FROM wkf + INNER JOIN wkf_activity a ON a.wkf_id = wkf.id + INNER JOIN wkf_transition t ON (t.act_to = a.id) + WHERE wkf.osv = %s + AND t.signal = %s + AND t.group_id is NOT NULL + """, (model, button.get('name'))) + group_ids = [x[0] for x in cr.fetchall() if x[0]] + can_click = not group_ids or bool(set(user_groups).intersection(group_ids)) + button.set('readonly', str(int(not can_click))) + return node + + def __view_look_dom_arch(self, cr, user, model, node, view_id, context=None): + """ Return an architecture and a description of all the fields. + + The field description combines the result of fields_get() and + __view_look_dom(). + + :param node: the architecture as as an etree + :return: a tuple (arch, fields) where arch is the given node as a + string and fields is the description of all the fields. + + """ + fields = {} + Model = self.pool[model] + if node.tag == 'diagram': + if node.getchildren()[0].tag == 'node': + node_model = self.pool[node.getchildren()[0].get('object')] + node_fields = node_model.fields_get(cr, user, None, context) + fields.update(node_fields) + if not node.get("create") and not node_model.check_access_rights(cr, user, 'create', raise_exception=False): + node.set("create", 'false') + if node.getchildren()[1].tag == 'arrow': + arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context) + fields.update(arrow_fields) + else: + fields = Model.fields_get(cr, user, None, context) + fields_def = self.__view_look_dom(cr, user, model, node, view_id, False, fields, context=context) + node = self._disable_workflow_buttons(cr, user, model, node) + if node.tag in ('kanban', 'tree', 'form', 'gantt'): + for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')): + if not node.get(action) and not Model.check_access_rights(cr, user, operation, raise_exception=False): + node.set(action, 'false') + arch = etree.tostring(node, encoding="utf-8").replace('\t', '') + for k in fields.keys(): + if k not in fields_def: + del fields[k] + for field in fields_def: + if field == 'id': + # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references) + fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'} + elif field in fields: + fields[field].update(fields_def[field]) + else: + cr.execute('select name, model from ir_ui_view where (id=%s or inherit_id=%s) and arch like %s', (view_id, view_id, '%%%s%%' % field)) + res = cr.fetchall()[:] + model = res[0][1] + res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None)) + msg = "\n * ".join([r[0] for r in res]) + msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model" + _logger.error(msg) + raise orm.except_orm('View error', msg) + return arch, fields + class view_sc(osv.osv): _name = 'ir.ui.view_sc' _columns = { diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index cd1a7f024ae..3eea19cbfd1 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1697,268 +1697,6 @@ class BaseModel(object): return any([self.pool.get('res.users').has_group(cr, uid, group_ext_id) for group_ext_id in groups.split(',')]) - def __view_look_dom(self, cr, user, model, node, view_id, in_tree_view, model_fields, context=None): - """Return the description of the fields in the node. - - In a normal call to this method, node is a complete view architecture - but it is actually possible to give some sub-node (this is used so - that the method can call itself recursively). - - Originally, the field descriptions are drawn from the node itself. - But there is now some code calling fields_get() in order to merge some - of those information in the architecture. - - """ - if context is None: - context = {} - result = False - fields = {} - children = True - - modifiers = {} - Model = self.pool[model] - - def encode(s): - if isinstance(s, unicode): - return s.encode('utf8') - return s - - def check_group(node): - """Apply group restrictions, may be set at view level or model level:: - * at view level this means the element should be made invisible to - people who are not members - * at model level (exclusively for fields, obviously), this means - the field should be completely removed from the view, as it is - completely unavailable for non-members - - :return: True if field should be included in the result of fields_view_get - """ - if node.tag == 'field' and node.get('name') in Model._all_columns: - column = Model._all_columns[node.get('name')].column - if column.groups and not self.user_has_groups(cr, user, - groups=column.groups, - context=context): - node.getparent().remove(node) - fields.pop(node.get('name'), None) - # no point processing view-level ``groups`` anymore, return - return False - if node.get('groups'): - can_see = self.user_has_groups(cr, user, - groups=node.get('groups'), - context=context) - if not can_see: - node.set('invisible', '1') - modifiers['invisible'] = True - if 'attrs' in node.attrib: - del(node.attrib['attrs']) #avoid making field visible later - del(node.attrib['groups']) - return True - - if node.tag in ('field', 'node', 'arrow'): - if node.get('object'): - attrs = {} - views = {} - xml = "
" - for f in node: - if f.tag == 'field': - xml += etree.tostring(f, encoding="utf-8") - xml += "
" - new_xml = etree.fromstring(encode(xml)) - ctx = context.copy() - ctx['base_model_name'] = model - xarch, xfields = self.__view_look_dom_arch(cr, user, node.get('object'), new_xml, view_id, ctx) - views['form'] = { - 'arch': xarch, - 'fields': xfields - } - attrs = {'views': views} - fields = xfields - if node.get('name'): - attrs = {} - try: - if node.get('name') in Model._columns: - column = Model._columns[node.get('name')] - else: - column = Model._inherit_fields[node.get('name')][2] - except Exception: - column = False - - if column: - relation = self.pool[column._obj] if column._obj else None - - children = False - views = {} - for f in node: - if f.tag in ('form', 'tree', 'graph', 'kanban'): - node.remove(f) - ctx = context.copy() - ctx['base_model_name'] = Model - xarch, xfields = self.__view_look_dom_arch(cr, user, column._obj or None, f, view_id, ctx) - views[str(f.tag)] = { - 'arch': xarch, - 'fields': xfields - } - attrs = {'views': views} - if node.get('widget') and node.get('widget') == 'selection': - # Prepare the cached selection list for the client. This needs to be - # done even when the field is invisible to the current user, because - # other events could need to change its value to any of the selectable ones - # (such as on_change events, refreshes, etc.) - - # If domain and context are strings, we keep them for client-side, otherwise - # we evaluate them server-side to consider them when generating the list of - # possible values - # TODO: find a way to remove this hack, by allow dynamic domains - dom = [] - if column._domain and not isinstance(column._domain, basestring): - dom = list(column._domain) - dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time}) - search_context = dict(context) - if column._context and not isinstance(column._context, basestring): - search_context.update(column._context) - attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1) - if (node.get('required') and not int(node.get('required'))) or not column.required: - attrs['selection'].append((False, '')) - fields[node.get('name')] = attrs - - field = model_fields.get(node.get('name')) - if field: - transfer_field_to_modifiers(field, modifiers) - - - elif node.tag in ('form', 'tree'): - result = Model.view_header_get(cr, user, False, node.tag, context) - if result: - node.set('string', result) - in_tree_view = node.tag == 'tree' - - elif node.tag == 'calendar': - for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'): - if node.get(additional_field): - fields[node.get(additional_field)] = {} - - if not check_group(node): - # node must be removed, no need to proceed further with its children - return fields - - # The view architeture overrides the python model. - # Get the attrs before they are (possibly) deleted by check_group below - transfer_node_to_modifiers(node, modifiers, context, in_tree_view) - - # TODO remove attrs couterpart in modifiers when invisible is true ? - - # translate view - if 'lang' in context: - Translations = self.pool['ir.translation'] - if node.text and node.text.strip(): - trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.text.strip()) - if trans: - node.text = node.text.replace(node.text.strip(), trans) - if node.tail and node.tail.strip(): - trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.tail.strip()) - if trans: - node.tail = node.tail.replace(node.tail.strip(), trans) - - if node.get('string') and not result: - trans = Translations._get_source(cr, user, model, 'view', context['lang'], node.get('string')) - if trans == node.get('string') and ('base_model_name' in context): - # If translation is same as source, perhaps we'd have more luck with the alternative model name - # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model - trans = Translations._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string')) - if trans: - node.set('string', trans) - - for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'): - attr_value = node.get(attr_name) - if attr_value: - trans = Translations._get_source(cr, user, model, 'view', context['lang'], attr_value) - if trans: - node.set(attr_name, trans) - - for f in node: - if children or (node.tag == 'field' and f.tag in ('filter','separator')): - fields.update(self.__view_look_dom(cr, user, model, f, view_id, in_tree_view, model_fields, context)) - - transfer_modifiers_to_node(modifiers, node) - return fields - - def _disable_workflow_buttons(self, cr, user, model, node): - """ Set the buttons in node to readonly if the user can't activate them. """ - if user == 1: - # admin user can always activate workflow buttons - return node - - # TODO handle the case of more than one workflow for a model or multiple - # transitions with different groups and same signal - usersobj = self.pool.get('res.users') - buttons = (n for n in node.getiterator('button') if n.get('type') != 'object') - for button in buttons: - user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id'] - cr.execute("""SELECT DISTINCT t.group_id - FROM wkf - INNER JOIN wkf_activity a ON a.wkf_id = wkf.id - INNER JOIN wkf_transition t ON (t.act_to = a.id) - WHERE wkf.osv = %s - AND t.signal = %s - AND t.group_id is NOT NULL - """, (model, button.get('name'))) - group_ids = [x[0] for x in cr.fetchall() if x[0]] - can_click = not group_ids or bool(set(user_groups).intersection(group_ids)) - button.set('readonly', str(int(not can_click))) - return node - - def __view_look_dom_arch(self, cr, user, model, node, view_id, context=None): - """ Return an architecture and a description of all the fields. - - The field description combines the result of fields_get() and - __view_look_dom(). - - :param node: the architecture as as an etree - :return: a tuple (arch, fields) where arch is the given node as a - string and fields is the description of all the fields. - - """ - fields = {} - Model = self.pool[model] - if node.tag == 'diagram': - if node.getchildren()[0].tag == 'node': - node_model = self.pool[node.getchildren()[0].get('object')] - node_fields = node_model.fields_get(cr, user, None, context) - fields.update(node_fields) - if not node.get("create") and not node_model.check_access_rights(cr, user, 'create', raise_exception=False): - node.set("create", 'false') - if node.getchildren()[1].tag == 'arrow': - arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context) - fields.update(arrow_fields) - else: - fields = Model.fields_get(cr, user, None, context) - fields_def = self.__view_look_dom(cr, user, model, node, view_id, False, fields, context=context) - node = self._disable_workflow_buttons(cr, user, model, node) - if node.tag in ('kanban', 'tree', 'form', 'gantt'): - for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')): - if not node.get(action) and not Model.check_access_rights(cr, user, operation, raise_exception=False): - node.set(action, 'false') - arch = etree.tostring(node, encoding="utf-8").replace('\t', '') - for k in fields.keys(): - if k not in fields_def: - del fields[k] - for field in fields_def: - if field == 'id': - # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references) - fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'} - elif field in fields: - fields[field].update(fields_def[field]) - else: - cr.execute('select name, model from ir_ui_view where (id=%s or inherit_id=%s) and arch like %s', (view_id, view_id, '%%%s%%' % field)) - res = cr.fetchall()[:] - model = res[0][1] - res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None)) - msg = "\n * ".join([r[0] for r in res]) - msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model" - _logger.error(msg) - raise except_orm('View error', msg) - return arch, fields - def _get_default_form_view(self, cr, user, context=None): """ Generates a default single-line form view using all fields of the current model except the m2m and o2m ones. @@ -2105,7 +1843,7 @@ class BaseModel(object): ctx = context if root_view.get('model') != self._name: ctx = dict(context, base_model_name=root_view.get('model')) - xarch, xfields = self.__view_look_dom_arch( + xarch, xfields = View._view__view_look_dom_arch( cr, user, self._name, etree.fromstring(result['arch']), result['view_id'], context=ctx) result['arch'] = xarch @@ -2149,7 +1887,9 @@ class BaseModel(object): } return result - _view_look_dom_arch = __view_look_dom_arch + def _view_look_dom_arch(self, cr, uid, node, view_id, context=None): + return self['ir.ui.view']._view__view_look_dom_arch( + cr, uid, self._name, node, view_id, context=context) def search_count(self, cr, user, args, context=None): if not context: