[FIX] orm.read_group: get correct number of return values
[odoo/odoo.git] / bin / osv / orm.py
index 7b670b0..db1dc48 100644 (file)
@@ -186,18 +186,18 @@ class browse_record(object):
             # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
             if col._prefetch:
                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
-                ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
+                fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
                 # gen the list of inherited fields
                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
                 # complete the field list with the inherited fields which are classic or many2one
-                ffields += filter(lambda x: x[1]._classic_write, inherits)
+                fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
             # otherwise we fetch only that field
             else:
-                ffields = [(name, col)]
+                fields_to_fetch = [(name, col)]
             ids = filter(lambda id: name not in self._data[id], self._data.keys())
-            # read the data
-            fffields = map(lambda x: x[0], ffields)
-            datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write")
+            # read the results
+            field_names = map(lambda x: x[0], fields_to_fetch)
+            field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
             if self._fields_process:
                 lang = self._context.get('lang', 'en_US') or 'en_US'
                 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
@@ -205,74 +205,68 @@ class browse_record(object):
                     raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
                 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
 
-                for n, f in ffields:
-                    if f._type in self._fields_process:
-                        for d in datas:
-                            d[n] = self._fields_process[f._type](d[n])
-                            if d[n]:
-                                d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj)
+                for field_name, field_column in fields_to_fetch:
+                    if field_column._type in self._fields_process:
+                        for result_line in field_values:
+                            result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
+                            if result_line[field_name]:
+                                result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
 
-
-            if not datas:
+            if not field_values:
                 # Where did those ids come from? Perhaps old entries in ir_model_dat?
-                self.__logger.warn("No datas found for ids %s in %s",
-                                   ids, self)
+                self.__logger.warn("No field_values found for ids %s in %s", ids, self)
                 raise KeyError('Field %s not found in %s'%(name,self))
             # create browse records for 'remote' objects
-            for data in datas:
-                if len(str(data['id']).split('-')) > 1:
-                    data['id'] = int(str(data['id']).split('-')[0])
+            for result_line in field_values:
                 new_data = {}
-                for n, f in ffields:
-                    if f._type in ('many2one', 'one2one'):
-                        if data[n]:
-                            obj = self._table.pool.get(f._obj)
-                            compids = False
-                            if type(data[n]) in (type([]),type( (1,) )):
-                                ids2 = data[n][0]
+                for field_name, field_column in fields_to_fetch:
+                    if field_column._type in ('many2one', 'one2one'):
+                        if result_line[field_name]:
+                            obj = self._table.pool.get(field_column._obj)
+                            if isinstance(result_line[field_name], (list,tuple)):
+                                value = result_line[field_name][0]
                             else:
-                                ids2 = data[n]
-                            if ids2:
+                                value = result_line[field_name]
+                            if value:
                                 # FIXME: this happen when a _inherits object
                                 #        overwrite a field of it parent. Need
                                 #        testing to be sure we got the right
                                 #        object and not the parent one.
-                                if not isinstance(ids2, browse_record):
-                                    new_data[n] = browse_record(self._cr,
-                                        self._uid, ids2, obj, self._cache,
+                                if not isinstance(value, browse_record):
+                                    new_data[field_name] = browse_record(self._cr,
+                                        self._uid, value, obj, self._cache,
                                         context=self._context,
                                         list_class=self._list_class,
                                         fields_process=self._fields_process)
                                 else:
-                                    new_data[n] = ids2
+                                    new_data[field_name] = value
                             else:
-                                new_data[n] = browse_null()
+                                new_data[field_name] = browse_null()
                         else:
-                            new_data[n] = browse_null()
-                    elif f._type in ('one2many', 'many2many') and len(data[n]):
-                        new_data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
-                    elif f._type in ('reference'):
-                        if data[n]:
-                            if isinstance(data[n], browse_record):
-                                new_data[n] = data[n]
+                            new_data[field_name] = browse_null()
+                    elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
+                        new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(field_column._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
+                    elif field_column._type in ('reference'):
+                        if result_line[field_name]:
+                            if isinstance(result_line[field_name], browse_record):
+                                new_data[field_name] = result_line[field_name]
                             else:
-                                ref_obj, ref_id = data[n].split(',')
+                                ref_obj, ref_id = result_line[field_name].split(',')
                                 ref_id = long(ref_id)
                                 obj = self._table.pool.get(ref_obj)
-                                compids = False
-                                new_data[n] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
+                                new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
                         else:
-                            new_data[n] = browse_null()
+                            new_data[field_name] = browse_null()
                     else:
-                        new_data[n] = data[n]
-                self._data[data['id']].update(new_data)
+                        new_data[field_name] = result_line[field_name]
+                self._data[result_line['id']].update(new_data)
 
         if not name in self._data[self._id]:
             #how did this happen?
             self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
-                    "Ffields: %s, datas: %s"%(fffields, datas))
+                    "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
             self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
-                    "Data: %s, Table: %s"%(self._data[self._id], self._table))
+                    "Cached: %s, Table: %s"%(self._data[self._id], self._table))
             raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
         return self._data[self._id][name]
 
