← Back to team overview

openerp-dev-web team mailing list archive

lp:~openerp-dev/openobject-server/remove_duplication_of_ir.filters into lp:~openerp-dev/openobject-server/trunk-dev-framework

 

RGA(OpenERP) has proposed merging lp:~openerp-dev/openobject-server/remove_duplication_of_ir.filters into lp:~openerp-dev/openobject-server/trunk-dev-framework.

Requested reviews:
  OpenERP Core Team (openerp)


Hello,
Avoid duplication of ir.filters,
In order to avoid creating duplicates filters: when the user tries to save a new filter, the it should modify the existing filter if another filter exists already with the name that is specified

related client branch is:lp:~openerp-dev/openobject-client/duplication_of_ir.filters
-- 
https://code.launchpad.net/~openerp-dev/openobject-server/remove_duplication_of_ir.filters/+merge/35991
Your team OpenERP R&D Team is subscribed to branch lp:~openerp-dev/openobject-server/remove_duplication_of_ir.filters.
=== modified file 'bin/addons/base/ir/ir_filters.py'
--- bin/addons/base/ir/ir_filters.py	2010-08-30 13:04:48 +0000
+++ bin/addons/base/ir/ir_filters.py	2010-09-20 09:31:47 +0000
@@ -36,6 +36,20 @@
         act_ids = self.search(cr,uid,[('model_id','=',model),('user_id','=',uid)])
         my_acts = self.read(cr, uid, act_ids, ['name', 'domain','context'])
         return my_acts
