diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 3f46fc5a5a7..d3b37b9226f 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1178,5 +1178,24 @@ def field_to_dict(self, cr, user, context, field): return res + +class column_info(object): + """Struct containing details about an osv column, either one local to + its model, or one inherited via _inherits. + + :attr name: name of the column + :attr column: column instance, subclass of osv.fields._column + :attr parent_model: if the column is inherited, name of the model + that contains it, None for local columns. + :attr parent_column: the name of the column containing the m2o + relationship to the parent model that contains + this column, None for local columns. + """ + def __init__(self, name, column, parent_model=None, parent_column=None): + self.name = name + self.column = column + self.parent_model = parent_model + self.parent_column = parent_column + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 961afbad58e..f4a7dfa8ed2 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -479,6 +479,11 @@ class orm_template(object): # r is the (local) field towards m, # and f is the _column object itself. _inherit_fields = {} + # Mapping field name/column_info object + # This is similar to _inherit_fields but: + # 1. includes self fields, + # 2. uses column_info instead of a triple. + _all_columns = {} _table = None _invalids = set() _log_create = False @@ -1265,7 +1270,7 @@ class orm_template(object): def fields_get(self, cr, user, allfields=None, context=None, write_access=True): - """ Returns the definition of each field. + """ Return the definition of each field. The returned value is a dictionary (indiced by field name) of dictionaries. The _inherits'd fields are included. The string, @@ -1320,7 +1325,7 @@ class orm_template(object): return False def __view_look_dom(self, cr, user, node, view_id, context=None): - if not context: + if context is None: context = {} result = False fields = {} @@ -1331,8 +1336,8 @@ class orm_template(object): return s.encode('utf8') return s - # return True if node can be displayed to current user def check_group(node): + """ Set invisible to true if the user is not in the specified groups. """ if node.get('groups'): groups = node.get('groups').split(',') access_pool = self.pool.get('ir.model.access') @@ -1342,9 +1347,6 @@ class orm_template(object): if 'attrs' in node.attrib: del(node.attrib['attrs']) #avoid making field visible later del(node.attrib['groups']) - return can_see - else: - return True if node.tag in ('field', 'node', 'arrow'): if node.get('object'): @@ -1423,12 +1425,11 @@ class orm_template(object): if node.get(additional_field): fields[node.get(additional_field)] = {} - if 'groups' in node.attrib: - check_group(node) + check_group(node) # translate view - if ('lang' in context) and not result: - if node.get('string'): + if 'lang' in context: + if node.get('string') and not result: trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, '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 @@ -1456,6 +1457,7 @@ class orm_template(object): return fields def _disable_workflow_buttons(self, cr, user, 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 @@ -1487,12 +1489,10 @@ class orm_template(object): if node.tag == 'diagram': if node.getchildren()[0].tag == 'node': node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context) + fields.update(node_fields) if node.getchildren()[1].tag == 'arrow': arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context) - for key, value in node_fields.items(): - fields[key] = value - for key, value in arrow_fields.items(): - fields[key] = value + fields.update(arrow_fields) else: fields = self.fields_get(cr, user, fields_def.keys(), context) for field in fields_def: @@ -1563,6 +1563,7 @@ class orm_template(object): tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) fields_to_search = set() + # TODO it seems _all_columns could be used instead of fields_get (no need for translated fields info) fields = self.fields_get(cr, uid, context=context) for field in fields: if fields[field].get('select'): @@ -1617,63 +1618,79 @@ class orm_template(object): raise AttributeError("View definition error for inherited view '%s' on model '%s': %s" % (child_view.xml_id, self._name, error_msg)) - def _inherit_apply(src, inherit, inherit_id=None): - def _find(node, node2): - if node2.tag == 'xpath': - res = node.xpath(node2.get('expr')) - if res: - return res[0] - else: - return None - else: - for n in node.getiterator(node2.tag): - res = True - if node2.tag == 'field': - # only compare field names, a field can be only once in a given view - # at a given level (and for multilevel expressions, we should use xpath - # inheritance spec anyway) - if node2.get('name') == n.get('name'): - return n - else: - continue - for attr in node2.attrib: - if attr == 'position': - continue - if n.get(attr): - if n.get(attr) == node2.get(attr): - continue - res = False - if res: - return n + def locate(source, spec): + """ Locate a node in a source (parent) architecture. + + Given a complete source (parent) architecture (i.e. the field + `arch` in a view), and a 'spec' node (a node in an inheriting + view that specifies the location in the source view of what + should be changed), return (if it exists) the node in the + source view matching the specification. + + :param source: a parent architecture to modify + :param spec: a modifying node in an inheriting view + :return: a node in the source matching the spec + + """ + if spec.tag == 'xpath': + nodes = source.xpath(spec.get('expr')) + return nodes[0] if nodes else None + elif spec.tag == 'field': + # Only compare the field name: a field can be only once in a given view + # at a given level (and for multilevel expressions, we should use xpath + # inheritance spec anyway). + for node in source.getiterator('field'): + if node.get('name') == spec.get('name'): + return node + return None + else: + for node in source.getiterator(spec.tag): + good = True + for attr in spec.attrib: + if attr != 'position' and (not node.get(attr) or node.get(attr) != spec.get(attr)): + good = False + break + if good: + return node return None - # End: _find(node, node2) + def apply_inheritance_specs(source, specs_arch, inherit_id=None): + """ Apply an inheriting view. - doc_dest = etree.fromstring(encode(inherit)) - toparse = [doc_dest] + Apply to a source architecture all the spec nodes (i.e. nodes + describing where and what changes to apply to some parent + architecture) given by an inheriting view. - while len(toparse): - node2 = toparse.pop(0) - if isinstance(node2, SKIPPED_ELEMENT_TYPES): + :param source: a parent architecture to modify + :param specs_arch: a modifying architecture in an inheriting view + :param inherit_id: the database id of the inheriting view + :return: a modified source where the specs are applied + + """ + specs_tree = etree.fromstring(encode(specs_arch)) + # Queue of specification nodes (i.e. nodes describing where and + # changes to apply to some parent architecture). + specs = [specs_tree] + + while len(specs): + spec = specs.pop(0) + if isinstance(spec, SKIPPED_ELEMENT_TYPES): continue - if node2.tag == 'data': - toparse += [ c for c in doc_dest ] + if spec.tag == 'data': + specs += [ c for c in specs_tree ] continue - node = _find(src, node2) + node = locate(source, spec) if node is not None: - pos = 'inside' - if node2.get('position'): - pos = node2.get('position') + pos = spec.get('position', 'inside') if pos == 'replace': - parent = node.getparent() - if parent is None: - src = copy.deepcopy(node2[0]) + if node.getparent() is None: + source = copy.deepcopy(spec[0]) else: - for child in node2: + for child in spec: node.addprevious(child) node.getparent().remove(node) elif pos == 'attributes': - for child in node2.getiterator('attribute'): + for child in spec.getiterator('attribute'): attribute = (child.get('name'), child.text and child.text.encode('utf8') or None) if attribute[1]: node.set(attribute[0], attribute[1]) @@ -1681,7 +1698,7 @@ class orm_template(object): del(node.attrib[attribute[0]]) else: sib = node.getnext() - for child in node2: + for child in spec: if pos == 'inside': node.append(child) elif pos == 'after': @@ -1696,22 +1713,39 @@ class orm_template(object): raise_view_error("Invalid position value: '%s'" % pos, inherit_id) else: attrs = ''.join([ - ' %s="%s"' % (attr, node2.get(attr)) - for attr in node2.attrib + ' %s="%s"' % (attr, spec.get(attr)) + for attr in spec.attrib if attr != 'position' ]) - tag = "<%s%s>" % (node2.tag, attrs) + tag = "<%s%s>" % (spec.tag, attrs) raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id) - return src - # End: _inherit_apply(src, inherit) + return source + + def apply_view_inheritance(source, inherit_id): + """ Apply all the (directly and indirectly) inheriting views. + + :param source: a parent architecture to modify (with parent + modifications already applied) + :param inherit_id: the database id of the parent view + :return: a modified source where all the modifying architecture + are applied + + """ + # get all views which inherit from (ie modify) this view + cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name)) + sql_inherit = cr.fetchall() + for (inherit, id) in sql_inherit: + source = apply_inheritance_specs(source, inherit, id) + source = apply_view_inheritance(source, id) + return source result = {'type': view_type, 'model': self._name} - ok = True sql_res = False parent_view_model = None - while ok: - view_ref = context.get(view_type + '_view_ref', False) + view_ref = context.get(view_type + '_view_ref') + # Search for a root (i.e. without any parent) view. + while True: if view_ref and not view_id: if '.' in view_ref: module, view_ref = view_ref.split('.', 1) @@ -1725,48 +1759,35 @@ class orm_template(object): FROM ir_ui_view WHERE id=%s""", (view_id,)) else: - cr.execute('''SELECT - arch,name,field_parent,id,type,inherit_id,model - FROM - ir_ui_view - WHERE - model=%s AND - type=%s AND - inherit_id IS NULL - ORDER BY priority''', (self._name, view_type)) - sql_res = cr.fetchone() + cr.execute("""SELECT arch,name,field_parent,id,type,inherit_id,model + FROM ir_ui_view + WHERE model=%s AND type=%s AND inherit_id IS NULL + ORDER BY priority""", (self._name, view_type)) + sql_res = cr.dictfetchone() if not sql_res: break - ok = sql_res[5] - view_id = ok or sql_res[3] - parent_view_model = sql_res[6] + view_id = sql_res['inherit_id'] or sql_res['id'] + parent_view_model = sql_res['model'] + if not sql_res['inherit_id']: + break # if a view was found if sql_res: - result['type'] = sql_res[4] - result['view_id'] = sql_res[3] - result['arch'] = sql_res[0] + result['type'] = sql_res['type'] + result['view_id'] = sql_res['id'] - def _inherit_apply_rec(result, inherit_id): - # get all views which inherit from (ie modify) this view - cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name)) - sql_inherit = cr.fetchall() - for (inherit, id) in sql_inherit: - result = _inherit_apply(result, inherit, id) - result = _inherit_apply_rec(result, id) - return result + source = etree.fromstring(encode(sql_res['arch'])) + result['arch'] = apply_view_inheritance(source, result['view_id']) - inherit_result = etree.fromstring(encode(result['arch'])) - result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3]) - - result['name'] = sql_res[1] - result['field_parent'] = sql_res[2] or False + result['name'] = sql_res['name'] + result['field_parent'] = sql_res['field_parent'] or False else: # otherwise, build some kind of default view if view_type == 'form': + # TODO it seems fields_get can be replaced by _all_columns (no need for translation) res = self.fields_get(cr, user, context=context) xml = ' ' \ '
' % (self._description,) @@ -1792,7 +1813,7 @@ class orm_template(object): xml = self.__get_default_search_view(cr, user, context) else: - xml = '' # what happens here, graph case? + # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) result['arch'] = etree.fromstring(encode(xml)) result['name'] = 'default' @@ -2352,6 +2373,7 @@ class orm(orm_template): groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2]) assert groupby_def and groupby_def._classic_write, "Fields in 'groupby' must be regular database-persisted fields (no function or related fields), or function fields with store=True" + # TODO it seems fields_get can be replaced by _all_columns (no need for translation) fget = self.fields_get(cr, uid, fields) float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields) flist = '' @@ -3132,6 +3154,7 @@ class orm(orm_template): if self._name in obj._inherits: obj._inherits_reload() + def _inherits_reload(self): """ Recompute the _inherit_fields mapping. @@ -3141,14 +3164,27 @@ class orm(orm_template): res = {} for table in self._inherits: other = self.pool.get(table) - res.update(other._inherit_fields) for col in other._columns.keys(): res[col] = (table, self._inherits[table], other._columns[col]) for col in other._inherit_fields.keys(): res[col] = (table, self._inherits[table], other._inherit_fields[col][2]) self._inherit_fields = res + self._all_columns = self._get_column_infos() self._inherits_reload_src() + + def _get_column_infos(self): + """Returns a dict mapping all fields names (direct fields and + inherited field via _inherits) to a ``column_info`` struct + giving detailed columns """ + result = {} + for k, (parent, m2o, col) in self._inherit_fields.iteritems(): + result[k] = fields.column_info(k, col, parent, m2o) + for k, col in self._columns.iteritems(): + result[k] = fields.column_info(k, col) + return result + + def _inherits_check(self): for table, field_name in self._inherits.items(): if field_name not in self._columns: @@ -4365,6 +4401,7 @@ class orm(orm_template): else: raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name)) + # TODO it seems fields_get can be replaced by _all_columns (no need for translation) fields = self.fields_get(cr, uid, context=context) for f in fields: ftype = fields[f]['type'] @@ -4422,6 +4459,7 @@ class orm(orm_template): seen_map[self._name].append(old_id) trans_obj = self.pool.get('ir.translation') + # TODO it seems fields_get can be replaced by _all_columns (no need for translation) fields = self.fields_get(cr, uid, context=context) translation_records = []