@@ -650,7 +644,7 @@ class orm_template(object):
         This method is used when exporting data via client menu
 
         """
-        if not context:
+        if context is None:
             context = {}
         imp_comp = context.get('import_comp',False)
         cols = self._columns.copy()
@@ -1049,14 +1043,97 @@ class orm_template(object):
 
     def default_get(self, cr, uid, fields_list, context=None):
         """
-        Set default values for the object's fields.
-
-        :param fields_list: fields for which the object doesn't have any value yet, and default values need to be provided.
-                                  If fields outside this list are returned, the user-provided values will be overwritten.
-        :rtype: a dict of {field_name:default_value}
+        Returns default values for the fields in fields_list.
 
+        :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
+        :type fields_list: list
+        :param context: usual context dictionary - it may contains keys in the form ``default_XXX``,
+                        where XXX is a field name to set or override a default value.
+        :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
         """
-        return {}
+        # trigger view init hook
+        self.view_init(cr, uid, fields_list, context)
+
+        if not context:
+            context = {}
+        defaults = {}
+
+        # get the default values for the inherited fields
+        for t in self._inherits.keys():
+            defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
+                context))
+
+        # get the default values defined in the object
+        for f in fields_list:
+            if f in self._defaults:
+                if callable(self._defaults[f]):
+                    defaults[f] = self._defaults[f](self, cr, uid, context)
+                else:
+                    defaults[f] = self._defaults[f]
+
+            fld_def = ((f in self._columns) and self._columns[f]) \
+                    or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
+                    or False
+
+            if isinstance(fld_def, fields.property):
+                property_obj = self.pool.get('ir.property')
+                prop_value = property_obj.get(cr, uid, f, self._name, context=context)
+                if prop_value:
+                    if isinstance(prop_value, (browse_record, browse_null)):
+                        defaults[f] = prop_value.id
+                    else:
+                        defaults[f] = prop_value
+                else:
+                    if f not in defaults:
+                        defaults[f] = False
+
+        # get the default values set by the user and override the default
+        # values defined in the object
+        ir_values_obj = self.pool.get('ir.values')
+        res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
+        for id, field, field_value in res:
+            if field in fields_list:
+                fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
+                if fld_def._type in ('many2one', 'one2one'):
+                    obj = self.pool.get(fld_def._obj)
+                    if not obj.search(cr, uid, [('id', '=', field_value or False)]):
+                        continue
+                if fld_def._type in ('many2many'):
+                    obj = self.pool.get(fld_def._obj)
+                    field_value2 = []
+                    for i in range(len(field_value)):
+                        if not obj.search(cr, uid, [('id', '=',
+                            field_value[i])]):
+                            continue
+                        field_value2.append(field_value[i])
+                    field_value = field_value2
+                if fld_def._type in ('one2many'):
+                    obj = self.pool.get(fld_def._obj)
+                    field_value2 = []
+                    for i in range(len(field_value)):
+                        field_value2.append({})
+                        for field2 in field_value[i]:
+                            if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
+                                obj2 = self.pool.get(obj._columns[field2]._obj)
+                                if not obj2.search(cr, uid,
+                                        [('id', '=', field_value[i][field2])]):
+                                    continue
+                            elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
+                                obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
+                                if not obj2.search(cr, uid,
+                                        [('id', '=', field_value[i][field2])]):
+                                    continue
+                            # TODO add test for many2many and one2many
+                            field_value2[i][field2] = field_value[i][field2]
+                    field_value = field_value2
+                defaults[field] = field_value
+
+        # get the default values from the context
+        for key in context or {}:
+            if key.startswith('default_') and (key[8:] in fields_list):
+                defaults[key[8:]] = context[key]
+        return defaults
+
 
     def perm_read(self, cr, user, ids, context=None, details=True):
         raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
@@ -1769,7 +1846,7 @@ class orm_memory(orm_template):
 
     def _check_access(self, uid, object_id, mode):
         if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
-            raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects' % mode.capitalize())
+            raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
 
     def vaccum(self, cr, uid):
         self.check_id += 1
@@ -1844,12 +1921,12 @@ class orm_memory(orm_template):
         self.vaccum(cr, user)
         self.next_id += 1
         id_new = self.next_id
-        default = []
-        for f in self._columns.keys():
-            if not f in vals:
-                default.append(f)
-        if len(default):
-            vals.update(self.default_get(cr, user, default, context))
+
+        # override defaults with the provided values, never allow the other way around
+        defaults = self.default_get(cr, user, [], context)
+        defaults.update(vals)
+        vals = defaults
+
         vals2 = {}
         upd_todo = []
         for field in vals:
@@ -1874,66 +1951,6 @@ class orm_memory(orm_template):
         wf_service.trg_create(user, self._name, id_new, cr)
         return id_new
 
-    def default_get(self, cr, uid, fields_list, context=None):
-        self.view_init(cr, uid, fields_list, context)
-        if not context:
-            context = {}
-        # we define default value for each field existing in the object
-        value = dict.fromkeys(fields_list, False)
-        # get the default values for the inherited fields
-        for f in fields_list:
-            if f in self._defaults:
-                if callable(self._defaults[f]):
-                    value[f] = self._defaults[f](self, cr, uid, context)
-                else:
-                    value[f] = self._defaults[f]
-
-            fld_def = ((f in self._columns) and self._columns[f]) \
-                    or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
-                    or False
-
-        # get the default values set by the user and override the default
-        # values defined in the object
-        ir_values_obj = self.pool.get('ir.values')
-        res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
-        for id, field, field_value in res:
-            if field in fields_list:
-                fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
-                if fld_def._type in ('many2one', 'one2one'):
-                    obj = self.pool.get(fld_def._obj)
-                    if not obj.search(cr, uid, [('id', '=', field_value)]):
-                        continue
-                if fld_def._type in ('many2many'):
-                    obj = self.pool.get(fld_def._obj)
-                    field_value2 = []
-                    for i in range(len(field_value)):
-                        if not obj.search(cr, uid, [('id', '=',
-                            field_value[i])]):
-                            continue
-                        field_value2.append(field_value[i])
-                    field_value = field_value2
-                if fld_def._type in ('one2many'):
-                    obj = self.pool.get(fld_def._obj)
-                    field_value2 = []
-                    for i in range(len(field_value)):
-                        field_value2.append({})
-                        for field2 in field_value[i]:
-                            if obj._columns[field2]._type in ('many2one', 'one2one'):
-                                obj2 = self.pool.get(obj._columns[field2]._obj)
-                                if not obj2.search(cr, uid,
-                                        [('id', '=', field_value[i][field2])]):
-                                    continue
-                            # TODO add test for many2many and one2many
-                            field_value2[i][field2] = field_value[i][field2]
-                    field_value = field_value2
-                value[field] = field_value
-
-        # get the default values from the context
-        for key in context or {}:
-            if key.startswith('default_') and (key[8:] in fields_list):
-                value[key[8:]] = context[key]
-        return value
-
     def _where_calc(self, cr, user, args, active_test=True, context=None):
         if not context:
             context = {}
@@ -1963,10 +1980,11 @@ class orm_memory(orm_template):
         if not context:
             context = {}
 
-        # implicit filter on current user
-        if not args:
-            args = []
-        args.insert(0, ('internal.create_uid', '=', user))
+        # implicit filter on current user except for superuser
+        if user != 1:
+            if not args:
+                args = []
+            args.insert(0, ('internal.create_uid', '=', user))
 
         result = self._where_calc(cr, user, args, context=context)
         if result==[]:
@@ -2062,20 +2080,28 @@ class orm(orm_template):
         if not fields:
             fields = self._columns.keys()
 
-        (where_clause, where_params, tables) = self._where_calc(cr, uid, domain, context=context)
-        dom = self.pool.get('ir.rule').domain_get(cr, uid, self._name, 'read', context=context)
-        where_clause = where_clause + dom[0]
-        where_params = where_params + dom[1]
-        for t in dom[2]:
-            if t not in tables:
-                tables.append(t)
+        # compute the where, order by, limit and offset clauses
+        (where_clause, where_clause_params, tables) = self._where_calc(cr, uid, domain, context=context)
+
+        # apply direct ir.rules from current model
+        self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', context=context)
+
+        # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
+        for inherited_model in self._inherits:
+            previous_tables = list(tables)
+            if self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
+                # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
+                # list of table in case the inherited table was not in the list before (as that means the corresponding 
+                # JOIN(s) was(were) not present)
+                self._inherits_join_add(inherited_model, previous_tables, where_clause)
+                tables = list(set(tables).union(set(previous_tables)))
 
         # Take care of adding join(s) if groupby is an '_inherits'ed field
         groupby_list = groupby
         if groupby:
             if groupby and isinstance(groupby, list):
                 groupby = groupby[0]
-            tables, where_clause = self._inherits_join_calc(groupby,tables,where_clause)
+            tables, where_clause, qfield = self._inherits_join_calc(groupby,tables,where_clause)
 
         if len(where_clause):
             where_clause = ' where '+string.join(where_clause, ' and ')
@@ -2112,7 +2138,7 @@ class orm(orm_template):
             gb = ' group by '+groupby
         else:
             gb = ''
-        cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_params)
+        cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_clause_params)
         alldata = {}
         groupby = group_by
         for r in cr.dictfetchall():
@@ -2121,7 +2147,6 @@ class orm(orm_template):
             alldata[r['id']] = r
             del r['id']
         data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
-        today = datetime.date.today()
         for d in data:
             if groupby:
                 d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
@@ -2130,35 +2155,54 @@ class orm(orm_template):
                         d['__context'] = {'group_by':groupby_list[1:]}
             if groupby and fget.has_key(groupby):
                 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
-                   dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
-                   days = calendar.monthrange(dt.year, dt.month)[1]
+                    dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
+                    days = calendar.monthrange(dt.year, dt.month)[1]
 
-                   d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
-                   d['__domain'] = [(groupby,'>=',alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-01','%Y-%m-%d').strftime('%Y-%m-%d') or False),\
-                                    (groupby,'<=',alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-' + str(days),'%Y-%m-%d').strftime('%Y-%m-%d') or False)] + domain
+                    d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
+                    d['__domain'] = [(groupby,'>=',alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-01','%Y-%m-%d').strftime('%Y-%m-%d') or False),\
+                                     (groupby,'<=',alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-' + str(days),'%Y-%m-%d').strftime('%Y-%m-%d') or False)] + domain
                 del alldata[d['id']][groupby]
             d.update(alldata[d['id']])
             del d['id']
         return data
 
-    def _inherits_join_calc(self, field, tables, where_clause):
+    def _inherits_join_add(self, parent_model_name, tables, where_clause):
         """