+    
+    def create_or_replace(self, cr, uid, vals, context=None):
+        if context is None:
+            context = {}
+        is_filter_exist = False
+        for filter in self.get_filters(cr, uid, vals['model_id']):
+            if filter['name'].lower() == vals['name'].lower():
+                is_filter_exist = filter['id']
+                break
+        if is_filter_exist:
+             return super(ir_filters, self).write(cr, uid, is_filter_exist, vals, context)
+        else:
+             return super(ir_filters, self).create(cr, uid, vals, context)
+
 
     _columns = {
         'name': fields.char('Action Name', size=64, translate=True, required=True),

=== modified file 'bin/addons/base/res/ir_property.py'
--- bin/addons/base/res/ir_property.py	2010-06-21 21:16:27 +0000
+++ bin/addons/base/res/ir_property.py	2010-09-20 09:31:47 +0000
@@ -135,15 +135,7 @@
     def create(self, cr, uid, values, context=None):
         return super(ir_property, self).create(cr, uid, self._update_values(cr, uid, None, values), context=context)
 
-    def get_by_id(self, cr, uid, record_ids, context=None):
-        if isinstance(record_ids, (int, long)):
-            record_ids = [record_ids]
-
-        if not record_ids:
-            return False
-
-        record = self.browse(cr, uid, record_ids[0], context=context)
-
+    def get_by_record(self, cr, uid, record, context=None):
         if record.type in ('char', 'text'):
             return record.value_text
         elif record.type == 'float':
@@ -162,7 +154,6 @@
             if not record.value_datetime:
                 return False
             return time.strftime('%Y-%m-%d', time.strptime(record.value_datetime, '%Y-%m-%d %H:%M:%S'))
-
         return False
 
     def get(self, cr, uid, name, model, res_id=False, context={}):
@@ -170,7 +161,9 @@
         if domain is not None:
             domain = [('res_id', '=', res_id)] + domain
             nid = self.search(cr, uid, domain, context=context)
-            return self.get_by_id(cr, uid, nid, context=context)
+            if not nid: return False
+            record = self.browse(cr, uid, nid[0], context=context)
+            return self.get_by_record(cr, uid, record, context=context)
         return False
 
     def _get_domain_default(self, cr, uid, prop_name, model, context=None):

=== modified file 'bin/addons/base/rng/view.rng'
--- bin/addons/base/rng/view.rng	2010-09-15 21:01:16 +0000
+++ bin/addons/base/rng/view.rng	2010-09-20 09:31:47 +0000
@@ -411,6 +411,7 @@
             <rng:ref name="overload"/>
             <rng:ref name="access_rights"/>
             <rng:optional><rng:attribute name="editable"/></rng:optional>
+            <rng:optional><rng:attribute name="domain_filter"/></rng:optional>
             <rng:optional><rng:attribute name="attrs"/></rng:optional>
             <rng:optional><rng:attribute name="string"/></rng:optional>
             <rng:optional><rng:attribute name="completion"/></rng:optional>

=== modified file 'bin/netsvc.py'
--- bin/netsvc.py	2010-09-06 14:06:45 +0000
+++ bin/netsvc.py	2010-09-20 09:31:47 +0000
@@ -132,6 +132,7 @@
 LOG_NOTSET = 'notset'
 LOG_DEBUG_SQL = 'debug_sql'
 LOG_DEBUG_RPC = 'debug_rpc'
+LOG_DEBUG_RPC_ANSWER = 'debug_rpc_answer'
 LOG_DEBUG = 'debug'
 LOG_TEST = 'test'
 LOG_INFO = 'info'
@@ -139,9 +140,11 @@
 LOG_ERROR = 'error'
 LOG_CRITICAL = 'critical'
 
+logging.DEBUG_RPC_ANSWER = logging.DEBUG - 4
+logging.addLevelName(logging.DEBUG_RPC_ANSWER, 'DEBUG_RPC_ANSWER')
 logging.DEBUG_RPC = logging.DEBUG - 2
 logging.addLevelName(logging.DEBUG_RPC, 'DEBUG_RPC')
-logging.DEBUG_SQL = logging.DEBUG_RPC - 2
+logging.DEBUG_SQL = logging.DEBUG_RPC - 3
 logging.addLevelName(logging.DEBUG_SQL, 'DEBUG_SQL')
 
 logging.TEST = logging.INFO - 5
@@ -157,6 +160,7 @@
 LEVEL_COLOR_MAPPING = {
     logging.DEBUG_SQL: (WHITE, MAGENTA),
     logging.DEBUG_RPC: (BLUE, WHITE),
+    logging.DEBUG_RPC_ANSWER: (BLUE, WHITE),
     logging.DEBUG: (BLUE, DEFAULT),
     logging.INFO: (GREEN, DEFAULT),
     logging.TEST: (WHITE, BLUE),
@@ -420,11 +424,11 @@
         self.traceback = traceback
 
 class OpenERPDispatcher:
-    def log(self, title, msg):
+    def log(self, title, msg, channel=logging.DEBUG_RPC, depth=2):
         logger = logging.getLogger(title)
-        if logger.isEnabledFor(logging.DEBUG_RPC):
-            for line in pformat(msg).split('\n'):
-                logger.log(logging.DEBUG_RPC, line)
+        if logger.isEnabledFor(channel):
+            for line in pformat(msg, depth=depth).split('\n'):
+                logger.log(channel, line)
 
     def dispatch(self, service_name, method, params):
         try:
@@ -433,10 +437,8 @@
             self.log('params', params)
             auth = getattr(self, 'auth_provider', None)
             result = ExportService.getService(service_name).dispatch(method, auth, params)
-            self.log('result', result)
-            # We shouldn't marshall None,
-            if result == None:
-                result = False
+            logger = logging.getLogger('result')
+            self.log('result', result, channel=logging.DEBUG_RPC_ANSWER, depth=(logger.isEnabledFor(logging.DEBUG_SQL) and 1 or None))
             return result
         except Exception, e:
             self.log('exception', tools.exception_to_unicode(e))

=== modified file 'bin/osv/fields.py'
--- bin/osv/fields.py	2010-08-17 13:28:29 +0000
+++ bin/osv/fields.py	2010-09-20 09:31:47 +0000
@@ -303,10 +303,9 @@
         return result
 
     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
-        if not context:
-            context = {}
-        if not values:
-            values = {}
+        context = context or {}
+        values = values or {}
+
         res = {}
         for r in values:
             res[r['id']] = r[name]
@@ -315,21 +314,14 @@
         obj = obj.pool.get(self._obj)
 
         # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
-        from orm import except_orm
-        names = {}
-        for record in list(set(filter(None, res.values()))):
-            try:
-                record_name = dict(obj.name_get(cr, user, [record], context))
-            except except_orm:
-                record_name = {}
-                record_name[record] = '// Access Denied //'
-            names.update(record_name)
-
-        for r in res.keys():
-            if res[r] and res[r] in names:
-                res[r] = (res[r], names[res[r]])
+        # we use uid=1 because the visibility of a many2one field value (just id and name)
+        # must be the access right of the parent form and not the linked object itself.
+        records = dict(obj.name_get(cr, 1, list(set(filter(None, res.values()))), context=context))
+        for id in res:
+            if res[id] in records:
+                res[id] = (res[id], records[res[id]])
             else:
-                res[r] = False
+                res[id] = False
         return res
 
     def set(self, cr, obj_src, id, field, values, user=None, context=None):
@@ -881,26 +873,34 @@
         super(serialized, self).__init__(string=string, **args)
 
 
+# TODO: review completly this class for speed improvement
 class property(function):
 
     def _get_default(self, obj, cr, uid, prop_name, context=None):
-        from orm import browse_record
+        return self._get_defaults(obj, cr, uid, [prop_name], context=None)[0][prop_name]
+
+    def _get_defaults(self, obj, cr, uid, prop_name, context=None):
         prop = obj.pool.get('ir.property')
-        domain = prop._get_domain_default(cr, uid, prop_name, obj._name, context)
-        ids = prop.search(cr, uid, domain, order='company_id', context=context)
-        if not ids:
-            return False
-
-        default_value = prop.get_by_id(cr, uid, ids, context=context)
-        if isinstance(default_value, browse_record):
-            return default_value.id
-        return default_value or False
+        domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)]
+        ids = prop.search(cr, uid, domain, context=context)
+        replaces = {}
+        default_value = {}.fromkeys(prop_name, False)
+        for prop_rec in prop.browse(cr, uid, ids, context=context):
+            if default_value.get(prop_rec.fields_id.name, False):
+                continue
+            value = prop.get_by_record(cr, uid, prop_rec, context=context) or False
+            default_value[prop_rec.fields_id.name] = value
+            if value and (prop_rec.type == 'many2one'):
+                replaces.setdefault(value._name, {})
+                replaces[value._name][value.id] = True
+        return default_value, replaces
 
     def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
         prop = obj.pool.get('ir.property')
         vids = [obj._name + ',' + str(oid) for oid in  ids]
 
