account,account_report,base,product,scrum,stock,kernel: fix name_search
[odoo/odoo.git] / bin / osv / orm.py
1 # -*- coding: latin1 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
5 #
6 # $Id: orm.py 1008 2005-07-25 14:03:55Z pinky $
7 #
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
13 # Service Company
14 #
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28 #
29 ##############################################################################
30
31 #
32 # Object relationnal mapping to postgresql module
33 #    . Hierarchical structure
34 #    . Constraints consistency, validations
35 #    . Object meta Data depends on its status
36 #    . Optimised processing by complex query (multiple actions at once)
37 #    . Default fields value
38 #    . Permissions optimisation
39 #    . Multi-company features
40 #    . Persistant object: DB postgresql
41 #    . Datas conversions
42 #    . Multi-level caching system
43 #    . 2 different inheritancies
44 #    . Fields:
45 #         - classicals (varchar, integer, boolean, ...)
46 #         - relations (one2many, many2one, many2many)
47 #         - functions
48 #
49 #
50
51 from xml import dom
52 from xml.parsers import expat
53 import string
54 import pooler
55 import psycopg
56 import netsvc
57 import re
58
59 import fields
60 import ir
61 import tools
62
63 prof = 0
64
65 def intersect(la, lb):
66         return filter(lambda x: x in lb, la)
67
68
69 class except_orm(Exception):
70         def __init__(self, name, value):
71                 self.name = name
72                 self.value = value
73                 self.args = (name, value)
74
75 #class find_fields(object):
76 #       def _start_el(self,name, attrs):
77 #               if name == 'field' and attrs.get('name', False):
78 #                       self.datas[str(attrs['name'])] = attrs.get('preload','')
79 #       def __init__(self):
80 #               self.datas = {}
81 #       def parse(self, datas):
82 #               p = expat.ParserCreate()
83 #               p.StartElementHandler = self._start_el
84 #               p.Parse(datas, 1)
85 #               return self.datas
86
87 #
88 # TODO: trigger pour chaque action
89 #
90 # Readonly python database object browser
91 class browse_null(object):
92
93         def __init__(self):
94                 self.id=False
95
96         def __getitem__(self, name):
97                 return False
98
99         def __int__(self):
100                 return False
101
102         def __str__(self):
103                 return ''
104
105         def __nonzero__(self):
106                 return False
107
108 #
109 # TODO: execute an object method on browse_record_list
110 #
111 class browse_record_list(list):
112
113         def __init__(self, lst, context={}):
114                 super(browse_record_list, self).__init__(lst)
115                 self.context = context
116
117 #
118 # table : the object (inherited from orm)
119 # context : a dictionnary with an optionnal context
120 #    default to : browse_record_list
121 #
122 class browse_record(object):
123         def __init__(self, cr, uid, id, table, cache, context={}, list_class = None):
124                 assert id, 'Wrong ID for the browse record, got '+str(id)+ ', expected an integer.'
125                 self._list_class = list_class or browse_record_list
126                 self._cr = cr
127                 self._uid = uid
128                 self._id = id
129                 self._table = table
130                 self._table_name = self._table._name
131                 self._context = context
132
133                 cache.setdefault(table._name, {})
134                 self._data = cache[table._name]
135                 if not id in self._data:
136                         self._data[id] = {'id':id}
137                 self._cache = cache
138
139         def __getitem__(self, name):
140                 if name == 'id':
141                         return self._id
142                 if not self._data[self._id].has_key(name):
143                         # build the list of fields we will fetch
144
145                         # fetch the definition of the field which was asked for
146                         if name in self._table._columns:
147                                 col = self._table._columns[name]
148                         elif name in self._table._inherit_fields:
149                                 col = self._table._inherit_fields[name][2]
150                         elif hasattr(self._table, name):
151                                 return getattr(self._table, name)
152                         else:
153                                 print "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name)
154                                 return False
155                         
156                         # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
157                         if col._classic_write:
158                                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
159                                 ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
160                                 # gen the list of inherited fields
161                                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
162                                 # complete the field list with the inherited fields which are classic or many2one
163                                 ffields += filter(lambda x: x[1]._classic_write, inherits)
164                         # otherwise we fetch only that field
165                         else:
166                                 ffields = [(name,col)]
167                                 
168                         if isinstance(col, fields.function):
169                                 ids = [self._id]
170                         else:
171                                 # filter out all ids which were already fetched (and are already in _data)
172                                 ids = filter(lambda id: not self._data[id].has_key(name), self._data.keys())
173                         
174                         # read the data
175                         datas = self._table.read(self._cr, self._uid, ids, map(lambda x: x[0], ffields), context=self._context, load="_classic_write")
176                         
177                         # create browse records for 'remote' objects
178                         for data in datas:
179                                 for n,f in ffields:
180                                         if isinstance(f, fields.many2one) or isinstance(f, fields.one2one):
181                                                 if data[n]:
182                                                         obj = self._table.pool.get(f._obj)
183                                                         compids=False
184                                                         #
185                                                         # Removed for performance sake. (Bug may arise) [20070516]
186                                                         #
187 #                                                       if 'company_id' in obj._columns :
188 #                                                               compids = tools.get_user_companies(self._cr, self._uid)
189 #                                                               if compids:
190 #                                                                       self._cr.execute('SELECT id FROM '+obj._table+' where id = %d AND  (company_id in ('+','.join(map(str,compids))+') or company_id is null)', (data[n],))
191 #                                                                       if not self._cr.fetchall():
192 #                                                                               raise except_orm('BrowseError', 'Object %s (id:%d) is linked to the object %s (id:%d) which is not in your company' %(self._table._description, self._id, obj._description, data[n]))
193                                                         data[n] = browse_record(self._cr, self._uid, data[n], obj, self._cache, context=self._context, list_class=self._list_class)
194                                                 else:
195                                                         data[n] = browse_null()
196                                         elif isinstance(f, (fields.one2many, fields.many2many)) and len(data[n]):
197                                                 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) for id in data[n]], self._context)
198                                 self._data[data['id']].update(data)
199                 return self._data[self._id][name]
200
201         def __getattr__(self, name):
202 #               raise an AttributeError exception.
203                 return self[name]
204
205         def __int__(self):
206                 return self._id
207
208         def __str__(self):
209                 return "browse_record(%s, %d)" % (self._table_name, self._id)
210
211         def __eq__(self, other):
212                 return (self._table_name, self._id) == (other._table_name, other._id)
213
214         def __ne__(self, other):
215                 return (self._table_name, self._id) != (other._table_name, other._id)
216
217         # we need to define __unicode__ even though we've already defined __str__ 
218         # because we have overridden __getattr__
219         def __unicode__(self):
220                 return unicode(str(self))
221
222         def __hash__(self):
223                 return hash((self._table_name, self._id))
224
225         __repr__ = __str__
226
227
228 # returns a tuple (type returned by postgres when the column was created, type expression to create the column)
229 def get_pg_type(f):
230         type_dict = {fields.boolean:'bool', fields.integer:'int4', fields.text:'text', fields.date:'date', fields.time:'time', fields.datetime:'timestamp', fields.binary:'bytea', fields.many2one:'int4'}
231
232         if type_dict.has_key(type(f)):
233                 f_type = (type_dict[type(f)], type_dict[type(f)])
234         elif isinstance(f, fields.float):
235                 if f.digits:
236                         f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0],f.digits[1]))
237                 else:
238                         f_type = ('float8', 'DOUBLE PRECISION')
239         elif isinstance(f, (fields.char, fields.reference)):
240                 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
241         elif isinstance(f, fields.selection):
242                 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
243                         f_size = reduce(lambda x,y: max(x,len(y[0])), f.selection, f.size or 16)
244                 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
245                         f_size = -1
246                 else:
247                         f_size = (hasattr(f,'size') and f.size) or 16
248                         
249                 if f_size == -1:
250                         f_type = ('int4', 'INTEGER')
251                 else:
252                         f_type = ('varchar', 'VARCHAR(%d)' % f_size)
253         elif isinstance(f, fields.function) and type_dict.has_key(type(locals().get('fields.'+(f._type)))):
254                 t=type(locals().get('fields.'+(f._type)))
255                 f_type = (type_dict[t], type_dict[t])
256         elif isinstance(f, fields.function) and f._type == 'float':
257                 f_type = ('float8', 'DOUBLE PRECISION')
258         else:
259                 logger = netsvc.Logger()
260                 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (f_type))
261                 f_type = None
262         return f_type
263
264                 
265         
266 class orm(object):
267         _columns = {}
268         _sql_constraints = []
269         _constraints = []
270         _defaults = {}
271         _log_access = True
272         _table = None
273         _name = None
274         _rec_name = 'name'
275         _parent_name = 'parent_id'
276         _order = 'id'
277         _inherits = {}
278         _sequence = None
279         _description = None
280         _protected = ['read','write','create','default_get','perm_read','perm_write','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data','search_count']
281         def _field_create(self, cr):
282                 cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name)
283                 if not cr.rowcount:
284                         # reference model in order to have a description of its fonctionnality in custom_report
285                         cr.execute("INSERT INTO ir_model (model, name, info) VALUES (%s, %s, %s)", (self._name, self._description, self.__doc__)) 
286                 cr.commit()
287
288                 for k in self._columns:
289                         f = self._columns[k]
290                         cr.execute("select id, relate from ir_model_fields where model=%s and name=%s", (self._name,k))
291                         if not cr.rowcount:
292                                 cr.execute("select id from ir_model where model='%s'" % self._name)
293                                 model_id = cr.fetchone()[0]
294                                 cr.execute("INSERT INTO ir_model_fields (model_id, model, name, field_description, ttype, relate,relation,group_name,view_load) VALUES (%d,%s,%s,%s,%s,%s,%s,%s,%s)", (model_id, self._name, k, f.string.replace("'", " "), f._type, (f.relate and 'True') or 'False', f._obj or 'NULL', f.group_name or '', (f.view_load and 'True') or 'False'))
295                         else:
296                                 id, relate = cr.fetchone()
297                                 if relate != f.relate:
298                                         cr.execute("UPDATE ir_model_fields SET relate=%s WHERE id=%d", ((f.relate and 'True') or 'False', id))
299                 cr.commit()
300
301         def _auto_init(self, cr):
302                 logger = netsvc.Logger()
303                 create = False
304                 self._field_create(cr)
305                 if not hasattr(self, "_auto") or self._auto:
306                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
307                         if not cr.rowcount:
308                                 cr.execute("CREATE TABLE %s(id SERIAL NOT NULL, perm_id INTEGER, PRIMARY KEY(id)) WITH OIDS" % self._table)
309                                 create = True
310                         cr.commit()
311                         if self._log_access:
312                                 logs = {
313                                         'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
314                                         'create_date': 'TIMESTAMP',
315                                         'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
316                                         'write_date': 'TIMESTAMP'
317                                 }
318                                 for k in logs:
319                                         cr.execute(
320                                                 """
321                                                 SELECT c.relname 
322                                                 FROM pg_class c, pg_attribute a
323                                                 WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid
324                                                 """ % (self._table, k))
325                                         if not cr.rowcount:
326                                                 cr.execute("ALTER TABLE %s ADD COLUMN %s %s" % 
327                                                         (self._table, k, logs[k]))
328                                                 cr.commit()
329
330                         # iterate on the database columns to drop the NOT NULL constraints
331                         # of fields which were required but have been removed
332                         cr.execute(
333                                 "SELECT a.attname, a.attnotnull "\
334                                 "FROM pg_class c, pg_attribute a "\
335                                 "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table)
336                         db_columns = cr.dictfetchall()
337                         for column in db_columns:
338                                 if column['attname'] not in ('id', 'oid', 'tableoid', 'ctid', 'xmin', 'xmax', 'cmin', 'cmax'):
339                                         if column['attnotnull'] and column['attname'] not in self._columns:
340                                                 cr.execute("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL" % (self._table, column['attname']))
341
342                         # iterate on the "object columns"
343                         for k in self._columns:
344                                 if k in ('id','perm_id','write_uid','write_date','create_uid','create_date'):
345                                         continue
346                                         #raise 'Can not define a column %s. Reserved keyword !' % (k,)
347                                 f = self._columns[k]
348
349                                 if isinstance(f, fields.one2many):
350                                         cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'"%f._obj)
351                                         if cr.fetchone():
352                                                 q = """SELECT count(*) as c FROM pg_class c,pg_attribute a WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid"""%(f._obj,f._fields_id)
353                                                 cr.execute(q)
354                                                 res = cr.fetchone()[0]
355                                                 if not res:
356                                                         cr.execute("ALTER TABLE %s ADD FOREIGN KEY (%s) REFERENCES %s ON DELETE SET NULL" % (self._obj, f._fields_id, f._table))
357                                 elif isinstance(f, fields.many2many):
358                                         cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'"%f._rel)
359                                         if not cr.dictfetchall():
360                                                 #FIXME: Remove this try/except
361                                                 try:
362                                                         ref = self.pool.get(f._obj)._table
363                                                 except AttributeError:
364                                                         ref = f._obj.replace('.','_')
365                                                 cr.execute("CREATE TABLE %s (%s INTEGER NOT NULL REFERENCES %s ON DELETE CASCADE, %s INTEGER NOT NULL REFERENCES %s ON DELETE CASCADE) WITH OIDS"%(f._rel,f._id1,self._table,f._id2,ref))
366                                                 cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (f._rel,f._id1,f._rel,f._id1))
367                                                 cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (f._rel,f._id2,f._rel,f._id2))
368                                                 cr.commit()
369                                 else:
370                                         q = """SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size FROM pg_class c,pg_attribute a,pg_type t WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid AND a.atttypid=t.oid""" % (self._table, k.lower())
371                                         cr.execute(q)
372                                         res = cr.dictfetchall() 
373                                         if not res:
374                                                 if not isinstance(f,fields.function) or f.store:
375
376                                                         # add the missing field
377                                                         cr.execute("ALTER TABLE %s ADD COLUMN %s %s" % (self._table, k, get_pg_type(f)[1]))
378                                                         
379                                                         # initialize it
380                                                         if not create and k in self._defaults:
381                                                                 default = self._defaults[k](self, cr, 1, {})
382                                                                 if not default:
383                                                                         cr.execute("UPDATE %s SET %s=NULL" % (self._table, k))
384                                                                 else:
385                                                                         cr.execute("UPDATE %s SET %s='%s'" % (self._table, k, default))
386
387                                                         # and add constraints if needed
388                                                         if isinstance(f, fields.many2one):
389                                                                 #FIXME: Remove this try/except
390                                                                 try:
391                                                                         ref = self.pool.get(f._obj)._table
392                                                                 except AttributeError:
393                                                                         ref = f._obj.replace('.','_')
394                                                                 # ir_actions is inherited so foreign key doesn't work on it
395                                                                 if ref <> 'ir_actions':
396                                                                         cr.execute("ALTER TABLE %s ADD FOREIGN KEY (%s) REFERENCES %s ON DELETE %s" % (self._table, k, ref, f.ondelete))
397                                                         if f.select:
398                                                                 cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (self._table, k, self._table, k))
399                                                         if f.required:
400                                                                 cr.commit()
401                                                                 try:
402                                                                         cr.execute("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k))
403                                                                 except:
404                                                                         logger.notifyChannel('init', netsvc.LOG_WARNING, 'WARNING: unable to set column %s of table %s not null !\nTry to re-run: tinyerp-server.py --update=module\nIf it doesn\'t work, update records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
405                                                         cr.commit()
406                                         elif len(res)==1:
407                                                 f_pg_def = res[0]
408                                                 f_pg_type = f_pg_def['typname']
409                                                 f_pg_size = f_pg_def['size']
410                                                 f_pg_notnull = f_pg_def['attnotnull']
411                                                 if isinstance(f, fields.function) and not f.store:
412                                                         logger.notifyChannel('init', netsvc.LOG_WARNING, 'column %s (%s) in table %s was converted to a function !\nYou should remove this column from your database.' % (k, f.string, self._table))
413                                                         f_obj_type = None
414                                                 else:
415                                                         f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
416                                                 
417                                                 if f_obj_type:
418                                                         if f_pg_type != f_obj_type:
419                                                                 logger.notifyChannel('init', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed type (DB = %s, def = %s) !" % (k, self._table, f_pg_type, f._type))
420                                                         if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
421                                                                 # columns with the name 'type' cannot be changed for an unknown reason?!
422                                                                 if k != 'type':
423                                                                         if f_pg_size > f.size:
424                                                                                 logger.notifyChannel('init', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed size (DB = %d, def = %d), strings will be truncated !" % (k, self._table, f_pg_size, f.size))
425 #TODO: check si y a des donnees qui vont poser probleme (select char_length(...))
426 #TODO: issue a log message even if f_pg_size < f.size
427                                                                         cr.execute("ALTER TABLE %s RENAME COLUMN %s TO temp_change_size" % (self._table,k))
428                                                                         cr.execute("ALTER TABLE %s ADD COLUMN %s VARCHAR(%d)" % (self._table,k,f.size))
429                                                                         cr.execute("UPDATE %s SET %s=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size))
430                                                                         cr.execute("ALTER TABLE %s DROP COLUMN temp_change_size" % (self._table,))
431                                                                         cr.commit()
432                                                         # if the field is required and hasn't got a NOT NULL constraint
433                                                         if f.required and f_pg_notnull == 0:
434                                                                 # set the field to the default value if any
435                                                                 if self._defaults.has_key(k):
436                                                                         default = self._defaults[k](self, cr, 1, {})
437                                                                         if not (default is False):
438                                                                                 cr.execute("UPDATE %s SET %s='%s' WHERE %s is NULL" % (self._table, k, default, k))
439                                                                                 cr.commit()
440                                                                 # add the NOT NULL constraint
441                                                                 try:
442                                                                         cr.execute("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k))
443                                                                         cr.commit()
444                                                                 except:
445                                                                         logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
446                                                                 cr.commit()
447                                                         elif not f.required and f_pg_notnull == 1:
448                                                                 cr.execute("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL" % (self._table,k))
449                                                                 cr.commit()
450                                                         cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table))
451                                                         res = cr.dictfetchall()
452                                                         if not res and f.select:
453                                                                 cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (self._table, k, self._table, k))
454                                                                 cr.commit()
455                                                         if res and not f.select:
456                                                                 cr.execute("DROP INDEX %s_%s_index" % (self._table, k))
457                                                                 cr.commit()
458                                         else:
459                                                 print "ERROR"
460                 else:
461                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
462                         create = not bool(cr.fetchone())
463
464                 for (key,con,_) in self._sql_constraints:
465                         cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key))
466                         if not cr.dictfetchall():
467                                 try:
468                                         cr.execute('alter table %s add constraint %s_%s %s' % (self._table,self._table,key, con,))
469                                         cr.commit()
470                                 except:
471                                         logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table,self._table,key, con,))
472
473                 if create:
474                         if hasattr(self,"_sql"):
475                                 for line in self._sql.split(';'):
476                                         line2 = line.replace('\n','').strip()
477                                         if line2:
478                                                 cr.execute(line2)
479                                                 cr.commit()
480
481         def __init__(self):
482                 if not self._table:
483                         self._table=self._name.replace('.','_')
484                 if not self._description:
485                         self._description = self._name
486                 for (key,_,msg) in self._sql_constraints:
487                         self.pool._sql_error[self._table+'_'+key] = msg
488                 
489 #               if self.__class__.__name__ != 'fake_class':
490                 self._inherits_reload()
491                 if not self._sequence:
492                         self._sequence = self._table+'_id_seq'
493                 for k in self._defaults:
494                         assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
495                 # FIXME: does not work at all
496 #               if self._log_access:
497 #                       self._columns.update({
498 #                               'create_uid': fields.many2one('res.users','Creation user',required=True, readonly=True),
499 #                               'create_date': fields.datetime('Creation date',required=True, readonly=True),
500 #                               'write_uid': fields.many2one('res.users','Last modification by', readonly=True),
501 #                               'write_date': fields.datetime('Last modification date', readonly=True),
502 #                               })
503 #                       self._defaults.update({
504 #                               'create_uid': lambda self,cr,uid,context : uid,
505 #                               'create_date': lambda *a : time.strftime("%Y-%m-%d %H:%M:%S")
506 #                               })
507         #
508         # Update objects that uses this one to update their _inherits fields
509         #
510         def _inherits_reload_src(self):
511                 for obj in self.pool.obj_pool.values():
512                         if self._name in obj._inherits:
513                                 obj._inherits_reload()
514
515         def _inherits_reload(self):
516                 res = {}
517                 for table in self._inherits:
518                         res.update(self.pool.get(table)._inherit_fields)
519                         for col in self.pool.get(table)._columns.keys():
520                                 res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col])
521                         for col in self.pool.get(table)._inherit_fields.keys():
522                                 res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
523                 self._inherit_fields=res
524                 self._inherits_reload_src()
525
526         def browse(self, cr, uid, select, context=None, list_class=None):
527                 if not context:
528                         context={}
529                 self._list_class = list_class or browse_record_list
530                 cache = {}
531                 # need to accepts ints and longs because ids coming from a method 
532                 # launched by button in the interface have a type long...
533                 if isinstance(select, (int, long)):
534                         return browse_record(cr,uid,select,self,cache, context=context, list_class=self._list_class)
535                 elif isinstance(select,list):
536                         return self._list_class([browse_record(cr,uid,id,self,cache, context=context, list_class=self._list_class) for id in select], context)
537                 else:
538                         return []
539
540         # TODO: implement this
541         def __export_row(self, cr, uid, row, fields, prefix, context=None):
542                 if not context:
543                         context={}
544                 lines = []
545                 data = map(lambda x: '', range(len(fields)))
546                 done = []
547                 for fpos in range(len(fields)):
548                         f = fields[fpos]
549                         if f and f[:len(prefix)] == prefix:
550                                 r = row
551                                 i = 0
552                                 while i<len(f):
553                                         r = r[f[i]]
554                                         if not r:
555                                                 break
556                                         if isinstance(r, (browse_record_list, list)):
557                                                 first = True
558                                                 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) or [], fields)
559                                                 if fields2 in done:
560                                                         break
561                                                 done.append(fields2)
562                                                 for row2 in r:
563                                                         lines2 = self.__export_row(cr, uid, row2, fields2, f[i+2:], context)
564                                                         if first:
565                                                                 for fpos2 in range(len(fields)):
566                                                                         if lines2 and lines2[0][fpos2]:
567                                                                                 data[fpos2] = lines2[0][fpos2]
568                                                                 lines+= lines2[1:]
569                                                                 first = False
570                                                         else:
571                                                                 lines+= lines2
572                                                 break
573                                         i+=1
574                                 if i==len(f):
575                                         data[fpos] = str(r or '')
576                 return [data] + lines
577
578         def export_data(self, cr, uid, ids, fields, context=None):
579                 if not context:
580                         context={}
581                 fields = map(lambda x: x.split('/'), fields)
582                 datas = []
583                 for row in self.browse(cr, uid, ids, context):
584                         datas += self.__export_row(cr, uid, row, fields, [], context)
585                 return datas
586
587         def import_data(self, cr, uid, fields, datas,  mode='init', current_module=None, noupdate=False, context=None):
588                 if not context:
589                         context={}
590                 fields = map(lambda x: x.split('/'), fields)
591                 logger = netsvc.Logger()
592                 def process_liness(self, datas, prefix, fields_def, position=0):
593                         line = datas[position]
594                         row = {}
595                         translate = {}
596                         todo = []
597                         warning = ''
598                         data_id= False
599                         #
600                         # Import normal fields
601                         #
602                         for i in range(len(fields)):
603                                 if i>=len(line):
604                                         raise 'Please check that all your lines have %d cols.' % (len(fields),)
605                                 field = fields[i]
606                                 if field == ["id"]:
607                                         data_id= line[i]
608                                         continue
609                                 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
610                                         res_id = False
611                                         if line[i]:
612                                                 if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
613                                                         res_id = []
614                                                         for word in line[i].split(','):
615                                                                 module, xml_id = word.rsplit('.', 1)
616                                                                 ir_model_data_obj = self.pool.get('ir.model.data')
617                                                                 id=ir_model_data_obj._get_id(cr, uid, module, xml_id)
618                                                                 res_id2=ir_model_data_obj.read(cr, uid, [id], ['res_id'])[0]['res_id']
619                                                                 if res_id2:
620                                                                         res_id.append(res_id2)
621                                                         if len(res_id):
622                                                                 res_id=[(6,0,res_id)]
623                                                 else:
624                                                         module, xml_id = line[i].rsplit('.', 1)
625                                                         ir_model_data_obj = self.pool.get('ir.model.data')
626                                                         id=ir_model_data_obj._get_id(cr, uid, module, xml_id)
627                                                         res_id=ir_model_data_obj.read(cr, uid, [id], ['res_id'])[0]['res_id']
628                                         row[field[0][:-3]] = res_id or False
629                                         continue
630                                 if (len(field)==len(prefix)+1) and len(field[len(prefix)].split(':lang=')) == 2:
631                                         f, lang = field[len(prefix)].split(':lang=')
632                                         translate.setdefault(lang, {})[f]=line[i] or False
633                                         continue
634                                 if (len(field)==len(prefix)+1) and (prefix==field[0:len(prefix)]):
635                                         if fields_def[field[len(prefix)]]['type']=='integer':
636                                                 res =line[i] and int(line[i])
637                                         elif fields_def[field[len(prefix)]]['type']=='float':
638                                                 res =line[i] and float(line[i])
639                                         elif fields_def[field[len(prefix)]]['type']=='selection':
640                                                 res = False
641                                                 for key,val in fields_def[field[len(prefix)]]['selection']:
642                                                         if key==line[i]:             #val==line[i] if from the client !
643                                                                 res = key
644                                                 if line[i] and not res:
645                                                         logger.notifyChannel("import", netsvc.LOG_WARNING, "key '%s' not found in selection field '%s'" %(line[i], field[len(prefix)]))
646                                         elif fields_def[field[len(prefix)]]['type']=='many2one':
647                                                 res = False
648                                                 if line[i]:
649                                                         relation = fields_def[field[len(prefix)]]['relation']
650                                                         res2 = self.pool.get(relation).name_search(cr, uid, line[i], [],operator='=')
651                                                         res = (res2 and res2[0][0]) or False
652                                                         if not res and line[i]:
653                                                                 warning += ('Relation not found: '+line[i]+' on '+relation + ' !\n')
654                                                         if not res:
655                                                                 logger.notifyChannel("import",netsvc.LOG_WARNING,'Relation not found: '+line[i]+' on '+relation + ' !\n')
656                                         elif fields_def[field[len(prefix)]]['type']=='many2many':
657                                                 res = []
658                                                 if line[i]:
659                                                         relation = fields_def[field[len(prefix)]]['relation']
660                                                         for word in line[i].split(','):
661                                                                 res2 = self.pool.get(relation).name_search(cr, uid, word, [],operator='=')
662                                                                 res3 = (res2 and res2[0][0]) or False
663                                                                 if res3:
664                                                                         res.append(res3)
665                                                         if len(res):
666                                                                 res= [(6,0,res)]
667                                         else:
668                                                 res = line[i] or False
669                                         row[field[len(prefix)]] = res
670                                 elif (prefix==field[0:len(prefix)]):
671                                         if field[0] not in todo:
672                                                 todo.append(field[len(prefix)])
673                         #
674                         # Import one2many fields
675                         #
676                         nbrmax = 1
677                         for field in todo:
678                                 newfd = self.pool.get(fields_def[field]['relation']).fields_get(cr, uid, context=context)
679                                 res = process_liness(self, datas, prefix+[field], newfd, position)
680                                 (newrow,max2,w2, translate2,data_id) = res
681                                 nbrmax = max(nbrmax, max2)
682                                 warning = warning+w2
683                                 reduce(lambda x,y: x and y, newrow)
684                                 row[field] = (reduce(lambda x,y: x or y, newrow.values()) and [(0,0,newrow)]) or []
685                                 i = max2
686                                 while (position+i)<len(datas):
687                                         ok = True
688                                         for j in range(len(fields)):
689                                                 field2 = fields[j]
690                                                 if (len(field2)<=(len(prefix)+1)) and datas[position+i][j]:
691                                                         ok = False
692                                         if not ok:
693                                                 break
694
695                                         (newrow,max2,w2, translate2,data_id) = process_liness(self, datas, prefix+[field], newfd, position+i)
696                                         warning = warning+w2
697                                         if reduce(lambda x,y: x or y, newrow.values()):
698                                                 row[field].append((0,0,newrow))
699                                         i+=max2
700                                         nbrmax = max(nbrmax, i)
701
702                         if len(prefix)==0:
703                                 for i in range(max(nbrmax,1)):
704                                         #if datas:
705                                         datas.pop(0)
706                         result = (row, nbrmax, warning, translate, data_id)
707                         return result
708
709                 fields_def = self.fields_get(cr, uid, context=context)
710                 done = 0
711
712                 while len(datas):
713                         (res,other,warning,translate,data_id) = process_liness(self, datas, [], fields_def)
714                         try:
715                                 id= self.pool.get('ir.model.data')._update(cr,uid,self._name, current_module, res, xml_id=data_id, mode=mode, noupdate=noupdate)
716                                 for lang in translate:
717                                         context2=context.copy()
718                                         context2['lang']=lang
719                                         self.write(cr, uid, [id], translate[lang], context2)
720                         except Exception, e:
721                                 logger.notifyChannel("import",netsvc.LOG_ERROR, e)
722                                 cr.rollback()
723                                 return (-1, res, e[0], warning)
724                         done += 1
725                 #
726                 # TODO: Send a request with the result and multi-thread !
727                 #
728                 return (done, 0, 0, 0)
729
730         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
731                 if not context:
732                         context={}
733                 self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
734                 if not fields:
735                         fields = self._columns.keys() + self._inherit_fields.keys()
736                 select = ids
737                 if isinstance(ids, (int, long)):
738                         select = [ids]
739                 result =  self._read_flat(cr, user, select, fields, context, load)
740                 for r in result:
741                         for key,v in r.items():
742                                 if v == None:
743                                         r[key]=False
744                 if isinstance(ids, (int, long)):
745                         return result[0]
746                 return result
747
748         def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
749                 if not context:
750                         context={}
751                 if not ids:
752                         return []
753
754                 if fields==None:
755                         fields = self._columns.keys()
756
757                 # construct a clause for the rules :
758                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
759
760                 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
761                 fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
762
763                 if len(fields_pre) :
764                         fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or x, fields_pre)
765                         if d1:
766                                 cr.execute('select %s from %s where id in (%s) and %s order by %s' % (','.join(fields_pre2 + ['id']), self._table, ','.join([str(x) for x in ids]), d1, self._order),d2)
767                                 if not cr.rowcount == len({}.fromkeys(ids)):
768                                         raise except_orm('AccessError', 'You try to bypass an access rule (Document type: %s).' % self._description)
769                         else:
770                                 cr.execute('select %s from %s where id in (%s) order by %s' % (','.join(fields_pre2 + ['id']), self._table, ','.join([str(x) for x in ids]), self._order))
771
772                         res = cr.dictfetchall()
773                 else:
774                         res = map(lambda x: {'id':x}, ids)
775
776                 if context.get('lang', False) and context['lang'] != 'en_US':
777                         for f in fields_pre:
778                                 if self._columns[f].translate:
779                                         ids = map(lambda x: x['id'], res)
780                                         res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context['lang'], ids)
781                                         for r in res:
782                                                 r[f] = res_trans.get(r['id'], r[f])
783
784                 for table in self._inherits:
785                         col = self._inherits[table]
786                         cols = intersect(self._inherit_fields.keys(), fields)
787                         res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
788
789                         res3 = {}
790                         for r in res2:
791                                 res3[r['id']] = r
792                                 del r['id']
793
794                         for record in res:
795                                 record.update(res3[record[col]])
796                                 if col not in fields:
797                                         del record[col]
798
799                 # all fields which need to be post-processed by a simple function (symbol_get)
800                 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
801                 if fields_post:
802                         # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
803                         # to get the _symbol_get in each occurence
804                         for r in res:
805                                 for f in fields_post:
806                                         r[f] = self.columns[f]._symbol_get(r[f])
807                 ids = map(lambda x: x['id'], res)
808
809                 # all non inherited fields for which the attribute whose name is in load is False
810                 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
811                 for f in fields_post:
812                         # get the value of that field for all records/ids
813                         res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
814                         for record in res:
815                                 record[f] = res2[record['id']]
816
817                 return res
818
819         def _validate(self, cr, uid, ids):
820                 field_error = []
821                 field_err_str = []
822                 for field in self._constraints:
823                         if not field[0](self, cr, uid, ids):
824                                 if len(field)>1:
825                                         field_error+=field[2]
826                                 field_err_str.append(field[1])
827                 if len(field_err_str):
828                         cr.rollback()
829                         raise except_orm('ValidateError', ('\n'.join(field_err_str), ','.join(field_error)))
830
831         def default_get(self, cr, uid, fields, context=None):
832                 if not context:
833                         context={}
834                 value = {}
835                 # get the default values for the inherited fields
836                 for t in self._inherits.keys():
837                         value.update(self.pool.get(t).default_get(cr, uid, fields, context))
838
839                 # get the default values defined in the object
840                 for f in fields:
841                         if f in self._defaults:
842                                 value[f] = self._defaults[f](self, cr, uid, context)
843
844                 # get the default values set by the user and override the default
845                 # values defined in the object
846                 res = ir.ir_get(cr, uid, 'default', False, [self._name])
847                 for id, field, field_value in res:
848                         if field in fields:
849                                 value[field] = field_value 
850                 return value
851
852         def perm_read(self, cr, user, ids, context=None, details=True):
853                 if not context:
854                         context={}
855                 if not ids:
856                         return []
857                 fields = ', p.level, p.uid, p.gid'
858                 if self._log_access:
859                         fields +=', u.create_uid, u.create_date, u.write_uid, u.write_date'
860                 if isinstance(ids, (int, long)):
861                         ids_str = str(ids)
862                 else:
863                         ids_str = string.join(map(lambda x:str(x), ids),',')
864                 cr.execute('select u.id'+fields+' from perm p right join '+self._table+' u on u.perm_id=p.id where u.id in ('+ids_str+')')
865                 res = cr.dictfetchall()
866 #               for record in res:
867 #                       for f in ('ox','ux','gx','uid','gid'):
868 #                               if record[f]==None:
869 #                                       record[f]=False
870                 for r in res:
871                         for key in r:
872                                 r[key] = r[key] or False
873                                 if key in ('write_uid','create_uid','uid') and details:
874                                         if r[key]:
875                                                 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
876                 if isinstance(ids, (int, long)):
877                         return res[ids]
878                 return res
879
880         def unlink(self, cr, uid, ids, context=None):
881                 if not context:
882                         context={}
883                 if not ids:
884                         return True
885                 if isinstance(ids, (int, long)):
886                         ids = [ids]
887                 delta= context.get('read_delta',False)
888                 if delta and self._log_access:
889                         cr.execute("select  (now()  - min(write_date)) <= '%s'::interval  from %s where id in (%s)"% (delta,self._table,",".join(map(str, ids))) )
890                         res= cr.fetchone()
891                         if res and res[0]:
892                                 raise except_orm('ConcurrencyException', 'This record was modified in the meanwhile')
893
894                 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink')
895
896                 wf_service = netsvc.LocalService("workflow")
897                 for id in ids:
898                         wf_service.trg_delete(uid, self._name, id, cr)
899                 str_d = string.join(('%d',)*len(ids),',')
900
901                 #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
902                 #res = cr.dictfetchall()
903                 #for key in self._inherits:
904                 #       ids2 = [x[self._inherits[key]] for x in res]
905                 #       self.pool.get(key).unlink(cr, uid, ids2)
906
907                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name)
908                 if d1:
909                         d1 = ' AND '+d1
910                         cr.execute('SELECT id FROM '+self._table+' WHERE id IN ('+str_d+')'+d1, ids+d2)
911                         if not cr.rowcount == len({}.fromkeys(ids)):
912                                 raise except_orm('AccessError', 'You try to bypass an access rule (Document type: %s).'%self._description)
913
914                 cr.execute('delete from inherit where (obj_type=%s and obj_id in ('+str_d+')) or (inst_type=%s and inst_id in ('+str_d+'))', (self._name,)+tuple(ids)+(self._name,)+tuple(ids))
915                 cr.execute('delete from '+self._table+' where id in ('+str_d+')'+d1, ids+d2)
916                 return True
917
918         #
919         # TODO: Validate
920         #
921         def write(self, cr, user, ids, vals, context=None):
922                 if not context:
923                         context={}
924                 if not ids:
925                         return True
926                 if isinstance(ids, (int, long)):
927                         ids = [ids]
928                 delta= context.get('read_delta',False)
929                 if delta and self._log_access:
930                         cr.execute("select  (now()  - min(write_date)) <= '%s'::interval  from %s where id in (%s)"% (delta,self._table,",".join(map(str, ids))) )
931                         res= cr.fetchone()
932                         if res and res[0]:
933                                 for field in vals:
934                                         if field in self._columns and self._columns[field]._classic_write:
935                                                 raise except_orm('ConcurrencyException', 'This record was modified in the meanwhile')
936
937                 self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
938
939                 #for v in self._inherits.values():
940                 #       assert v not in vals, (v, vals)
941                 ids_str = string.join(map(str, ids),',')
942                 upd0=[]
943                 upd1=[]
944                 upd_todo=[]
945                 updend=[]
946                 direct = []
947                 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
948                 for field in vals:
949                         if field in self._columns:
950                                 if self._columns[field]._classic_write:
951                                         if (not totranslate) or not self._columns[field].translate:
952                                                 upd0.append(field+'='+self._columns[field]._symbol_set[0])
953                                                 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
954                                         direct.append(field)
955                                 else:
956                                         upd_todo.append(field)
957                         else:
958                                 updend.append(field)
959                 if self._log_access:
960                         upd0.append('write_uid=%d')
961                         upd0.append('write_date=now()')
962                         upd1.append(user)
963
964                 if len(upd0):
965
966                         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
967                         if d1:
968                                 d1 = ' and '+d1
969                                 cr.execute('SELECT id FROM '+self._table+' WHERE id IN ('+ids_str+')'+d1, d2)
970                                 if not cr.rowcount == len({}.fromkeys(ids)):
971                                         raise except_orm('AccessError', 'You try to bypass an access rule (Document type: %s).'%self._description)
972                         else:
973                                 cr.execute('SELECT id FROM '+self._table+' WHERE id IN ('+ids_str+')')
974                                 if not cr.rowcount == len({}.fromkeys(ids)):
975                                         raise except_orm('AccessError', 'You try to write on an record that doesn\'t exist (Document type: %s).'%self._description)
976                         cr.execute('update '+self._table+' set '+string.join(upd0,',')+' where id in ('+ids_str+')'+d1, upd1+ d2)
977
978                         if totranslate:
979                                 for f in direct:
980                                         if self._columns[f].translate:
981                                                 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model',  context['lang'], ids,vals[f])
982
983                 # call the 'set' method of fields which are not classic_write
984                 upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
985                 for field in upd_todo:
986                         for id in ids:
987                                 self._columns[field].set(cr, self, id, field, vals[field], user, context=context)
988
989                 for table in self._inherits:
990                         col = self._inherits[table]
991                         cr.execute('select distinct '+col+' from '+self._table+' where id in ('+ids_str+')', upd1)
992                         nids = [x[0] for x in cr.fetchall()]
993
994                         v = {}
995                         for val in updend:
996                                 if self._inherit_fields[val][0]==table:
997                                         v[val]=vals[val]
998                         self.pool.get(table).write(cr, user, nids, v, context)
999
1000                 self._validate(cr, user, ids)
1001
1002                 if context.has_key('read_delta'):
1003                         del context['read_delta']
1004                 
1005                 wf_service = netsvc.LocalService("workflow")
1006                 for id in ids:
1007                         wf_service.trg_write(user, self._name, id, cr)
1008                 self._update_function_stored(cr, user, ids, context=context)
1009                 return True
1010
1011         #
1012         # TODO: Should set perm to user.xxx
1013         #
1014         def create(self, cr, user, vals, context=None):
1015                 """ create(cr, user, vals, context) -> int
1016                 cr = database cursor
1017                 user = user id
1018                 vals = dictionary of the form {'field_name':field_value, ...}
1019                 """
1020                 if not context:
1021                         context={}
1022                 self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
1023
1024                 default = []
1025
1026                 avoid_table = []
1027                 for (t,c) in self._inherits.items():
1028                         if c in vals:
1029                                 avoid_table.append(t)
1030                 for f in self._columns.keys(): # + self._inherit_fields.keys():
1031                         if not f in vals:
1032                                 default.append(f)
1033                 for f in self._inherit_fields.keys():
1034                         if (not f in vals) and (not self._inherit_fields[f][0] in avoid_table):
1035                                 default.append(f)
1036
1037                 if len(default):
1038                         vals.update(self.default_get(cr, user, default, context))
1039
1040                 tocreate = {}
1041                 for v in self._inherits:
1042                         if self._inherits[v] not in vals:
1043                                 tocreate[v] = {}
1044
1045                 #cr.execute('select perm_id from res_users where id=%d', (user,))
1046                 perm = False #cr.fetchone()[0]
1047                 (upd0, upd1, upd2) = ('', '', [])
1048                 upd_todo = []
1049
1050                 for v in vals.keys():
1051                         if v in self._inherit_fields:
1052                                 (table,col,col_detail) = self._inherit_fields[v]
1053                                 tocreate[table][v] = vals[v]
1054                                 del vals[v]
1055
1056                 cr.execute("select nextval('"+self._sequence+"')")
1057                 id_new = cr.fetchone()[0]
1058                 for table in tocreate:
1059                         id = self.pool.get(table).create(cr, user, tocreate[table])
1060                         upd0 += ','+self._inherits[table]
1061                         upd1 += ',%d'
1062                         upd2.append(id)
1063                         cr.execute('insert into inherit (obj_type,obj_id,inst_type,inst_id) values (%s,%d,%s,%d)', (table,id,self._name,id_new))
1064
1065                 for field in vals:
1066                         if self._columns[field]._classic_write:
1067                                 upd0=upd0+','+field
1068                                 upd1=upd1+','+self._columns[field]._symbol_set[0]
1069                                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
1070                         else:
1071                                 upd_todo.append(field)
1072                 if self._log_access:
1073                         upd0 += ',create_uid,create_date'
1074                         upd1 += ',%d,now()'
1075                         upd2.append(user)
1076                 cr.execute('insert into '+self._table+' (id,perm_id'+upd0+") values ("+str(id_new)+',NULL'+upd1+')', tuple(upd2))
1077                 upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
1078                 for field in upd_todo:
1079                         self._columns[field].set(cr, self, id_new, field, vals[field], user, context)
1080
1081                 self._validate(cr, user, [id_new])
1082
1083                 wf_service = netsvc.LocalService("workflow")
1084                 wf_service.trg_create(user, self._name, id_new, cr)
1085                 self._update_function_stored(cr, user, [id_new], context=context)
1086                 return id_new
1087
1088         def _update_function_stored(self, cr, user, ids, context=None):
1089                 if not context:
1090                         context={}
1091                 f=filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns)
1092                 if f:
1093                         result=self.read(cr, user, ids, fields=f, context=context)
1094                         for res in result:
1095                                 upd0=[]
1096                                 upd1=[]
1097                                 for field in res:
1098                                         if field not in f:
1099                                                 continue
1100                                         upd0.append(field+'='+self._columns[field]._symbol_set[0])
1101                                         upd1.append(self._columns[field]._symbol_set[1](res[field]))
1102                                 upd1.append(res['id'])
1103                                 cr.execute('update '+self._table+' set '+string.join(upd0,',')+ ' where id = %d', upd1)
1104                 return True
1105
1106         #
1107         # TODO: Validate
1108         #
1109         def perm_write(self, cr, user, ids, fields, context=None):
1110                 if not context:
1111                         context={}
1112                 if not ids:
1113                         return True
1114                 if isinstance(ids, (int, long)):
1115                         ids = [ids]
1116                 query = []
1117                 vals = []
1118                 keys = fields.keys()
1119                 for x in keys:
1120                         query.append(x+'=%d')
1121                         vals.append(fields[x])
1122                 cr.execute('select id from perm where ' + ' and '.join(query) + ' limit 1', vals)
1123                 res = cr.fetchone()
1124                 if res:
1125                         id = res[0]
1126                 else:
1127                         cr.execute("select nextval('perm_id_seq')")
1128                         id = cr.fetchone()[0]
1129                         cr.execute('insert into perm (id,' + ','.join(keys) + ') values (' + str(id) + ',' + ('%d,'*(len(keys)-1)+'%d')+')', vals)
1130                 ids_str = ','.join(map(str, ids))
1131                 cr.execute('update '+self._table+' set perm_id=%d where id in ('+ids_str+')', (id,))
1132                 return True
1133
1134         # returns the definition of each field in the object
1135         # the optional fields parameter can limit the result to some fields
1136         def fields_get(self, cr, user, fields=None, context=None):
1137                 if not context:
1138                         context={}
1139                 res = {}
1140                 for parent in self._inherits:
1141                         res.update(self.pool.get(parent).fields_get(cr, user, fields, context))
1142                 read_access= self.pool.get('ir.model.access').check(cr, user, self._name,'write',raise_exception=False)
1143                 for f in self._columns.keys():
1144                         res[f] = {'type': self._columns[f]._type}
1145                         for arg in ('string','readonly','states','size','required','change_default','translate', 'help', 'select'):
1146                                 if getattr(self._columns[f], arg):
1147                                         res[f][arg] = getattr(self._columns[f], arg)
1148                         if not read_access:
1149                                 res[f]['readonly']= True
1150                                 res[f]['states']={}
1151                         for arg in ('digits', 'invisible'):
1152                                 if hasattr(self._columns[f], arg) and getattr(self._columns[f], arg):
1153                                         res[f][arg] = getattr(self._columns[f], arg)
1154
1155                         # translate the field label
1156                         if context.get('lang', False) and context['lang'] != 'en_US':
1157                                 res_trans = self.pool.get('ir.translation')._get_source(cr, user, self._name+','+f, 'field', context['lang'])
1158                                 if res_trans:
1159                                         res[f]['string'] = res_trans
1160
1161                         if hasattr(self._columns[f], 'selection'):
1162                                 if isinstance(self._columns[f].selection, (tuple, list)):
1163                                         sel = self._columns[f].selection
1164                                         
1165                                         # translate each selection option
1166                                         if context.get('lang', False) and context['lang'] != 'en_US':
1167                                                 sel2 = []
1168                                                 for (key,val) in sel:
1169                                                         val2 = self.pool.get('ir.translation')._get_source(cr, user, self._name+','+f, 'selection', context['lang'], val)
1170                                                         sel2.append((key, val2 or val))
1171                                                 sel = sel2
1172                                         res[f]['selection'] = sel
1173                                 else:
1174                                         # call the 'dynamic selection' function
1175                                         res[f]['selection'] = self._columns[f].selection(self, cr, user, context)
1176                         if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1177                                 res[f]['relation'] = self._columns[f]._obj
1178                                 res[f]['domain'] = self._columns[f]._domain
1179                                 res[f]['context'] = self._columns[f]._context
1180
1181                 if fields:
1182                         # filter out fields which aren't in the fields list
1183                         for r in res.keys():
1184                                 if r not in fields:
1185                                         del res[r]
1186                 return res
1187
1188         #
1189         # Overload this method if you need a window title which depends on the context
1190         #
1191         def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1192                 return False
1193
1194         def __view_look_dom(self, cr, user, node, context=None):
1195                 if not context:
1196                         context={}
1197                 result = False
1198                 fields = {}
1199                 childs = True
1200                 if node.nodeType==node.ELEMENT_NODE and node.localName=='field':
1201                         if node.hasAttribute('name'):
1202                                 attrs = {}
1203                                 try:
1204                                         if node.getAttribute('name') in self._columns:
1205                                                 relation = self._columns[node.getAttribute('name')]._obj
1206                                         else:
1207                                                 relation = self._inherit_fields[node.getAttribute('name')][2]._obj
1208                                 except:
1209                                         relation = False
1210                                 if relation:
1211                                         childs = False
1212                                         views = {}
1213                                         for f in node.childNodes:
1214                                                 if f.nodeType==f.ELEMENT_NODE and f.localName in ('form','tree'):
1215                                                         node.removeChild(f)
1216                                                         xarch,xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, context)
1217                                                         views[str(f.localName)] = {
1218                                                                 'arch': xarch,
1219                                                                 'fields': xfields
1220                                                         }
1221                                         attrs = {'views': views}
1222                                 fields[node.getAttribute('name')] = attrs
1223
1224                 elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form','tree'):
1225                         result = self.view_header_get(cr, user, False, node.localName, context)
1226                         if result:
1227                                 node.setAttribute('string', result)
1228
1229                 if node.nodeType == node.ELEMENT_NODE:
1230                         # translate view
1231                         if ('lang' in context) and not result:
1232                                 if node.hasAttribute('string') and node.getAttribute('string'):
1233                                         trans = tools.translate(cr, user, self._name, 'view', context['lang'], node.getAttribute('string').encode('utf8'))
1234                                         if trans:
1235                                                 node.setAttribute('string', trans.decode('utf8'))
1236                                 if node.hasAttribute('sum') and node.getAttribute('sum'):
1237                                         trans = tools.translate(cr, user, self._name, 'view', context['lang'], node.getAttribute('sum').encode('utf8'))
1238                                         if trans:
1239                                                 node.setAttribute('sum', trans.decode('utf8'))
1240                         #
1241                         # Add view for properties !
1242                         #
1243                         if node.localName=='properties':
1244                                 parent = node.parentNode
1245                                 doc = node.ownerDocument
1246                                 models = map(lambda x: "'"+x+"'", [self._name] + self._inherits.keys())
1247                                 cr.execute('select id,name,group_name from ir_model_fields where model in ('+','.join(models)+') and view_load order by group_name, id')
1248                                 oldgroup = None
1249                                 res = cr.fetchall()
1250                                 for id, fname, gname in res:
1251                                         if oldgroup != gname:
1252                                                 child = doc.createElement('separator')
1253                                                 child.setAttribute('string', gname)
1254                                                 child.setAttribute('colspan', "4")
1255                                                 oldgroup = gname
1256                                                 parent.insertBefore(child, node)
1257
1258                                         child = doc.createElement('field')
1259                                         child.setAttribute('name', fname)
1260                                         parent.insertBefore(child, node)
1261                                 parent.removeChild(node)
1262
1263                 if childs:
1264                         for f in node.childNodes:
1265                                 fields.update(self.__view_look_dom(cr, user, f,context))
1266                 return fields
1267
1268         def __view_look_dom_arch(self, cr, user, node, context=None):
1269                 if not context:
1270                         context={}
1271                 fields_def = self.__view_look_dom(cr, user, node, context=context)
1272                 arch = node.toxml()
1273                 fields = self.fields_get(cr, user, fields_def.keys(), context)
1274                 for field in fields_def:
1275                         fields[field].update(fields_def[field])
1276                 return arch,fields
1277
1278
1279         #
1280         # if view_id, view_type is not required
1281         #
1282         def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False):
1283                 if not context:
1284                         context={}
1285                 def _inherit_apply(src, inherit):
1286                         def _find(node, node2):
1287                                 if node.nodeType==node.ELEMENT_NODE and node.localName==node2.localName:
1288                                         res = True
1289                                         for attr in node2.attributes.keys():
1290                                                 if attr=='position':
1291                                                         continue
1292                                                 if node.hasAttribute(attr):
1293                                                         if node.getAttribute(attr)==node2.getAttribute(attr):
1294                                                                 continue
1295                                                 res = False
1296                                         if res:
1297                                                 return node
1298                                 for child in node.childNodes:
1299                                         res = _find(child, node2)
1300                                         if res: return res
1301                                 return None
1302
1303                         doc_src = dom.minidom.parseString(src)
1304                         doc_dest = dom.minidom.parseString(inherit)
1305                         for node2 in doc_dest.childNodes:
1306                                 if not node2.nodeType==node2.ELEMENT_NODE:
1307                                         continue
1308                                 node = _find(doc_src, node2)
1309                                 if node:
1310                                         pos = 'inside'
1311                                         if node2.hasAttribute('position'):
1312                                                 pos = node2.getAttribute('position')
1313                                         if pos=='replace':
1314                                                 parent = node.parentNode
1315                                                 for child in node2.childNodes:
1316                                                         if child.nodeType==child.ELEMENT_NODE:
1317                                                                 parent.insertBefore(child, node)
1318                                                 parent.removeChild(node)
1319                                         else:
1320                                                 for child in node2.childNodes:
1321                                                         if child.nodeType==child.ELEMENT_NODE:
1322                                                                 if pos=='inside':
1323                                                                         node.appendChild(child)
1324                                                                 elif pos=='after':
1325                                                                         sib = node.nextSibling
1326                                                                         if sib:
1327                                                                                 node.parentNode.insertBefore(child, sib)
1328                                                                         else:
1329                                                                                 node.parentNode.appendChild(child)
1330                                                                 elif pos=='before':
1331                                                                         node.parentNode.insertBefore(child, node)
1332                                                                 else:
1333                                                                         raise AttributeError, 'Unknown position in inherited view %s !' % pos
1334                                 else:
1335                                         attrs = ''.join([
1336                                                 ' %s="%s"' % (attr, node2.getAttribute(attr)) 
1337                                                 for attr in node2.attributes.keys() 
1338                                                 if attr != 'position'
1339                                         ])
1340                                         tag = "<%s%s>" % (node2.localName, attrs)
1341                                         raise AttributeError, "Couldn't find tag '%s' in parent view !" % tag
1342                         return doc_src.toxml()
1343
1344                 result = {'type':view_type, 'model':self._name}
1345
1346                 ok = True
1347                 model = True
1348                 while ok:
1349                         if view_id:
1350                                 where = (model and (" and model='%s'" % (self._name,))) or ''
1351                                 cr.execute('select arch,name,field_parent,id,type,inherit_id from ir_ui_view where id=%d'+where, (view_id,))
1352                         else:
1353                                 cr.execute('select arch,name,field_parent,id,type,inherit_id from ir_ui_view where model=%s and type=%s order by priority', (self._name,view_type))
1354                         sql_res = cr.fetchone()
1355                         if not sql_res:
1356                                 break
1357                         ok = sql_res[5]
1358                         view_id = ok or sql_res[3]
1359                         model = False
1360
1361                 # if a view was found
1362                 if sql_res:
1363                         result['type'] = sql_res[4]
1364                         result['view_id'] = sql_res[3]
1365                         result['arch'] = sql_res[0]
1366
1367                         def _inherit_apply_rec(result, inherit_id):
1368                                 # get all views which inherit from (ie modify) this view
1369                                 cr.execute('select arch,id from ir_ui_view where inherit_id=%d and model=%s order by priority', (inherit_id, self._name))
1370                                 sql_inherit = cr.fetchall()
1371                                 for (inherit,id) in sql_inherit:
1372                                         result = _inherit_apply(result, inherit)
1373                                         result = _inherit_apply_rec(result,id)
1374                                 return result
1375
1376                         result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
1377
1378                         result['name'] = sql_res[1]
1379                         result['field_parent'] = sql_res[2] or False
1380                 else:
1381                         # otherwise, build some kind of default view
1382                         if view_type == 'form':
1383                                 res = self.fields_get(cr, user, context=context)
1384                                 xml = '''<?xml version="1.0"?>\n<form string="%s">\n''' % (self._description,)
1385                                 for x in res:
1386                                         if res[x]['type'] not in ('one2many', 'many2many'):
1387                                                 xml += '\t<field name="%s"/>\n' % (x,)
1388                                                 if res[x]['type'] == 'text':
1389                                                         xml += "<newline/>"
1390                                 xml += "</form>"
1391                         elif view_type == 'tree':
1392                                 xml = '''<?xml version="1.0"?>\n<tree string="%s">\n\t<field name="%s"/>\n</tree>''' % (self._description,self._rec_name)
1393                         else:
1394                                 xml = ''
1395                         result['arch'] = xml
1396                         result['name'] = 'default'
1397                         result['field_parent'] = False
1398                         result['view_id'] = 0
1399
1400                 doc = dom.minidom.parseString(result['arch'])
1401                 xarch, xfields = self.__view_look_dom_arch(cr, user, doc, context=context)
1402                 result['arch'] = xarch
1403                 result['fields'] = xfields
1404                 if toolbar:
1405                         def clean(x):
1406                                 x = x[2]
1407                                 for key in ('report_sxw_content','report_rml_content','report_sxw','report_rml'):
1408                                         if key in x:
1409                                                 del x[key]
1410                                 return x
1411                         resprint = self.pool.get('ir.values').get(cr, user, 'action', 'client_print_multi', [(self._name, False)], False, context)
1412                         resaction = self.pool.get('ir.values').get(cr, user, 'action', 'client_action_multi', [(self._name, False)], False, context)
1413                         resrelate = self.pool.get('ir.values').get(cr, user, 'action', 'client_action_relate', [(self._name, False)], False, context)
1414                         resprint = map(clean, resprint)
1415                         resaction = map(clean, resaction)
1416                         resaction = filter(lambda x: not x.get('multi',False), resaction)
1417                         resprint = filter(lambda x: not x.get('multi',False), resprint)
1418                         resrelate = map(lambda x:x[2], resrelate)
1419                         for x in resprint+resaction+resrelate:
1420                                 x['string'] = x['name']
1421                         result['toolbar'] = {
1422                                 'print': resprint,
1423                                 'action': resaction,
1424                                 'relate': resrelate
1425                         }
1426                 return result
1427
1428         # TODO: ameliorer avec NULL
1429         def _where_calc(self, cr, user, args, active_test=True, context=None):
1430                 if not context:
1431                         context={}
1432                 args = args[:]
1433                 # if the object has a field named 'active', filter out all inactive
1434                 # records unless they were explicitely asked for
1435                 if 'active' in self._columns and active_test:
1436                         i = 0
1437                         active_found = False
1438                         while i<len(args):
1439                                 if args[i][0]=='active':
1440                                         if not args[i][2]:
1441                                                 active_found = True
1442                                 i += 1
1443                         if not active_found:
1444                                 args.append(('active', '=', 1))
1445
1446                 i = 0
1447                 tables=[self._table]
1448                 joins=[]
1449                 while i<len(args):
1450                         table=self
1451                         if args[i][0] in self._inherit_fields:
1452                                 table=self.pool.get(self._inherit_fields[args[i][0]][0])
1453                                 if (table._table not in tables):
1454                                         tables.append(table._table)
1455                                         joins.append(('id', 'join', '%s.%s' % (self._table, self._inherits[table._name]), table))
1456                         fargs = args[i][0].split('.', 1)
1457                         field = table._columns.get(fargs[0],False)
1458                         if not field:
1459                                 i+=1
1460                                 continue
1461                         if len(fargs) > 1:
1462                                 if field._type == 'many2one':
1463                                         args[i] = (fargs[0], 'in', self.pool.get(field._obj).search(cr, user, [(fargs[1], args[i][1], args[i][2])], context=context))
1464                                         i+=1
1465                                         continue
1466                                 else:
1467                                         i+=1
1468                                         continue
1469                         if field._properties:
1470                                 arg = [args.pop(i)]
1471                                 j = i
1472                                 while j<len(args):
1473                                         if args[j][0]==arg[0][0]:
1474                                                 arg.append(args.pop(j))
1475                                         else:
1476                                                 j+=1
1477                                 if field._fnct_search:
1478                                         args.extend(field.search(cr, user, self, arg[0][0], arg))
1479
1480                         elif field._type=='one2many':
1481                                 field_obj = self.pool.get(field._obj)
1482
1483                                 # get the ids of the records of the "distant" resource
1484                                 ids2 = [x[0] for x in field_obj.name_search(cr, user, args[i][2], [], args[i][1])]
1485                                 if not ids2:
1486                                         args[i] = ('id','=','0')
1487                                 else:
1488                                         cr.execute('select '+field._fields_id+' from '+field_obj._table+' where id in ('+','.join(map(str,ids2))+')')
1489                                         ids3 = [x[0] for x in cr.fetchall()]
1490
1491                                         args[i] = ('id', 'in', ids3)
1492                                 i+=1
1493
1494                         elif field._type=='many2many':
1495                                 #FIXME
1496                                 if args[i][1]=='child_of':
1497                                         if isinstance(args[i][2], basestring):
1498                                                 ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
1499                                         else:
1500                                                 ids2 = args[i][2]
1501                                         def _rec_get(ids):
1502                                                 if not len(ids): return []
1503                                                 cr.execute('select '+field._id1+' from '+field._rel+' where '+field._id2+' in ('+','.join(map(str,ids))+')')
1504                                                 ids = [x[0] for x in cr.fetchall()]
1505                                                 return ids + _rec_get(ids)
1506                                         args[i] = ('id','in',ids2+_rec_get(ids2))
1507                                 else:
1508                                         if isinstance(args[i][2], basestring):
1509                                                 res_ids = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])]
1510                                         else:
1511                                                 res_ids = args[i][2]
1512                                         if not len(res_ids):
1513                                                 return []
1514                                         cr.execute('select '+field._id1+' from '+field._rel+' where '+field._id2+' in ('+','.join(map(str, res_ids))+')')
1515                                         args[i] = ('id', 'in', map(lambda x: x[0], cr.fetchall()))
1516                                 i+=1
1517
1518                         elif field._type=='many2one':
1519                                 if args[i][1]=='child_of':
1520                                         if isinstance(args[i][2], basestring):
1521                                                 ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
1522                                         else:
1523                                                 ids2 = args[i][2]
1524                                         def _rec_get(ids, table, parent):
1525                                                 if not ids:
1526                                                         return []
1527                                                 if 'active' in table._columns:
1528                                                         ids2 = table.search(cr, user, [(parent,'in',ids),('active','in', [True, False])])
1529                                                 else:
1530                                                         ids2 = table.search(cr, user, [(parent, 'in', ids)])
1531                                                 return ids + _rec_get(ids2, table, parent)
1532                                         if field._obj <> table._name:
1533                                                 args[i] = (args[i][0],'in',ids2+_rec_get(ids2, self.pool.get(field._obj), table._parent_name), table)
1534                                         else:
1535                                                 args[i] = ('id','in',ids2+_rec_get(ids2, table, args[i][0]), table)
1536                                 else:
1537                                         if isinstance(args[i][2], basestring):
1538                                                 res_ids = self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])
1539                                                 args[i] = (args[i][0],'in',map(lambda x: x[0], res_ids), table)
1540                                         else:
1541                                                 args[i] += (table,)
1542                                 i+=1
1543                         else:
1544                                 if field.translate and context.get('lang', False) and context['lang'] != 'en_US':
1545                                         if args[i][1] in ('like', 'ilike'):
1546                                                 args[i] = (args[i][0], args[i][1], '%%%s%%' % args[i][2])
1547                                         cr.execute('select res_id from ir_translation where name = %s and lang = %s and type = %s and value '+args[i][1]+' %s', (table._name+','+args[i][0], context['lang'], 'model', args[i][2]))
1548                                         ids = map(lambda x: x[0], cr.fetchall())
1549                                         cr.execute('select id from '+table._table+' where '+args[i][0]+' '+args[i][1]+' %s', (args[i][2],))
1550                                         ids += map(lambda x: x[0], cr.fetchall())
1551                                         args[i] = ('id', 'in', ids, table)
1552                                 else:
1553                                         args[i] += (table,)
1554                                 i+=1
1555                 args.extend(joins)
1556
1557                 qu1, qu2 = [], []
1558                 for x in args:
1559                         table=self
1560                         if len(x) > 3:
1561                                 table=x[3]
1562                         if x[1] != 'in':
1563 #FIXME: this replace all (..., '=', False) values with 'is null' and this is 
1564 # not what we want for real boolean fields. The problem is, we can't change it
1565 # easily because we use False everywhere instead of None
1566 # NOTE FAB: we can't use None because it is not accepted by XML-RPC, that's why
1567 # boolean (0-1), None -> False
1568 # Ged> boolean fields are not always = 0 or 1
1569                                 if (x[2] is False) and (x[1]=='='):
1570                                         qu1.append(x[0]+' is null')
1571                                 elif (x[2] is False) and (x[1]=='<>' or x[1]=='!='):
1572                                         qu1.append(x[0]+' is not null')
1573                                 else:
1574                                         if x[0]=='id':
1575                                                 if x[1]=='join':
1576                                                         qu1.append('(%s.%s = %s)' % (table._table, x[0], x[2]))
1577                                                 else:
1578                                                         qu1.append('(%s.%s %s %%s)' % (table._table, x[0], x[1]))
1579                                                         qu2.append(x[2])
1580                                         else:
1581                                                 add_null = False
1582                                                 if x[1] in ('like', 'ilike'):
1583                                                         if isinstance(x[2], str):
1584                                                                 str_utf8 = x[2]
1585                                                         elif isinstance(x[2], unicode):
1586                                                                 str_utf8 = x[2].encode('utf-8')
1587                                                         else:
1588                                                                 str_utf8 = str(x[2])
1589                                                         qu2.append('%%%s%%' % str_utf8)
1590                                                         if not str_utf8:
1591                                                                 add_null = True
1592                                                 else:
1593                                                         if x[0] in table._columns:
1594                                                                 qu2.append(table._columns[x[0]]._symbol_set[1](x[2]))
1595                                                 if x[1]=='=like':
1596                                                         x1 = 'like'
1597                                                 else:
1598                                                         x1 = x[1]
1599                                                 if x[0] in table._columns:
1600                                                         qu1.append('(%s.%s %s %s)' % (table._table, x[0], x1, table._columns[x[0]]._symbol_set[0]))
1601                                                 else:
1602                                                         qu1.append('(%s.%s %s \'%s\')' % (table._table, x[0], x1, x[2]))
1603
1604                                                 if add_null:
1605                                                         qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
1606                         elif x[1]=='in':
1607                                 if len(x[2])>0:
1608                                         todel = []
1609                                         for xitem in range(len(x[2])):
1610                                                 if x[2][xitem]==False and isinstance(x[2][xitem],bool):
1611                                                         todel.append(xitem)
1612                                         for xitem in todel[::-1]:
1613                                                 del x[2][xitem]
1614                                         if x[0]=='id':
1615                                                 qu1.append('(%s.id in (%s))' % (table._table, ','.join(['%d'] * len(x[2])),))
1616                                         else:
1617                                                 qu1.append('(%s.%s in (%s))' % (table._table, x[0], ','.join([table._columns[x[0]]._symbol_set[0]]*len(x[2]))))
1618                                         if todel:
1619                                                 qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
1620                                         qu2+=x[2]
1621                                 else:
1622                                         qu1.append(' (1=0)')
1623                 return (qu1,qu2,tables)
1624
1625         def search_count(self, cr, user, args, context=None):
1626                 if not context:
1627                         context={}
1628                 # compute the count of records
1629                 (qu1,qu2,tables) = self._where_calc(cr, user, args, context=context)
1630
1631                 if len(qu1):
1632                         qu1 = ' where '+string.join(qu1,' and ')
1633                 else:
1634                         qu1 = ''
1635
1636                 # construct a clause for the rules :
1637                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1638                 if d1:
1639                         qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
1640                         qu2 += d2
1641                 
1642                 # execute the "main" query to fetch the count of ids
1643                 cr.execute('select count(%s.id) from ' % self._table + ','.join(tables) +qu1, qu2)
1644                 res = cr.fetchall()
1645                 return res[0][0]
1646
1647         def search(self, cr, user, args, offset=0, limit=None, order=None, context=None):
1648                 if not context:
1649                         context={}
1650                 # compute the where, order by, limit and offset clauses
1651                 (qu1,qu2,tables) = self._where_calc(cr, user, args, context=context)
1652
1653                 if len(qu1):
1654                         qu1 = ' where '+string.join(qu1,' and ')
1655                 else:
1656                         qu1 = ''
1657                 order_by = order or self._order
1658
1659                 limit_str = limit and ' limit %d' % limit or ''
1660                 offset_str = offset and ' offset %d' % offset or ''
1661
1662
1663                 # construct a clause for the rules :
1664                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1665                 if d1:
1666                         qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
1667                         qu2 += d2
1668                 # execute the "main" query to fetch the ids we were searching for
1669                 cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
1670                 res = cr.fetchall()
1671                 return [x[0] for x in res]
1672
1673         # returns the different values ever entered for one field
1674         # this is used, for example, in the client when the user hits enter on 
1675         # a char field
1676         def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
1677                 if not args:
1678                         args=[]
1679                 if field in self._inherit_fields:
1680                         return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid,field,value,args,offset,limit)
1681                 else:
1682                         return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
1683
1684         def name_get(self, cr, user, ids, context=None):
1685                 if not context:
1686                         context={}
1687                 if not ids:
1688                         return []
1689                 if isinstance(ids, (int, long)):
1690                         ids = [ids]
1691                 return [(r['id'], r[self._rec_name]) for r in self.read(cr, user, ids, [self._rec_name], context, load='_classic_write')]
1692
1693         def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
1694                 if not args:
1695                         args=[]
1696                 if not context:
1697                         context={}
1698                 args=args[:]
1699                 if name:
1700                         args += [(self._rec_name,operator,name)]
1701                 ids = self.search(cr, user, args, limit=limit, context=context)
1702                 res = self.name_get(cr, user, ids, context)
1703                 return res
1704
1705         def copy(self, cr, uid, id, default=None, context=None):
1706                 if not context:
1707                         context={}
1708                 if not default:
1709                         default = {}
1710                 if 'state' not in default:
1711                         if 'state' in self._defaults:
1712                                 default['state'] = self._defaults['state'](self, cr, uid, context)
1713                 data = self.read(cr, uid, [id], context=context)[0]
1714                 fields = self.fields_get(cr, uid)
1715                 for f in fields:
1716                         ftype = fields[f]['type']
1717
1718                         if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
1719                                 del data[f]
1720
1721                         if f in default:
1722                                 data[f] = default[f]
1723                         elif ftype == 'function':
1724                                 del data[f]
1725                         elif ftype == 'many2one':
1726                                 try:
1727                                         data[f] = data[f] and data[f][0]
1728                                 except:
1729                                         pass
1730                         elif ftype in ('one2many', 'one2one'):
1731                                 res = []
1732                                 rel = self.pool.get(fields[f]['relation'])
1733                                 for rel_id in data[f]:
1734                                         # the lines are first duplicated using the wrong (old) 
1735                                         # parent but then are reassigned to the correct one thanks
1736                                         # to the (4, ...)
1737                                         res.append((4, rel.copy(cr, uid, rel_id, context=context)))
1738                                 data[f] = res
1739                         elif ftype == 'many2many':
1740                                 data[f] = [(6, 0, data[f])]
1741                 del data['id']
1742                 for v in self._inherits:
1743                         del data[self._inherits[v]]
1744                 return self.create(cr, uid, data)
1745
1746         def read_string(self, cr, uid, id, langs, fields=None, context=None):
1747                 if not context:
1748                         context={}
1749                 res = {}
1750                 res2 = {}
1751                 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read')
1752                 if not fields:
1753                         fields = self._columns.keys() + self._inherit_fields.keys()
1754                 for lang in langs:
1755                         res[lang] = {'code': lang}
1756                         for f in fields:
1757                                 if f in self._columns:
1758                                         res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1759                                         if res_trans:
1760                                                 res[lang][f]=res_trans
1761                                         else:
1762                                                 res[lang][f]=self._columns[f].string
1763                 for table in self._inherits:
1764                         cols = intersect(self._inherit_fields.keys(), fields)
1765                         res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1766                 for lang in res2:
1767                         if lang in res:
1768                                 res[lang]={'code': lang}
1769                         for f in res2[lang]:
1770                                 res[lang][f]=res2[lang][f]
1771                 return res
1772
1773         def write_string(self, cr, uid, id, langs, vals, context=None):
1774                 if not context:
1775                         context={}
1776                 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write')
1777                 for lang in langs:
1778                         for field in vals:
1779                                 if field in self._columns:
1780                                         self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field])
1781                 for table in self._inherits:
1782                         cols = intersect(self._inherit_fields.keys(), vals)
1783                         if cols:
1784                                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1785                 return True
1786
1787 # vim:noexpandtab:ts=4