-            Adds missing table select and join clause(s) for reaching
-            the field coming from an '_inherits' parent table.
+        Add missing table SELECT and JOIN clause for reaching the parent table (no duplicates)
 
-            :param tables: list of table._table names enclosed in double quotes as returned
-                           by _where_calc()
+        :param parent_model_name: name of the parent model for which the clauses should be added
+        :param tables: list of table._table names enclosed in double quotes as returned
+                       by _where_calc()
+        :param where_clause: current list of WHERE clause params
+        """
+        inherits_field = self._inherits[parent_model_name]
+        parent_model = self.pool.get(parent_model_name)
+        parent_table_name = parent_model._table
+        quoted_parent_table_name = '"%s"' % parent_table_name
+        if quoted_parent_table_name not in tables:
+            tables.append(quoted_parent_table_name)
+            where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
+        return (tables, where_clause)
 
+    def _inherits_join_calc(self, field, tables, where_clause):
+        """
+        Adds missing table select and join clause(s) for reaching
+        the field coming from an '_inherits' parent table (no duplicates).
+
+        :param tables: list of table._table names enclosed in double quotes as returned
+                        by _where_calc()
+        :param where_clause: current list of WHERE clause params
+        :return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated
+                 versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
+                 in the form ``table.field``, to be referenced in queries. 
         """
         current_table = self
         while field in current_table._inherit_fields and not field in current_table._columns:
-            parent_table = self.pool.get(current_table._inherit_fields[field][0])
-            parent_table_name = parent_table._table
-            if '"%s"'%parent_table_name not in tables:
-                tables.append('"%s"'%parent_table_name)
-                where_clause.append('(%s.%s = %s.id)' % (current_table._table, current_table._inherits[parent_table._name], parent_table_name))
+            parent_model_name = current_table._inherit_fields[field][0]
+            parent_table = self.pool.get(parent_model_name)
+            self._inherits_join_add(parent_model_name, tables, where_clause)
             current_table = parent_table
-        return (tables, where_clause)
+        return (tables, where_clause, '"%s".%s' % (current_table._table, field))
 
     def _parent_store_compute(self, cr):
         if not self._parent_store:
@@ -2425,15 +2469,24 @@ class orm(orm_template):
                             elif not f.required and f_pg_notnull == 1:
                                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
                                 cr.commit()
+
+                            # Verify index
                             indexname = '%s_%s_index' % (self._table, k)
                             cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
                             res2 = cr.dictfetchall()
                             if not res2 and f.select:
                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
                                 cr.commit()
+                                if f._type == 'text':
+                                    # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
+                                    logger.notifyChannel('orm', netsvc.LOG_WARNING, "Adding (b-tree) index for text column '%s' in table '%s'."\
+                                        "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts because there is a length limit for indexable btree values!\n"\
+                                        "Use a search view instead if you simply want to make the field searchable." % (k, f._type, self._table))
                             if res2 and not f.select:
                                 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
                                 cr.commit()
+                                logger.notifyChannel('orm', netsvc.LOG_WARNING, "Dropping index for column '%s' of type '%s' in table '%s' as it is not required anymore" % (k, f._type, self._table))
+
                             if isinstance(f, fields.many2one):
                                 ref = self.pool.get(f._obj)._table
                                 if ref != 'ir_actions':
@@ -2615,7 +2668,7 @@ class orm(orm_template):
                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
                 else:
                     self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
-
+        self._inherits_check()
         self._inherits_reload()
         if not self._sequence:
             self._sequence = self._table+'_id_seq'
@@ -2624,95 +2677,6 @@ class orm(orm_template):
         for f in self._columns:
             self._columns[f].restart()
 
-    def default_get(self, cr, uid, fields_list, context=None):
-        """
-        To Get default field values of given fields list of the model
-
-        :param cr: database cursor
-        :param uid: current user id
-        :param fields_list: list of fields to get the default value
-        :type fields_list: list (example ['field1', 'field2',])
-        :param context: context arguments, like lang, time zone
-        :return: dictionary of the default values for fields (set on the object class, by the user preferences, or via the context)
-
-        """
-        if not context:
-            context = {}
-        value = {}
-        # get the default values for the inherited fields
-        for t in self._inherits.keys():
-            value.update(self.pool.get(t).default_get(cr, uid, fields_list,
-                context))
-
-        # get the default values defined in the object
-        for f in fields_list:
-            if f in self._defaults:
-                if callable(self._defaults[f]):
-                    value[f] = self._defaults[f](self, cr, uid, context)
-                else:
-                    value[f] = self._defaults[f]
-
-            fld_def = ((f in self._columns) and self._columns[f]) \
-                    or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
-                    or False
-
-            if isinstance(fld_def, fields.property):
-                property_obj = self.pool.get('ir.property')
-                prop_value = property_obj.get(cr, uid, f, self._name, context=context)
-                if prop_value:
-                    if isinstance(prop_value, (browse_record, browse_null)):
-                        value[f] = prop_value.id
-                    else:
-                        value[f] = prop_value
-                else:
-                    if f not in value:
-                        value[f] = False
-
-        # get the default values set by the user and override the default
-        # values defined in the object
-        ir_values_obj = self.pool.get('ir.values')
-        res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
-        for id, field, field_value in res:
-            if field in fields_list:
-                fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
-                if fld_def._type in ('many2one', 'one2one'):
-                    obj = self.pool.get(fld_def._obj)
-                    if not obj.search(cr, uid, [('id', '=', field_value or False)]):
-                        continue
-                if fld_def._type in ('many2many'):
-                    obj = self.pool.get(fld_def._obj)
-                    field_value2 = []
-                    for i in range(len(field_value)):
-                        if not obj.search(cr, uid, [('id', '=',
-                            field_value[i])]):
-                            continue
-                        field_value2.append(field_value[i])
-                    field_value = field_value2
-                if fld_def._type in ('one2many'):
-                    obj = self.pool.get(fld_def._obj)
-                    field_value2 = []
-                    for i in range(len(field_value)):
-                        field_value2.append({})
-                        for field2 in field_value[i]:
-                            if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
-                                obj2 = self.pool.get(obj._columns[field2]._obj)
-                                if not obj2.search(cr, uid,
-                                        [('id', '=', field_value[i][field2])]):
-                                    continue
-                            elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
-                                obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
-                                if not obj2.search(cr, uid,
-                                        [('id', '=', field_value[i][field2])]):
-                                    continue
-                            # TODO add test for many2many and one2many
-                            field_value2[i][field2] = field_value[i][field2]
-                    field_value = field_value2
-                value[field] = field_value
-        for key in context or {}:
-            if key.startswith('default_') and (key[8:] in fields_list):
-                value[key[8:]] = context[key]
-        return value
-
     #
     # Update objects that uses this one to update their _inherits fields
     #
@@ -2733,6 +2697,17 @@ class orm(orm_template):
         self._inherit_fields = res
         self._inherits_reload_src()
 
+    def _inherits_check(self):
+        for table, field_name in self._inherits.items():
+            if field_name not in self._columns:
+                logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
+                self._columns[field_name] =  fields.many2one(table, string="Automatically created field to link to parent %s" % table,
+                                                             required=True, ondelete="cascade")
+            elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
+                logging.getLogger('init').warning('Field definition for _inherits reference "%s" in "%s" must be marked as "required" with ondelete="cascade", forcing it.' % (field_name, self._name))
+                self._columns[field_name].required = True
+                self._columns[field_name].ondelete = "cascade"
+
     #def __getattr__(self, name):
     #    """
     #    Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