-        domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
+        domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
+        #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
         if domain is not None:
             domain = [('res_id', 'in', vids)] + domain
             return prop.search(cr, uid, domain, context=context)
@@ -908,11 +908,12 @@
             return []
 
 
+    # TODO: to rewrite more clean
     def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
         if context is None:
             context = {}
 
-        nids = self._get_by_id(obj, cr, uid, prop_name, [id], context)
+        nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
         if nids:
             cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
 
@@ -933,29 +934,37 @@
                 'company_id': cid,
                 'fields_id': def_id,
                 'type': self._type,
-                }, context=context)
+            }, context=context)
         return False
 
 
     def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
-        from orm import browse_record
         properties = obj.pool.get('ir.property')
-
-        default_val = self._get_default(obj, cr, uid, prop_name, context)
-
-        nids = self._get_by_id(obj, cr, uid, prop_name, ids, context)
+        domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
+        domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in  ids])]
+        nids = properties.search(cr, uid, domain, context=context)
+        default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context)
 
         res = {}
         for id in ids:
-            res[id] = default_val
-        for prop in properties.browse(cr, uid, nids):
-            value = prop.get_by_id(context=context)
-            if isinstance(value, browse_record):
-                if not value.exists():
-                    cr.execute('DELETE FROM ir_property WHERE id=%s', (prop.id,))
-                    continue
-                value = value.id
-            res[prop.res_id.id] = value or False
+            res[id] = default_val.copy()
+
+        brs = properties.browse(cr, uid, nids, context=context)
+        for prop in brs:
+            value = properties.get_by_record(cr, uid, prop, context=context)
+            res[prop.res_id.id][prop.fields_id.name] = value or False
+            if value and (prop.type == 'many2one'):
+                replaces.setdefault(value._name, {})
+                replaces[value._name][value.id] = True
+
+        for rep in replaces:
+            replaces[rep] = dict(obj.pool.get(rep).name_get(cr, uid, replaces[rep].keys(), context=context))
+
+        for prop in prop_name:
+            for id in ids:
+                if res[id][prop] and hasattr(res[id][prop], '_name'):
+                    res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False))
+
         return res
 
 