@@ -3431,7 +3406,9 @@ class orm(orm_template):
                     if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
                         default_values[dv] = [(6, 0, default_values[dv])]
 
-            vals.update(default_values)
+            # override defaults with the provided values, never allow the other way around
+            default_values.update(vals)
+            vals = default_values
 
         tocreate = {}
         for v in self._inherits:
@@ -3716,6 +3693,18 @@ class orm(orm_template):
 
     # TODO: ameliorer avec NULL
     def _where_calc(self, cr, user, args, active_test=True, context=None):
+        """Computes the WHERE clause needed to implement an OpenERP domain.
+        :param args: the domain to compute
+        :type args: list
+        :param active_test: whether the default filtering of records with ``active``
+                            field set to ``False`` should be applied. 
+        :return: tuple with 3 elements: (where_clause, where_clause_params, tables) where
+                 ``where_clause`` contains a list of where clause elements (to be joined with 'AND'),
+                 ``where_clause_params`` is a list of parameters to be passed to the db layer
+                 for the where_clause expansion, and ``tables`` is the list of double-quoted
+                 table names that need to be included in the FROM clause. 
+        :rtype: tuple 
+        """
         if not context:
             context = {}
         args = args[:]
@@ -3749,6 +3738,29 @@ class orm(orm_template):
             raise except_orm(_('AccessError'), _('Bad query.'))
         return True
 