@@ -972,7 +981,7 @@
         # TODO remove obj_prop parameter (use many2one type)
         self.field_id = {}
         function.__init__(self, self._fnct_read, False, self._fnct_write,
-                          obj_prop, **args)
+                          obj_prop, multi='properties', **args)
 
     def restart(self):
         self.field_id = {}

=== modified file 'bin/osv/orm.py'
--- bin/osv/orm.py	2010-09-13 00:48:40 +0000
+++ bin/osv/orm.py	2010-09-20 09:31:47 +0000
@@ -1809,10 +1809,10 @@
 
     def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
         """
-        Private implementation of search() method, allowing specifying the uid to use for the access right check. 
+        Private implementation of search() method, allowing specifying the uid to use for the access right check.
         This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
         by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
-        
+
         :param access_rights_uid: optional user ID to use when checking access rights
                                   (not for ir.rules, this is only for ir.model.access)
         """
@@ -1841,7 +1841,7 @@
 
     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
         """
-        Search for records and their display names according to a search domain. 
+        Search for records and their display names according to a search domain.
 
         :param cr: database cursor
         :param user: current user id
@@ -2207,7 +2207,7 @@
             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 
+                # 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)))
@@ -2215,7 +2215,7 @@
         # Take care of adding join(s) if groupby is an '_inherits'ed field
         groupby_list = groupby
         if groupby:
-            if groupby and isinstance(groupby, list):
+            if isinstance(groupby, list):
                 groupby = groupby[0]
             tables, where_clause, qfield = self._inherits_join_calc(groupby, tables, where_clause)
 
@@ -2234,14 +2234,14 @@
         flist = ''
         group_by = groupby
         if groupby:
-            if fget.get(groupby, False):
+            if fget.get(groupby):
                 if fget[groupby]['type'] in ('date', 'datetime'):
                     flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
                     groupby = "to_char(%s,'yyyy-mm')" % (groupby)
                 else:
                     flist = groupby
             else:
-                # Don't allow arbitrary values, as this would be a SQL injection vector! 
+                # Don't allow arbitrary values, as this would be a SQL injection vector!
                 raise except_orm(_('Invalid group_by'),
                                  _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
 
@@ -2256,10 +2256,7 @@
                     flist += ','
                 flist += operator+'('+f+') as '+f
 
-        if groupby:
-            gb = ' group by '+groupby
-        else:
-            gb = ''
+        gb = groupby and (' group by '+groupby) or ''
         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
@@ -2268,7 +2265,19 @@
                 if val == None: r[fld] = False
             alldata[r['id']] = r
             del r['id']
-        data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
+        if groupby and fget[groupby]['type'] == 'many2one':
+            data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=groupby, context=context)
+            data_read = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
+            # restore order of the search as read() uses the default _order:
+            data = []
+            for id in data_ids:
+                for rec in data_read:
+                    if rec['id'] == id:
+                        data.append(rec)
+        else:
+            data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
+            if groupby:
+                data.sort(lambda x,y:cmp(x[groupby],y[groupby]))
         for d in data:
             if groupby:
                 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
@@ -2314,9 +2323,9 @@
         :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
+        :return: (tables, where_clause, qualified_field) where ``tables`` 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. 
+                 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:
@@ -2988,19 +2997,20 @@
                     column = self._inherit_fields[key][2]
                 else:
                     continue
-                if v and column._type == 'reference':
-                    model_name, ref_id = v.split(',', 1)
-                    model = self.pool.get(model_name)
-                    if not model:
-                        reset = True
-                    else:
-                        cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
-                        reset = not cr.fetchone()[0]
-                    if reset:
-                        if column._classic_write:
-                            query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
-                            cr.execute(query, (r['id'],))
-                        r[key] = False
+# TODO: removed this, it's too slow
+#                if v and column._type == 'reference':
+#                    model_name, ref_id = v.split(',', 1)
+#                    model = self.pool.get(model_name)
+#                    if not model:
+#                        reset = True
+#                    else:
+#                        cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
+#                        reset = not cr.fetchone()[0]
+#                    if reset:
+#                        if column._classic_write:
+#                            query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
+#                            cr.execute(query, (r['id'],))
+#                        r[key] = False
 
         if isinstance(ids, (int, long, dict)):
             return result and result[0] or False
@@ -3888,13 +3898,13 @@
         :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. 
+                            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 
+                 table names that need to be included in the FROM clause.
+        :rtype: tuple
         """
         if not context:
             context = {}
@@ -3926,7 +3936,7 @@
 
     def _check_qorder(self, word):
         if not regex_order.match(word):
-            raise except_orm(_('AccessError'), _('Bad query.'))
+            raise except_orm(_('AccessError'), _('Invalid "order" specified. A valid "order" specification is a comma-separated list of valid field names (optionally followed by asc/desc for the direction)'))
         return True
 
     def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
@@ -3939,7 +3949,7 @@
                           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. 
+                              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)
@@ -3952,13 +3962,86 @@
             return True
         return False
 
+    def _generate_m2o_order_by(self, order_field, tables, where_clause):
+        """
+        Add possibly missing JOIN and generate the ORDER BY clause for m2o fields,
+        either native m2o fields or function/related fields that are stored, including
+        intermediate JOINs for inheritance if required.
+        """
+        if order_field not in self._columns and order_field in self._inherit_fields:
+            # also add missing joins for reaching the table containing the m2o field
+            tables, where_clause, qualified_field = self._inherits_join_calc(order_field, tables, where_clause)
+            order_field_column = self._inherit_fields[order_field][2]
+        else:
+            qualified_field = '"%s"."%s"' % (self._table, order_field)
+            order_field_column = self._columns[order_field]
+
+        assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
+        assert order_field_column._classic_write or getattr(order_field_column, 'store', False), "Many2one function/related fields must be stored to be used as ordering fields"
+
+        # figure out the applicable order_by for the m2o
+        dest_model = self.pool.get(order_field_column._obj)
+        m2o_order = dest_model._order
+        if not regex_order.match(m2o_order):
+            # _order is complex, can't use it here, so we default to _rec_name
+            m2o_order = dest_model._rec_name
+        else:
+            # extract the first field name, to be able to qualify it and add desc/asc
+            m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
+
+        # the perhaps missing join:
+        quoted_model_table = '"%s"' % dest_model._table
+        if quoted_model_table not in tables:
+            tables.append(quoted_model_table)
+            where_clause.append('%s = %s.id' % (qualified_field, quoted_model_table))
+
+        return ('%s.%s' % (quoted_model_table, m2o_order), tables, where_clause)
+
+
+    def _generate_order_by(self, order_spec, tables, where_clause):
+        """
+        Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
+        a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
+
+        :raise" except_orm in case order_spec is malformed
+        """
+        order_by_clause = self._order
+        if order_spec:
+            order_by_elements = []
+            self._check_qorder(order_spec)
+            for order_part in order_spec.split(','):
+                order_split = order_part.strip().split(' ')
+                order_field = order_split[0].strip()
+                order_direction = order_split[1].strip() if len(order_split) == 2 else ''
+                if order_field in self._columns:
+                    order_column = self._columns[order_field]
+                    if order_column._classic_read:
+                        order_by_clause = '"%s"."%s"' % (self._table, order_field)
+                    elif order_column._type == 'many2one':
+                        order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
+                    else:
+                        continue # ignore non-readable or "non-joignable" fields
+                elif order_field in self._inherit_fields:
+                    parent_obj = self.pool.get(self._inherit_fields[order_field][0])
+                    order_column = parent_obj._columns[order_field]
+                    if order_column._classic_read:
+                        tables, where_clause, order_by_clause = self._inherits_join_calc(order_field, tables, where_clause)
+                    elif order_column._type == 'many2one':
+                        order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
+                    else:
+                        continue # ignore non-readable or "non-joignable" fields
+                order_by_elements.append("%s %s" % (order_by_clause, order_direction))
+            order_by_clause = ",".join(order_by_elements)
+
+        return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
+
     def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
         """
-        Private implementation of search() method, allowing specifying the uid to use for the access right check. 
+        Private implementation of search() method, allowing specifying the uid to use for the access right check.
         This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
         by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
         This is ok at the security level because this method is private and not callable through XML-RPC.
-        
+
         :param access_rights_uid: optional user ID to use when checking access rights
                                   (not for ir.rules, this is only for ir.model.access)
         """