+    def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
+        """Add what's missing in ``where_clause``, ``where_params``, ``tables`` to implement
+           all appropriate ir.rules (on the current object but also from it's _inherits parents)
+
+           :param where_clause: list with current elements of the WHERE clause (strings)
+           :param where_clause_params: list with parameters for ``where_clause``
+           :param tables: list with double-quoted names of the tables that are joined
+                          in ``where_clause``
+           :param model_name: optional name of the model whose ir.rules should be applied (default:``self._name``)
+                              This could be useful for inheritance for example, but there is no provision to include
+                              the appropriate JOIN for linking the current model to the one referenced in model_name. 
+           :return: True if additional clauses where applied.
+        """
+        added_clause, added_params, added_tables  = self.pool.get('ir.rule').domain_get(cr, uid, model_name or self._name, mode, context=context)
+        if added_clause:
+            where_clause += added_clause
+            where_clause_params += added_params
+            for table in added_tables:
+                if table not in tables:
+                    tables.append(table)
+            return True
+        return False
+
     def search(self, cr, user, args, offset=0, limit=None, order=None,
             context=None, count=False):
         """
@@ -3796,41 +3808,51 @@ class orm(orm_template):
             context = {}
         self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
         # compute the where, order by, limit and offset clauses
-        (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
-        dom = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
-        qu1 = qu1 + dom[0]
-        qu2 = qu2 + dom[1]
-        for t in dom[2]:
-            if t not in tables:
-                tables.append(t)
+        (where_clause, where_clause_params, tables) = self._where_calc(cr, user, args, context=context)
 
-        where = qu1
+        # apply direct ir.rules from current model
+        self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', context=context)
+
+        # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
+        for inherited_model in self._inherits:
+            previous_tables = list(tables)
+            if self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
+                # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
+                # list of table in case the inherited table was not in the list before (as that means the corresponding
+                # JOIN(s) was(were) not present)
+                self._inherits_join_add(inherited_model, previous_tables, where_clause)
+                tables = list(set(tables).union(set(previous_tables)))
+
+        where = where_clause
 
         order_by = self._order
-        qu1_join = []
         if order:
             self._check_qorder(order)
             o = order.split(' ')[0]
-            if (o in self._columns) and getattr(self._columns[o], '_classic_write'):
-                order_by = order
+            if (o in self._columns):
+                # we can only do efficient sort if the fields is stored in database
+                if getattr(self._columns[o], '_classic_read'):
+                    order_by = order
             elif (o in self._inherit_fields):
-                #Allowing _inherits field for server side sorting
-                table_join,qu1_join = self._inherits_join_calc(o,[],[])
-                order_by = table_join[0] + '.' + order
-                tables += table_join
+                parent_obj = self.pool.get(self._inherit_fields[o][0])
+                if getattr(parent_obj._columns[o], '_classic_read'):
+                    # Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
+                    inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
 
         limit_str = limit and ' limit %d' % limit or ''
         offset_str = offset and ' offset %d' % offset or ''
         
-        where.extend(qu1_join)
-        where_str = " WHERE %s" % " AND ".join(where)
+        if where:
+            where_str = " WHERE %s" % " AND ".join(where)
+        else:
+            where_str = ""
 
         if count:
             cr.execute('select count(%s.id) from ' % self._table +
-                    ','.join(tables) + where_str + limit_str + offset_str, qu2)
+                    ','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
             res = cr.fetchall()
             return res[0][0]
-        cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, qu2)
+        cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params)
         res = cr.fetchall()
         return [x[0] for x in res]