@@ -3982,35 +4065,17 @@
                 tables = list(set(tables).union(set(previous_tables)))
 
         where = where_clause
-
-        order_by = self._order
-        if order:
-            self._check_qorder(order)
-            o = order.split(' ')[0]
-            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):
-                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)
-
+        order_by = self._generate_order_by(order, tables, where_clause)
         limit_str = limit and ' limit %d' % limit or ''
         offset_str = offset and ' offset %d' % offset or ''
-
-        if where:
-            where_str = " WHERE %s" % " AND ".join(where)
-        else:
-            where_str = ""
+        where_str = where and (" WHERE %s" % " AND ".join(where)) or ''
 
         if count:
             cr.execute('select count(%s.id) from ' % self._table +
                     ','.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, where_clause_params)
+        cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str + order_by + limit_str+offset_str, where_clause_params)
         res = cr.fetchall()
         return [x[0] for x in res]
 

=== modified file 'bin/sql_db.py'
--- bin/sql_db.py	2010-09-06 14:06:45 +0000
+++ bin/sql_db.py	2010-09-20 09:31:47 +0000
@@ -113,8 +113,6 @@
             self.__logger.warn(query)
             self.__logger.warn("SQL queries cannot contain %d or %f anymore. "
                                "Use only %s")
-            if params:
-                query = query.replace('%d', '%s').replace('%f', '%s')
 
         if self.sql_log:
             now = mdt.now()
@@ -167,6 +165,7 @@
                 sqllogitems = sqllogs[type].items()
                 sqllogitems.sort(key=lambda k: k[1][1])
                 self.__logger.log(logging.DEBUG_SQL, "SQL LOG %s:", type)
+                sqllogitems.sort(lambda x,y: cmp(x[1][0], y[1][0]))
                 for r in sqllogitems:
                     delay = timedelta(microseconds=r[1][1])
                     self.__logger.log(logging.DEBUG_SQL, "table: %s: %s/%s",

=== modified file 'bin/tools/config.py'
--- bin/tools/config.py	2010-09-12 12:07:43 +0000
+++ bin/tools/config.py	2010-09-20 09:31:47 +0000
@@ -98,7 +98,7 @@
         self.has_ssl = check_ssl()
 
         self._LOGLEVELS = dict([(getattr(netsvc, 'LOG_%s' % x), getattr(logging, x))
-                          for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'NOTSET')])
+                          for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'DEBUG_RPC_ANSWER','NOTSET')])
 
         version = "%s %s" % (release.description, release.version)
         self.parser = parser = optparse.OptionParser(version=version)

=== modified file 'bin/tools/yaml_import.py'
--- bin/tools/yaml_import.py	2010-09-16 15:08:05 +0000
+++ bin/tools/yaml_import.py	2010-09-20 09:31:47 +0000
@@ -70,14 +70,16 @@
 
 def is_eval(node):
     return isinstance(node, yaml_tag.Eval)
-    
+
 def is_ref(node):
     return isinstance(node, yaml_tag.Ref) \
         or _is_yaml_mapping(node, yaml_tag.Ref)
-    
+
 def is_ir_set(node):
     return _is_yaml_mapping(node, yaml_tag.IrSet)
 
+def is_string(node):
+    return isinstance(node, basestring)
 
 class TestReport(object):
     def __init__(self):
@@ -374,12 +376,12 @@
         elif column._type == "many2many":
             ids = [self.get_id(xml_id) for xml_id in expression]
             value = [(6, 0, ids)]
-        elif column._type == "date":
-            # enforce ISO format for date values, to be locale-agnostic during tests
+        elif column._type == "date" and is_string(expression):
+            # enforce ISO format for string date values, to be locale-agnostic during tests
             time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
             value = expression
-        elif column._type == "datetime":
-            # enforce ISO format for datetime values, to be locale-agnostic during tests
+        elif column._type == "datetime" and is_string(expression):
+            # enforce ISO format for string datetime values, to be locale-agnostic during tests
             time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
             value = expression
         else: # scalar field