Speed Improvement on Every Operation:
[odoo/odoo.git] / bin / osv / orm.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2004-2008 Tiny SPRL (http://tiny.be) All Rights Reserved.
5 #
6 # $Id$
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 # Object relationnal mapping to postgresql module
32 #    . Hierarchical structure
33 #    . Constraints consistency, validations
34 #    . Object meta Data depends on its status
35 #    . Optimised processing by complex query (multiple actions at once)
36 #    . Default fields value
37 #    . Permissions optimisation
38 #    . Persistant object: DB postgresql
39 #    . Datas conversions
40 #    . Multi-level caching system
41 #    . 2 different inheritancies
42 #    . Fields:
43 #         - classicals (varchar, integer, boolean, ...)
44 #         - relations (one2many, many2one, many2many)
45 #         - functions
46 #
47 #
48
49 import time
50 import types
51 from xml import dom, xpath
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 from tools.config import config
64
65 prof = 0
66 ID_MAX = 1000
67
68 regex_order = re.compile('^([a-zA-Z0-9_]+( desc)?( asc)?,?)+$', re.I)
69
70 def intersect(la, lb):
71         return filter(lambda x: x in lb, la)
72
73
74 class except_orm(Exception):
75         def __init__(self, name, value):
76                 self.name = name
77                 self.value = value
78                 self.args = (name, value)
79
80 # Readonly python database object browser
81 class browse_null(object):
82
83         def __init__(self):
84                 self.id=False
85
86         def __getitem__(self, name):
87                 return False
88
89         def __int__(self):
90                 return False
91
92         def __str__(self):
93                 return ''
94
95         def __nonzero__(self):
96                 return False
97
98 #
99 # TODO: execute an object method on browse_record_list
100 #
101 class browse_record_list(list):
102
103         def __init__(self, lst, context=None):
104                 if not context:
105                         context = {}
106                 super(browse_record_list, self).__init__(lst)
107                 self.context = context
108
109
110 class browse_record(object):
111         def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}):
112                 '''
113                 table : the object (inherited from orm)
114                 context : a dictionnary with an optionnal context
115                 '''
116                 if not context:
117                         context = {}
118                 assert id, _('Wrong ID for the browse record, got %s, expected an integer.') % str(id)
119                 self._list_class = list_class or browse_record_list
120                 self._cr = cr
121                 self._uid = uid
122                 self._id = id
123                 self._table = table
124                 self._table_name = self._table._name
125                 self._context = context
126                 self._fields_process = fields_process
127
128                 cache.setdefault(table._name, {})
129                 self._data = cache[table._name]
130                 if not id in self._data:
131                         self._data[id] = {'id':id}
132                 self._cache = cache
133
134         def __getitem__(self, name):
135                 if name == 'id':
136                         return self._id
137                 if not self._data[self._id].has_key(name):
138                         # build the list of fields we will fetch
139
140                         # fetch the definition of the field which was asked for
141                         if name in self._table._columns:
142                                 col = self._table._columns[name]
143                         elif name in self._table._inherit_fields:
144                                 col = self._table._inherit_fields[name][2]
145                         elif hasattr(self._table, name):
146                                 if isinstance( getattr(self._table, name), (types.MethodType,types.LambdaType,types.FunctionType)):
147                                         return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv)
148                                 else:
149                                         return getattr(self._table, name)
150                         else:
151                                 logger = netsvc.Logger()
152                                 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name))
153                                 return False
154
155                         # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
156                         if col._classic_write:
157                                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
158                                 ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
159                                 # gen the list of inherited fields
160                                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
161                                 # complete the field list with the inherited fields which are classic or many2one
162                                 ffields += filter(lambda x: x[1]._classic_write, inherits)
163                         # otherwise we fetch only that field
164                         else:
165                                 ffields = [(name,col)]
166                         ids = filter(lambda id: not self._data[id].has_key(name), self._data.keys())
167                         # read the data
168                         fffields = map(lambda x: x[0], ffields)
169                         datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write")
170                         if self._fields_process:
171                                 for n,f in ffields:
172                                         if f._type in self._fields_process:
173                                                 for d in datas:
174                                                         d[n] = self._fields_process[f._type](d[n])
175                                                         d[n].set_value(d[n], self, f)
176
177
178                         # create browse records for 'remote' objects
179                         for data in datas:
180                                 for n,f in ffields:
181                                         if f._type in ('many2one', 'one2one'):
182                                                 if data[n]:
183                                                         obj = self._table.pool.get(f._obj)
184                                                         compids=False
185                                                         if not f._classic_write:
186                                                                 ids2 = data[n][0]
187                                                         else:
188                                                                 ids2 = data[n]
189                                                         if ids2:
190                                                                 data[n] = browse_record(self._cr, self._uid, ids2, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
191                                                         else:
192                                                                 data[n] = browse_null()
193                                                 else:
194                                                         data[n] = browse_null()
195                                         elif f._type in ('one2many', 'many2many') and len(data[n]):
196                                                 data[n] = self._list_class([browse_record(self._cr,self._uid,id,self._table.pool.get(f._obj),self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
197                                 self._data[data['id']].update(data)
198                 return self._data[self._id][name]
199
200         def __getattr__(self, name):
201 #               raise an AttributeError exception.
202                 return self[name]
203
204         def __contains__(self, name):
205                 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
206
207         def __hasattr__(self, name):
208                 return name in self
209
210         def __int__(self):
211                 return self._id
212
213         def __str__(self):
214                 return "browse_record(%s, %d)" % (self._table_name, self._id)
215
216         def __eq__(self, other):
217                 return (self._table_name, self._id) == (other._table_name, other._id)
218
219         def __ne__(self, other):
220                 return (self._table_name, self._id) != (other._table_name, other._id)
221
222         # we need to define __unicode__ even though we've already defined __str__
223         # because we have overridden __getattr__
224         def __unicode__(self):
225                 return unicode(str(self))
226
227         def __hash__(self):
228                 return hash((self._table_name, self._id))
229
230         __repr__ = __str__
231
232
233 def get_pg_type(f):
234         '''
235         returns a tuple
236         (type returned by postgres when the column was created, type expression to create the column)
237         '''
238
239         type_dict = {
240                         fields.boolean:'bool',
241                         fields.integer:'int4',
242                         fields.text:'text',
243                         fields.date:'date',
244                         fields.time:'time',
245                         fields.datetime:'timestamp',
246                         fields.binary:'bytea',
247                         fields.many2one:'int4',
248                         }
249         if type_dict.has_key(type(f)):
250                 f_type = (type_dict[type(f)], type_dict[type(f)])
251         elif isinstance(f, fields.float):
252                 if f.digits:
253                         f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0],f.digits[1]))
254                 else:
255                         f_type = ('float8', 'DOUBLE PRECISION')
256         elif isinstance(f, (fields.char, fields.reference)):
257                 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
258         elif isinstance(f, fields.selection):
259                 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
260                         f_size = reduce(lambda x,y: max(x,len(y[0])), f.selection, f.size or 16)
261                 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
262                         f_size = -1
263                 else:
264                         f_size = (hasattr(f,'size') and f.size) or 16
265
266                 if f_size == -1:
267                         f_type = ('int4', 'INTEGER')
268                 else:
269                         f_type = ('varchar', 'VARCHAR(%d)' % f_size)
270         elif isinstance(f, fields.function) and type_dict.has_key(eval('fields.'+(f._type))):
271                 t=eval('fields.'+(f._type))
272                 f_type = (type_dict[t], type_dict[t])
273         elif isinstance(f, fields.function) and f._type == 'float':
274                 f_type = ('float8', 'DOUBLE PRECISION')
275         elif isinstance(f, fields.function) and f._type == 'selection':
276                 f_type = ('text', 'text')
277         else:
278                 logger = netsvc.Logger()
279                 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
280                 f_type = None
281         return f_type
282
283 class orm_template(object):
284         _name = None
285         _columns = {}
286         _constraints = []
287         _defaults = {}
288         _rec_name = 'name'
289         _parent_name = 'parent_id'
290         _date_name = 'date'
291         _order = 'id'
292         _sequence = None
293         _description = None
294         _inherits = {}
295         _table = None
296
297         def _field_create(self, cr, context={}):
298                 cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name)
299                 if not cr.rowcount:
300                         # reference model in order to have a description of its fonctionnality in custom_report
301                         cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
302                         id = cr.fetchone()[0]
303                         cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id,self._name, self._description, self.__doc__))
304                         if 'module' in context:
305                                 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
306                                         ( 'model_'+self._table, context['module'], 'ir.model', id)
307                                 )
308                 cr.commit()
309
310                 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
311                 cols = {}
312                 for rec in cr.dictfetchall():
313                         cols[rec['name']] = rec
314
315                 cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name)
316                 model_id = cr.fetchone()[0]
317
318                 for (k,f) in self._columns.items():
319                         vals = {
320                                 'model_id':model_id,
321                                 'model':self._name,
322                                 'name':k,
323                                 'field_description':f.string.replace("'", " "),
324                                 'ttype':f._type,
325                                 'relate':(f.relate and 1) or 0,
326                                 'relation':f._obj or 'NULL',
327                                 'group_name':f.group_name or '',
328                                 'view_load':(f.view_load and 1) or 0,
329                                 'select_level':str(f.select or 0)
330                         }
331                         if k not in cols:
332                                 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
333                                 id = cr.fetchone()[0]
334                                 vals['id'] = id
335                                 cr.execute("""INSERT INTO ir_model_fields (
336                                         id, model_id, model, name, field_description, ttype, 
337                                         relate,relation,group_name,view_load,state,select_level
338                                 ) VALUES (
339                                         %d,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s, %s
340                                 )""", (
341                                         id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
342                                         bool(vals['relate']), vals['relation'], vals['group_name'], bool(vals['view_load']), 'base',
343                                         vals['select_level']
344                                 ))
345                                 if 'module' in context:
346                                         cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
347                                                 ( ('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id)
348                                         )
349                         else:
350                                 for key,val in vals.items():
351                                         if cols[k][key]<>vals[key]:
352                                                 print 'Different', cols[k][key], vals[key], k, key, self._name
353                                                 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'],vals['model'],vals['name']))
354                                                 cr.commit()
355                                                 cr.execute("""UPDATE ir_model_fields SET
356                                                         model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s,
357                                                         group_name=%s, view_load=%s, select_level=%s
358                                                 WHERE
359                                                         model=%s AND name=%s""", (
360                                                                 vals['model_id'],vals['field_description'],vals['ttype'],bool(vals['relate']),
361                                                                 vals['relation'], vals['group_name'], bool(vals['view_load']), 
362                                                                 vals['select_level'], vals['model'], vals['name']
363                                                         ))
364                                                 continue
365                 cr.commit()
366
367         def _auto_init(self, cr, context={}):
368                 self._field_create(cr, context)
369
370         def __init__(self, cr):
371                 if not self._description:
372                         self._description = self._name
373                 if not self._table:
374                         self._table=self._name.replace('.','_')
375
376         def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
377                 if not context:
378                         context={}
379                 self._list_class = list_class or browse_record_list
380                 cache = {}
381                 # need to accepts ints and longs because ids coming from a method
382                 # launched by button in the interface have a type long...
383                 if isinstance(select, (int, long)):
384                         return browse_record(cr,uid,select,self,cache, context=context, list_class=self._list_class, fields_process=fields_process)
385                 elif isinstance(select,list):
386                         return self._list_class([browse_record(cr,uid,id,self,cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context)
387                 else:
388                         return browse_null()
389
390         def __export_row(self, cr, uid, row, fields, context=None):
391                 lines = []
392                 data = map(lambda x: '', range(len(fields)))
393                 done = []
394                 for fpos in range(len(fields)):
395                         f = fields[fpos]
396                         if f:
397                                 r = row
398                                 i = 0
399                                 while i<len(f):
400                                         r = r[f[i]]
401                                         if not r:
402                                                 break
403                                         if isinstance(r, (browse_record_list, list)):
404                                                 first = True
405                                                 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
406                                                                 or [], fields)
407                                                 if fields2 in done:
408                                                         break
409                                                 done.append(fields2)
410                                                 for row2 in r:
411                                                         lines2 = self.__export_row(cr, uid, row2, fields2,
412                                                                         context)
413                                                         if first:
414                                                                 for fpos2 in range(len(fields)):
415                                                                         if lines2 and lines2[0][fpos2]:
416                                                                                 data[fpos2] = lines2[0][fpos2]
417                                                                 lines+= lines2[1:]
418                                                                 first = False
419                                                         else:
420                                                                 lines+= lines2
421                                                 break
422                                         i+=1
423                                 if i==len(f):
424                                         data[fpos] = str(r or '')
425                 return [data] + lines
426
427         def export_data(self, cr, uid, ids, fields, context=None):
428                 if not context:
429                         context={}
430                 fields = map(lambda x: x.split('/'), fields)
431                 datas = []
432                 for row in self.browse(cr, uid, ids, context):
433                         datas += self.__export_row(cr, uid, row, fields, context)
434                 return datas
435
436         def import_data(self, cr, uid, fields, datas, mode='init',
437                         current_module=None, noupdate=False, context=None):
438                 if not context:
439                         context={}
440                 fields = map(lambda x: x.split('/'), fields)
441                 logger = netsvc.Logger()
442                 def process_liness(self, datas, prefix, fields_def, position=0):
443                         line = datas[position]
444                         row = {}
445                         translate = {}
446                         todo = []
447                         warning = ''
448                         data_id= False
449                         #
450                         # Import normal fields
451                         #
452                         for i in range(len(fields)):
453                                 if i>=len(line):
454                                         raise Exception, _('Please check that all your lines have %d columns.') % (len(fields),)
455                                 field = fields[i]
456                                 if field == ["id"]:
457                                         data_id= line[i]
458                                         continue
459                                 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
460                                         res_id = False
461                                         if line[i]:
462                                                 if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
463                                                         res_id = []
464                                                         for word in line[i].split(','):
465                                                                 if '.' in word:
466                                                                         module, xml_id = word.rsplit('.', 1)
467                                                                 else:
468                                                                         module, xml_id = current_module, word
469                                                                 ir_model_data_obj = self.pool.get('ir.model.data')
470                                                                 id = ir_model_data_obj._get_id(cr, uid, module,
471                                                                                 xml_id)
472                                                                 res_id2 = ir_model_data_obj.read(cr, uid, [id],
473                                                                                 ['res_id'])[0]['res_id']
474                                                                 if res_id2:
475                                                                         res_id.append(res_id2)
476                                                         if len(res_id):
477                                                                 res_id=[(6,0,res_id)]
478                                                 else:
479                                                         if '.' in line[i]:
480                                                                 module, xml_id = line[i].rsplit('.', 1)
481                                                         else:
482                                                                 module, xml_id = current_module, line[i]
483                                                         ir_model_data_obj = self.pool.get('ir.model.data')
484                                                         id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
485                                                         res_id = ir_model_data_obj.read(cr, uid, [id],
486                                                                         ['res_id'])[0]['res_id']
487                                         row[field[0][:-3]] = res_id or False
488                                         continue
489                                 if (len(field) == len(prefix)+1) and \
490                                                 len(field[len(prefix)].split(':lang=')) == 2:
491                                         f, lang = field[len(prefix)].split(':lang=')
492                                         translate.setdefault(lang, {})[f]=line[i] or False
493                                         continue
494                                 if (len(field) == len(prefix)+1) and \
495                                                 (prefix == field[0:len(prefix)]):
496                                         if fields_def[field[len(prefix)]]['type'] == 'integer':
497                                                 res =line[i] and int(line[i])
498                                         elif fields_def[field[len(prefix)]]['type'] == 'float':
499                                                 res =line[i] and float(line[i])
500                                         elif fields_def[field[len(prefix)]]['type'] == 'selection':
501                                                 res = False
502                                                 if isinstance(fields_def[field[len(prefix)]]['selection'],
503                                                                 (tuple, list)):
504                                                         sel = fields_def[field[len(prefix)]]['selection']
505                                                 else:
506                                                         sel = fields_def[field[len(prefix)]]['selection'](self,
507                                                                         cr, uid, context)
508                                                 for key, val in sel:
509                                                         if str(key) == line[i]:
510                                                                 res = key
511                                                 if line[i] and not res:
512                                                         logger.notifyChannel("import", netsvc.LOG_WARNING,
513                                                                         "key '%s' not found in selection field '%s'" % \
514                                                                                         (line[i], field[len(prefix)]))
515                                         elif fields_def[field[len(prefix)]]['type']=='many2one':
516                                                 res = False
517                                                 if line[i]:
518                                                         relation = fields_def[field[len(prefix)]]['relation']
519                                                         res2 = self.pool.get(relation).name_search(cr, uid,
520                                                                         line[i], [], operator='=')
521                                                         res = (res2 and res2[0][0]) or False
522                                                         if not res:
523                                                                 warning += ('Relation not found: ' + line[i] + \
524                                                                                 ' on ' + relation + ' !\n')
525                                                                 logger.notifyChannel("import", netsvc.LOG_WARNING,
526                                                                                 'Relation not found: ' + line[i] + \
527                                                                                                 ' on ' + relation + ' !\n')
528                                         elif fields_def[field[len(prefix)]]['type']=='many2many':
529                                                 res = []
530                                                 if line[i]:
531                                                         relation = fields_def[field[len(prefix)]]['relation']
532                                                         for word in line[i].split(','):
533                                                                 res2 = self.pool.get(relation).name_search(cr,
534                                                                                 uid, word, [], operator='=')
535                                                                 res3 = (res2 and res2[0][0]) or False
536                                                                 if not res3:
537                                                                         warning += ('Relation not found: ' + \
538                                                                                         line[i] + ' on '+relation + ' !\n')
539                                                                         logger.notifyChannel("import",
540                                                                                         netsvc.LOG_WARNING,
541                                                                                         'Relation not found: ' + line[i] + \
542                                                                                                         ' on '+relation + ' !\n')
543                                                                 else:
544                                                                         res.append(res3)
545                                                         if len(res):
546                                                                 res= [(6,0,res)]
547                                         else:
548                                                 res = line[i] or False
549                                         row[field[len(prefix)]] = res
550                                 elif (prefix==field[0:len(prefix)]):
551                                         if field[0] not in todo:
552                                                 todo.append(field[len(prefix)])
553                         #
554                         # Import one2many fields
555                         #
556                         nbrmax = 1
557                         for field in todo:
558                                 newfd = self.pool.get(fields_def[field]['relation']).fields_get(
559                                                 cr, uid, context=context)
560                                 res = process_liness(self, datas, prefix + [field], newfd, position)
561                                 (newrow, max2, w2, translate2, data_id2) = res
562                                 nbrmax = max(nbrmax, max2)
563                                 warning = warning + w2
564                                 reduce(lambda x,y: x and y, newrow)
565                                 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
566                                                 [(0,0,newrow)]) or []
567                                 i = max2
568                                 while (position+i)<len(datas):
569                                         ok = True
570                                         for j in range(len(fields)):
571                                                 field2 = fields[j]
572                                                 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
573                                                         ok = False
574                                         if not ok:
575                                                 break
576
577                                         (newrow, max2, w2, translate2, data_id2) = process_liness(
578                                                         self, datas, prefix+[field], newfd, position+i)
579                                         warning = warning+w2
580                                         if reduce(lambda x,y: x or y, newrow.values()):
581                                                 row[field].append((0,0,newrow))
582                                         i+=max2
583                                         nbrmax = max(nbrmax, i)
584
585                         if len(prefix)==0:
586                                 for i in range(max(nbrmax,1)):
587                                         #if datas:
588                                         datas.pop(0)
589                         result = (row, nbrmax, warning, translate, data_id)
590                         return result
591
592                 fields_def = self.fields_get(cr, uid, context=context)
593                 done = 0
594
595                 while len(datas):
596                         res = {}
597                         try:
598                                 (res, other, warning, translate, data_id) = \
599                                                 process_liness(self, datas, [], fields_def)
600                                 if warning:
601                                         cr.rollback()
602                                         return (-1, res, warning, '')
603                                 id= self.pool.get('ir.model.data')._update(cr, uid, self._name,
604                                                 current_module, res, xml_id=data_id, mode=mode,
605                                                 noupdate=noupdate)
606                                 for lang in translate:
607                                         context2=context.copy()
608                                         context2['lang']=lang
609                                         self.write(cr, uid, [id], translate[lang], context2)
610                                 if config.get('commit_mode', False):
611                                         cr.commit()
612                         except Exception, e:
613                                 logger.notifyChannel("import",netsvc.LOG_ERROR, e)
614                                 cr.rollback()
615                                 try:
616                                         return (-1, res, e[0], warning)
617                                 except:
618                                         return (-1, res, e[0], '')
619                         done += 1
620                 #
621                 # TODO: Send a request with the result and multi-thread !
622                 #
623                 return (done, 0, 0, 0)
624
625         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
626                 raise _('The read method is not implemented on this object !')
627
628         def _validate(self, cr, uid, ids):
629                 field_error = []
630                 field_err_str = []
631                 for field in self._constraints:
632                         if not field[0](self, cr, uid, ids):
633                                 if len(field)>1:
634                                         field_error+=field[2]
635                                 field_err_str.append(field[1])
636                 if len(field_err_str):
637                         cr.rollback()
638                         raise except_orm('ValidateError', ('\n'.join(field_err_str), ','.join(field_error)))
639
640         def default_get(self, cr, uid, fields_list, context=None):
641                 return {}
642
643         def perm_read(self, cr, user, ids, context=None, details=True):
644                 raise _('The perm_read method is not implemented on this object !')
645
646         def unlink(self, cr, uid, ids, context=None):
647                 raise _('The unlink method is not implemented on this object !')
648
649         def write(self, cr, user, ids, vals, context=None):
650                 raise _('The write method is not implemented on this object !')
651
652         def create(self, cr, user, vals, context=None):
653                 raise _('The create method is not implemented on this object !')
654
655         # returns the definition of each field in the object
656         # the optional fields parameter can limit the result to some fields
657         def fields_get(self, cr, user, fields=None, context=None, read_access=True):
658                 if context is None:
659                         context = {}
660                 res = {}
661                 translation_obj = self.pool.get('ir.translation')
662                 model_access_obj = self.pool.get('ir.model.access')
663                 for parent in self._inherits:
664                         res.update(self.pool.get(parent).fields_get(cr, user, fields,
665                                 context))
666                 for f in self._columns.keys():
667                         res[f] = {'type': self._columns[f]._type}
668                         for arg in ('string', 'readonly', 'states', 'size', 'required',
669                                         'change_default', 'translate', 'help', 'select'):
670                                 if getattr(self._columns[f], arg):
671                                         res[f][arg] = getattr(self._columns[f], arg)
672                         if not read_access:
673                                 res[f]['readonly']= True
674                                 res[f]['states'] = {}
675                         for arg in ('digits', 'invisible'):
676                                 if hasattr(self._columns[f], arg) \
677                                                 and getattr(self._columns[f], arg):
678                                         res[f][arg] = getattr(self._columns[f], arg)
679
680                         # translate the field label
681                         res_trans = translation_obj._get_source(cr, user,
682                                         self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
683                         if res_trans:
684                                 res[f]['string'] = res_trans
685                         help_trans = translation_obj._get_source(cr, user,
686                                         self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
687                         if help_trans:
688                                 res[f]['help'] = help_trans
689
690                         if hasattr(self._columns[f], 'selection'):
691                                 if isinstance(self._columns[f].selection, (tuple, list)):
692                                         sel = self._columns[f].selection
693                                         # translate each selection option
694                                         sel2 = []
695                                         for (key,val) in sel:
696                                                 val2 = translation_obj._get_source(cr, user,
697                                                                 self._name + ',' + f, 'selection',
698                                                                 context.get('lang', False) or 'en_US', val)
699                                                 sel2.append((key, val2 or val))
700                                         sel = sel2
701                                         res[f]['selection'] = sel
702                                 else:
703                                         # call the 'dynamic selection' function
704                                         res[f]['selection'] = self._columns[f].selection(self, cr,
705                                                         user, context)
706                         if res[f]['type'] in ('one2many', 'many2many',
707                                         'many2one', 'one2one'):
708                                 res[f]['relation'] = self._columns[f]._obj
709                                 res[f]['domain'] = self._columns[f]._domain
710                                 res[f]['context'] = self._columns[f]._context
711
712                 if fields:
713                         # filter out fields which aren't in the fields list
714                         for r in res.keys():
715                                 if r not in fields:
716                                         del res[r]
717                 return res
718
719         #
720         # Overload this method if you need a window title which depends on the context
721         #
722         def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
723                 return False
724
725         def __view_look_dom(self, cr, user, node, context=None):
726                 if not context:
727                         context={}
728                 result = False
729                 fields = {}
730                 childs = True
731
732                 if node.nodeType==node.ELEMENT_NODE and node.localName=='field':
733                         if node.hasAttribute('name'):
734                                 attrs = {}
735                                 try:
736                                         if node.getAttribute('name') in self._columns:
737                                                 relation = self._columns[node.getAttribute('name')]._obj
738                                         else:
739                                                 relation = self._inherit_fields[node.getAttribute('name')][2]._obj
740                                 except:
741                                         relation = False
742
743                                 if relation:
744                                         childs = False
745                                         views = {}
746                                         for f in node.childNodes:
747                                                 if f.nodeType==f.ELEMENT_NODE and f.localName in ('form','tree','graph'):
748                                                         node.removeChild(f)
749                                                         xarch,xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, context)
750                                                         views[str(f.localName)] = {
751                                                                 'arch': xarch,
752                                                                 'fields': xfields
753                                                         }
754                                         attrs = {'views': views}
755                                 fields[node.getAttribute('name')] = attrs
756
757                 elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form','tree'):
758                         result = self.view_header_get(cr, user, False, node.localName, context)
759                         if result:
760                                 node.setAttribute('string', result.decode('utf-8'))
761                 if node.nodeType==node.ELEMENT_NODE and node.hasAttribute('groups'):
762                         groups = None
763                         group_str = node.getAttribute('groups')
764                         if ',' in group_str:
765                                 groups = group_str.split(',');
766                                 readonly = False
767                                 access_pool = self.pool.get('ir.model.access')
768                                 for group in groups:
769                                         readonly = readonly and access_pool.check_groups(cr, user, group)
770
771                                 if readonly:
772                                         parent = node.parentNode
773                                         parent.removeChild(node)
774
775                         else:
776                                 if not self.pool.get('ir.model.access').check_groups(cr, user, group_str):
777                                         parent = node.parentNode
778                                         parent.removeChild(node)
779
780                 if node.nodeType == node.ELEMENT_NODE:
781                         # translate view
782                         if ('lang' in context) and not result:
783                                 if node.hasAttribute('string') and node.getAttribute('string'):
784                                         trans = tools.translate(cr, self._name, 'view', context['lang'], node.getAttribute('string').encode('utf8'))
785                                         if trans:
786                                                 node.setAttribute('string', trans.decode('utf8'))
787                                 if node.hasAttribute('sum') and node.getAttribute('sum'):
788                                         trans = tools.translate(cr, self._name, 'view', context['lang'], node.getAttribute('sum').encode('utf8'))
789                                         if trans:
790                                                 node.setAttribute('sum', trans.decode('utf8'))
791                         #
792                         # Add view for properties !
793                         #
794                         if node.localName=='properties':
795                                 parent = node.parentNode
796                                 doc = node.ownerDocument
797                                 models = map(lambda x: "'"+x+"'", [self._name] + self._inherits.keys())
798                                 cr.execute('select id,name,group_name from ir_model_fields where model in ('+','.join(models)+') and view_load order by group_name, id')
799                                 oldgroup = None
800                                 res = cr.fetchall()
801                                 for id, fname, gname in res:
802                                         if oldgroup != gname:
803                                                 child = doc.createElement('separator')
804                                                 child.setAttribute('string', gname.decode('utf-8'))
805                                                 child.setAttribute('colspan', "4")
806                                                 oldgroup = gname
807                                                 parent.insertBefore(child, node)
808
809                                         child = doc.createElement('field')
810                                         child.setAttribute('name', fname.decode('utf-8'))
811                                         parent.insertBefore(child, node)
812                                 parent.removeChild(node)
813
814                 if childs:
815                         for f in node.childNodes:
816                                 fields.update(self.__view_look_dom(cr, user, f,context))
817                 return fields
818
819         def __view_look_dom_arch(self, cr, user, node, context=None):
820                 if not context:
821                         context={}
822                 fields_def = self.__view_look_dom(cr, user, node, context=context)
823
824                 buttons = xpath.Evaluate('//button', node)
825                 if buttons:
826                         for button in buttons:
827                                 if button.getAttribute('type') == 'object':
828                                         continue
829
830                                 ok = True
831
832                                 serv = netsvc.LocalService('object_proxy')
833                                 user_roles = serv.execute_cr(cr, user, 'res.users', 'read', [user], ['roles_id'])[0]['roles_id']
834                                 cr.execute("select role_id from wkf_transition where signal='%s'" % button.getAttribute('name'))
835                                 roles = cr.fetchall()
836                                 for role in roles:
837                                         if role[0]:
838                                                 ok = ok and serv.execute_cr(cr, user, 'res.roles', 'check', user_roles, role[0])
839
840                                 if not ok:
841                                         button.setAttribute('readonly', '1')
842                                 else:
843                                         button.setAttribute('readonly', '0')
844
845                 arch = node.toxml(encoding="utf-8").replace('\t', '')
846
847                 fields = self.fields_get(cr, user, fields_def.keys(), context)
848                 for field in fields_def:
849                         fields[field].update(fields_def[field])
850                 return arch, fields
851
852
853         def __get_default_calendar_view(self):
854                 """Generate a default calendar view (For internal use only).
855                 """
856
857                 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
858                             '<calendar string="%s" date_start="%s"') % (self._description, self._date_name)
859
860                 if 'user_id' in self._columns:
861                         arch += ' color="user_id"'
862
863                 elif 'partner_id' in self._columns:
864                         arch += ' color="partner_id"'
865
866                 if 'date_stop' in self._columns:
867                         arch += ' date_stop="date_stop"'
868
869                 elif 'date_end' in self._columns:
870                         arch += ' date_stop="date_end"'
871
872                 elif 'date_delay' in self._columns:
873                         arch += ' date_delay="date_delay"'
874
875                 elif 'planned_hours' in self._columns:
876                         arch += ' date_delay="planned_hours"'
877
878                 arch += ('>\n'
879                                  '  <field name="%s"/>\n'
880                                  '</calendar>') % (self._rec_name)
881
882                 return arch
883
884         #
885         # if view_id, view_type is not required
886         #
887         def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False):
888                 if not context:
889                         context={}
890                 def _inherit_apply(src, inherit):
891                         def _find(node, node2):
892                                 # Check if xpath query or normal inherit (with field matching)
893                                 if node2.nodeType==node2.ELEMENT_NODE and node2.localName=='xpath':
894                                         res = xpath.Evaluate(node2.getAttribute('expr'), node)
895                                         return res and res[0]
896                                 else:
897                                         if node.nodeType==node.ELEMENT_NODE and node.localName==node2.localName:
898                                                 res = True
899                                                 for attr in node2.attributes.keys():
900                                                         if attr=='position':
901                                                                 continue
902                                                         if node.hasAttribute(attr):
903                                                                 if node.getAttribute(attr)==node2.getAttribute(attr):
904                                                                         continue
905                                                         res = False
906                                                 if res:
907                                                         return node
908                                         for child in node.childNodes:
909                                                 res = _find(child, node2)
910                                                 if res: return res
911                                 return None
912
913                         doc_src = dom.minidom.parseString(src)
914                         doc_dest = dom.minidom.parseString(inherit)
915                         toparse = doc_dest.childNodes
916                         while len(toparse):
917                                 node2 = toparse.pop(0)
918                                 if not node2.nodeType==node2.ELEMENT_NODE:
919                                         continue
920                                 if node2.localName=='data':
921                                         toparse += node2.childNodes
922                                         continue
923                                 node = _find(doc_src, node2)
924                                 if node:
925                                         pos = 'inside'
926                                         if node2.hasAttribute('position'):
927                                                 pos = node2.getAttribute('position')
928                                         if pos=='replace':
929                                                 parent = node.parentNode
930                                                 for child in node2.childNodes:
931                                                         if child.nodeType==child.ELEMENT_NODE:
932                                                                 parent.insertBefore(child, node)
933                                                 parent.removeChild(node)
934                                         else:
935                                                 for child in node2.childNodes:
936                                                         if child.nodeType==child.ELEMENT_NODE:
937                                                                 if pos=='inside':
938                                                                         node.appendChild(child)
939                                                                 elif pos=='after':
940                                                                         sib = node.nextSibling
941                                                                         if sib:
942                                                                                 node.parentNode.insertBefore(child, sib)
943                                                                         else:
944                                                                                 node.parentNode.appendChild(child)
945                                                                 elif pos=='before':
946                                                                         node.parentNode.insertBefore(child, node)
947                                                                 else:
948                                                                         raise AttributeError, _('Unknown position in inherited view %s !') % pos
949                                 else:
950                                         attrs = ''.join([
951                                                 ' %s="%s"' % (attr, node2.getAttribute(attr))
952                                                 for attr in node2.attributes.keys()
953                                                 if attr != 'position'
954                                         ])
955                                         tag = "<%s%s>" % (node2.localName, attrs)
956                                         raise AttributeError, _("Couldn't find tag '%s' in parent view !") % tag
957                         return doc_src.toxml(encoding="utf-8").replace('\t', '')
958
959                 result = {'type':view_type, 'model':self._name}
960
961                 ok = True
962                 model = True
963                 sql_res = False
964                 while ok:
965                         if view_id:
966                                 where = (model and (" and model='%s'" % (self._name,))) or ''
967                                 cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%d'+where, (view_id,))
968                         else:
969                                 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))
970                         sql_res = cr.fetchone()
971                         if not sql_res:
972                                 break
973                         ok = sql_res[5]
974                         view_id = ok or sql_res[3]
975                         model = False
976
977                 # if a view was found
978                 if sql_res:
979                         result['type'] = sql_res[4]
980                         result['view_id'] = sql_res[3]
981                         result['arch'] = sql_res[0]
982
983                         def _inherit_apply_rec(result, inherit_id):
984                                 # get all views which inherit from (ie modify) this view
985                                 cr.execute('select arch,id from ir_ui_view where inherit_id=%d and model=%s order by priority', (inherit_id, self._name))
986                                 sql_inherit = cr.fetchall()
987                                 for (inherit,id) in sql_inherit:
988                                         result = _inherit_apply(result, inherit)
989                                         result = _inherit_apply_rec(result, id)
990                                 return result
991
992                         result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
993
994                         result['name'] = sql_res[1]
995                         result['field_parent'] = sql_res[2] or False
996                 else:
997                         # otherwise, build some kind of default view
998                         if view_type == 'form':
999                                 res = self.fields_get(cr, user, context=context)
1000                                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1001                                 '''<form string="%s">''' % (self._description,)
1002                                 for x in res:
1003                                         if res[x]['type'] not in ('one2many', 'many2many'):
1004                                                 xml += '<field name="%s"/>' % (x,)
1005                                                 if res[x]['type'] == 'text':
1006                                                         xml += "<newline/>"
1007                                 xml += "</form>"
1008                         elif view_type == 'tree':
1009                                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1010                                 '''<tree string="%s"><field name="%s"/></tree>''' \
1011                                 % (self._description, self._rec_name)
1012                         elif view_type == 'calendar':
1013                                 xml = self.__get_default_calendar_view()
1014                         else:
1015                                 xml = ''
1016                         result['arch'] = xml
1017                         result['name'] = 'default'
1018                         result['field_parent'] = False
1019                         result['view_id'] = 0
1020
1021                 doc = dom.minidom.parseString(result['arch'].encode('utf-8'))
1022                 xarch, xfields = self.__view_look_dom_arch(cr, user, doc, context=context)
1023                 result['arch'] = xarch
1024                 result['fields'] = xfields
1025                 if toolbar:
1026                         def clean(x):
1027                                 x = x[2]
1028                                 for key in ('report_sxw_content', 'report_rml_content',
1029                                                 'report_sxw', 'report_rml',
1030                                                 'report_sxw_content_data', 'report_rml_content_data'):
1031                                         if key in x:
1032                                                 del x[key]
1033                                 return x
1034                         ir_values_obj = self.pool.get('ir.values')
1035                         resprint = ir_values_obj.get(cr, user, 'action',
1036                                         'client_print_multi', [(self._name, False)], False,
1037                                         context)
1038                         resaction = ir_values_obj.get(cr, user, 'action',
1039                                         'client_action_multi', [(self._name, False)], False,
1040                                         context)
1041
1042                         resrelate = ir_values_obj.get(cr, user, 'action',
1043                                         'client_action_relate', [(self._name, False)], False,
1044                                         context)
1045                         resprint = map(clean, resprint)
1046                         resaction = map(clean, resaction)
1047                         resaction = filter(lambda x: not x.get('multi',False), resaction)
1048                         resprint = filter(lambda x: not x.get('multi',False), resprint)
1049                         resrelate = map(lambda x:x[2], resrelate)
1050
1051                         for x in resprint+resaction+resrelate:
1052                                 x['string'] = x['name']
1053
1054                         result['toolbar'] = {
1055                                 'print': resprint,
1056                                 'action': resaction,
1057                                 'relate': resrelate
1058                         }
1059                 return result
1060
1061         _view_look_dom_arch=__view_look_dom_arch
1062
1063         def search_count(self, cr, user, args, context=None):
1064                 if not context:
1065                         context = {}
1066                 res = self.search(cr, user, args, context=context, count=True)
1067                 if isinstance(res, list):
1068                         return len(res)
1069                 return res
1070
1071         def search(self, cr, user, args, offset=0, limit=None, order=None,
1072                         context=None, count=False):
1073                 raise _('The search method is not implemented on this object !')
1074
1075         def name_get(self, cr, user, ids, context=None):
1076                 raise _('The name_get method is not implemented on this object !')
1077
1078         def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
1079                 raise _('The name_search method is not implemented on this object !')
1080
1081         def copy(self, cr, uid, id, default=None, context=None):
1082                 raise _('The copy method is not implemented on this object !')
1083
1084 class orm_memory(orm_template):
1085         _protected = ['read','write','create','default_get','perm_read','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data','search_count']
1086         _inherit_fields = {}
1087         _max_count = 200
1088         _max_hours = 1
1089         _check_time = 20
1090         def __init__(self, cr):
1091                 super(orm_memory, self).__init__(cr)
1092                 self.datas = {}
1093                 self.next_id = 0
1094                 self.check_id = 0
1095                 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1096
1097         def clear(self):
1098                 self.check_id+=1
1099                 if self.check_id % self._check_time:
1100                         return True
1101                 tounlink = []
1102                 max = time.time() - self._max_hours * 60 * 60
1103                 for id in self.datas:
1104                         if self.datas[id]['internal.date_access'] < max:
1105                                 tounlink.append(id)
1106                 self.unlink(tounlink)
1107                 if len(self.datas)>self._max_count:
1108                         sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1109                         sorted.sort()
1110                         ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1111                         self.unlink(ids)
1112                 return True
1113
1114         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1115                 if not fields:
1116                         fields = self._columns.keys()
1117                 result = []
1118                 for id in ids:
1119                         r = {'id': id}
1120                         for f in fields:
1121                                 r[f] = self.datas[id].get(f, False)
1122                         result.append(r)
1123                         self.datas[id]['internal.date_access'] = time.time()
1124                 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
1125                 for f in fields_post:
1126                         res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=False)
1127                         for record in result:
1128                                 record[f] = res2[record['id']]
1129                 return result
1130
1131         def write(self, cr, user, ids, vals, context=None):
1132                 vals2 = {}
1133                 upd_todo = []
1134                 for field in vals:
1135                         if self._columns[field]._classic_write:
1136                                 vals2[field] = vals[field]
1137                         else:
1138                                 upd_todo.append(field)
1139                 for id_new in ids:
1140                         self.datas[id_new].update(vals2)
1141                         self.datas[id_new]['internal.date_access'] = time.time()
1142                         for field in upd_todo:
1143                                 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1144                 self._validate(cr, user, [id_new])
1145                 wf_service = netsvc.LocalService("workflow")
1146                 wf_service.trg_write(user, self._name, id_new, cr)
1147                 self.clear()
1148                 return id_new
1149
1150         def create(self, cr, user, vals, context=None):
1151                 self.next_id += 1
1152                 id_new = self.next_id
1153                 default = []
1154                 for f in self._columns.keys():
1155                         if not f in vals:
1156                                 default.append(f)
1157                 if len(default):
1158                         vals.update(self.default_get(cr, user, default, context))
1159                 vals2 = {}
1160                 upd_todo = []
1161                 for field in vals:
1162                         if self._columns[field]._classic_write:
1163                                 vals2[field] = vals[field]
1164                         else:
1165                                 upd_todo.append(field)
1166                 self.datas[id_new] = vals2
1167                 self.datas[id_new]['internal.date_access'] = time.time()
1168                 for field in upd_todo:
1169                         self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1170                 self._validate(cr, user, [id_new])
1171                 wf_service = netsvc.LocalService("workflow")
1172                 wf_service.trg_create(user, self._name, id_new, cr)
1173                 self.clear()
1174                 return id_new
1175
1176         def default_get(self, cr, uid, fields_list, context=None):
1177                 if not context:
1178                         context={}
1179                 value = {}
1180                 # get the default values for the inherited fields
1181                 for f in fields_list:
1182                         if f in self._defaults:
1183                                 value[f] = self._defaults[f](self, cr, uid, context)
1184                         fld_def = ((f in self._columns) and self._columns[f]) \
1185                                         or ((f in self._inherit_fields ) and self._inherit_fields[f][2]) \
1186                                         or False
1187
1188                 # get the default values set by the user and override the default
1189                 # values defined in the object
1190                 ir_values_obj = self.pool.get('ir.values')
1191                 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1192                 for id, field, field_value in res:
1193                         if field in fields_list:
1194                                 fld_def = (field in self._columns)
1195                                 if fld_def._type in ('many2one', 'one2one'):
1196                                         obj = self.pool.get(fld_def._obj)
1197                                         if not obj.search(cr, uid, [('id', '=', field_value)]):
1198                                                 continue
1199                                 if fld_def._type in ('many2many'):
1200                                         obj = self.pool.get(fld_def._obj)
1201                                         field_value2 = []
1202                                         for i in range(len(field_value)):
1203                                                 if not obj.search(cr, uid, [('id', '=',
1204                                                         field_value[i])]):
1205                                                         continue
1206                                                 field_value2.append(field_value[i])
1207                                         field_value = field_value2
1208                                 if fld_def._type in ('one2many'):
1209                                         obj = self.pool.get(fld_def._obj)
1210                                         field_value2 = []
1211                                         for i in range(len(field_value)):
1212                                                 field_value2.append({})
1213                                                 for field2 in field_value[i]:
1214                                                         if obj._columns[field2]._type in ('many2one', 'one2one'):
1215                                                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1216                                                                 if not obj2.search(cr, uid,
1217                                                                                 [('id', '=', field_value[i][field2])]):
1218                                                                         continue
1219                                                         # TODO add test for many2many and one2many
1220                                                         field_value2[i][field2] = field_value[i][field2]
1221                                         field_value = field_value2
1222                                 value[field] = field_value
1223                 return value
1224
1225         def search(self, cr, user, args, offset=0, limit=None, order=None,
1226                         context=None, count=False):
1227                 return self.datas.keys()
1228
1229         def unlink(self, cr, uid, ids, context=None):
1230                 for id in ids:
1231                         if id in self.datas:
1232                                 del self.datas[id]
1233                 if len(ids):
1234                         cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str,ids))+')', (self._name, ))
1235                 return True
1236
1237         def perm_read(self, cr, user, ids, context=None, details=True):
1238                 result = []
1239                 for id in ids:
1240                         result.append({
1241                                 'create_uid': (user, 'Root'),
1242                                 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'),
1243                                 'write_uid': False,
1244                                 'write_date': False,
1245                                 'id': id
1246                         })
1247                 return result
1248
1249 class orm(orm_template):
1250
1251         _sql_constraints = []
1252
1253         _log_access = True
1254         _table = None
1255         _protected = ['read','write','create','default_get','perm_read','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data','search_count']
1256         def _auto_init(self, cr, context={}):
1257                 logger = netsvc.Logger()
1258                 create = False
1259                 self._field_create(cr, context=context)
1260                 if not hasattr(self, "_auto") or self._auto:
1261                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
1262                         if not cr.rowcount:
1263                                 cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table)
1264                                 create = True
1265                         cr.commit()
1266                         if self._log_access:
1267                                 logs = {
1268                                         'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1269                                         'create_date': 'TIMESTAMP',
1270                                         'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1271                                         'write_date': 'TIMESTAMP'
1272                                 }
1273                                 for k in logs:
1274                                         cr.execute(
1275                                                 """
1276                                                 SELECT c.relname
1277                                                 FROM pg_class c, pg_attribute a
1278                                                 WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid
1279                                                 """ % (self._table, k))
1280                                         if not cr.rowcount:
1281                                                 cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" %
1282                                                         (self._table, k, logs[k]))
1283                                                 cr.commit()
1284
1285                         # iterate on the database columns to drop the NOT NULL constraints
1286                         # of fields which were required but have been removed
1287                         cr.execute(
1288                                 "SELECT a.attname, a.attnotnull "\
1289                                 "FROM pg_class c, pg_attribute a "\
1290                                 "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table)
1291                         db_columns = cr.dictfetchall()
1292                         for column in db_columns:
1293                                 if column['attname'] not in ('id', 'oid', 'tableoid', 'ctid', 'xmin', 'xmax', 'cmin', 'cmax'):
1294                                         if column['attnotnull'] and column['attname'] not in self._columns:
1295                                                 cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, column['attname']))
1296
1297                         # iterate on the "object columns"
1298                         for k in self._columns:
1299                                 if k in ('id','write_uid','write_date','create_uid','create_date'):
1300                                         continue
1301                                         #raise _('Can not define a column %s. Reserved keyword !') % (k,)
1302                                 f = self._columns[k]
1303
1304                                 if isinstance(f, fields.one2many):
1305                                         cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
1306                                         if cr.fetchone():
1307                                                 cr.execute("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))
1308                                                 res = cr.fetchone()[0]
1309                                                 if not res:
1310                                                         cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (%s) REFERENCES \"%s\" ON DELETE SET NULL" % (self._obj, f._fields_id, f._table))
1311                                 elif isinstance(f, fields.many2many):
1312                                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
1313                                         if not cr.dictfetchall():
1314                                                 #FIXME: Remove this try/except
1315                                                 try:
1316                                                         ref = self.pool.get(f._obj)._table
1317                                                 except AttributeError:
1318                                                         ref = f._obj.replace('.','_')
1319                                                 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))
1320                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel,f._id1,f._rel,f._id1))
1321                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel,f._id2,f._rel,f._id2))
1322                                                 cr.commit()
1323                                 else:
1324                                         cr.execute("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))
1325                                         res = cr.dictfetchall()
1326                                         if not res:
1327                                                 if not isinstance(f,fields.function) or f.store:
1328
1329                                                         # add the missing field
1330                                                         cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % (self._table, k, get_pg_type(f)[1]))
1331
1332                                                         # initialize it
1333                                                         if not create and k in self._defaults:
1334                                                                 default = self._defaults[k](self, cr, 1, {})
1335                                                                 if not default:
1336                                                                         cr.execute("UPDATE \"%s\" SET \"%s\"=NULL" % (self._table, k))
1337                                                                 else:
1338                                                                         cr.execute("UPDATE \"%s\" SET \"%s\"='%s'" % (self._table, k, default))
1339                                                         if isinstance(f,fields.function):
1340                                                                 cr.execute('select id from '+self._table)
1341                                                                 ids_lst = map(lambda x: x[0], cr.fetchall())
1342                                                                 while ids_lst:
1343                                                                         iids = ids_lst[:40]
1344                                                                         ids_lst = ids_lst[40:]
1345                                                                         res = f.get(cr, self, iids, k, 1, {})
1346                                                                         for r in res.items():
1347                                                                                 cr.execute("UPDATE \"%s\" SET \"%s\"='%s' where id=%d"% (self._table, k, r[1],r[0]))
1348
1349                                                         # and add constraints if needed
1350                                                         if isinstance(f, fields.many2one):
1351                                                                 #FIXME: Remove this try/except
1352                                                                 try:
1353                                                                         ref = self.pool.get(f._obj)._table
1354                                                                 except AttributeError:
1355                                                                         ref = f._obj.replace('.','_')
1356                                                                 # ir_actions is inherited so foreign key doesn't work on it
1357                                                                 if ref <> 'ir_actions':
1358                                                                         cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (\"%s\") REFERENCES \"%s\" ON DELETE %s" % (self._table, k, ref, f.ondelete))
1359                                                         if f.select:
1360                                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k))
1361                                                         if f.required:
1362                                                                 cr.commit()
1363                                                                 try:
1364                                                                         cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k))
1365                                                                 except:
1366                                                                         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))
1367                                                         cr.commit()
1368                                         elif len(res)==1:
1369                                                 f_pg_def = res[0]
1370                                                 f_pg_type = f_pg_def['typname']
1371                                                 f_pg_size = f_pg_def['size']
1372                                                 f_pg_notnull = f_pg_def['attnotnull']
1373                                                 if isinstance(f, fields.function) and not f.store:
1374                                                         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))
1375                                                         f_obj_type = None
1376                                                 else:
1377                                                         f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
1378
1379                                                 if f_obj_type:
1380                                                         if f_pg_type != f_obj_type:
1381                                                                 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))
1382                                                         if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
1383                                                                 # columns with the name 'type' cannot be changed for an unknown reason?!
1384                                                                 if k != 'type':
1385                                                                         if f_pg_size > f.size:
1386                                                                                 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))
1387 #TODO: check si y a des donnees qui vont poser probleme (select char_length(...))
1388 #TODO: issue a log message even if f_pg_size < f.size
1389                                                                         cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table,k))
1390                                                                         cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table,k,f.size))
1391                                                                         cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size))
1392                                                                         cr.execute("ALTER TABLE \"%s\" DROP COLUMN temp_change_size" % (self._table,))
1393                                                                         cr.commit()
1394                                                         # if the field is required and hasn't got a NOT NULL constraint
1395                                                         if f.required and f_pg_notnull == 0:
1396                                                                 # set the field to the default value if any
1397                                                                 if self._defaults.has_key(k):
1398                                                                         default = self._defaults[k](self, cr, 1, {})
1399                                                                         if not (default is False):
1400                                                                                 cr.execute("UPDATE \"%s\" SET \"%s\"='%s' WHERE %s is NULL" % (self._table, k, default, k))
1401                                                                                 cr.commit()
1402                                                                 # add the NOT NULL constraint
1403                                                                 try:
1404                                                                         cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k))
1405                                                                         cr.commit()
1406                                                                 except:
1407                                                                         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))
1408                                                                 cr.commit()
1409                                                         elif not f.required and f_pg_notnull == 1:
1410                                                                 cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table,k))
1411                                                                 cr.commit()
1412                                                         cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table))
1413                                                         res = cr.dictfetchall()
1414                                                         if not res and f.select:
1415                                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k))
1416                                                                 cr.commit()
1417                                                         if res and not f.select:
1418                                                                 cr.execute("DROP INDEX \"%s_%s_index\"" % (self._table, k))
1419                                                                 cr.commit()
1420                                                         if isinstance(f, fields.many2one):
1421                                                                 ref = self.pool.get(f._obj)._table
1422                                                                 if ref != 'ir_actions':
1423                                                                         cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, ' \
1424                                                                                                 'pg_attribute as att1, pg_attribute as att2 ' \
1425                                                                                         'WHERE con.conrelid = cl1.oid ' \
1426                                                                                                 'AND cl1.relname = %s ' \
1427                                                                                                 'AND con.confrelid = cl2.oid ' \
1428                                                                                                 'AND cl2.relname = %s ' \
1429                                                                                                 'AND array_lower(con.conkey, 1) = 1 ' \
1430                                                                                                 'AND con.conkey[1] = att1.attnum ' \
1431                                                                                                 'AND att1.attrelid = cl1.oid ' \
1432                                                                                                 'AND att1.attname = %s ' \
1433                                                                                                 'AND array_lower(con.confkey, 1) = 1 ' \
1434                                                                                                 'AND con.confkey[1] = att2.attnum ' \
1435                                                                                                 'AND att2.attrelid = cl2.oid ' \
1436                                                                                                 'AND att2.attname = %s ' \
1437                                                                                                 'AND con.contype = \'f\'', (self._table, ref, k, 'id'))
1438                                                                         res = cr.dictfetchall()
1439                                                                         if res:
1440                                                                                 confdeltype = {
1441                                                                                         'RESTRICT': 'r',
1442                                                                                         'NO ACTION': 'a',
1443                                                                                         'CASCADE': 'c',
1444                                                                                         'SET NULL': 'n',
1445                                                                                         'SET DEFAULT': 'd',
1446                                                                                 }
1447                                                                                 if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'):
1448                                                                                         cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
1449                                                                                         cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
1450                                                                                         cr.commit()
1451                                         else:
1452                                                 print "ERROR"
1453                 else:
1454                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
1455                         create = not bool(cr.fetchone())
1456
1457                 for (key,con,_) in self._sql_constraints:
1458                         cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key))
1459                         if not cr.dictfetchall():
1460                                 try:
1461                                         cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table,self._table,key, con,))
1462                                         cr.commit()
1463                                 except:
1464                                         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,))
1465
1466                 if create:
1467                         if hasattr(self,"_sql"):
1468                                 for line in self._sql.split(';'):
1469                                         line2 = line.replace('\n','').strip()
1470                                         if line2:
1471                                                 cr.execute(line2)
1472                                                 cr.commit()
1473
1474         def __init__(self, cr):
1475                 super(orm, self).__init__(cr)
1476                 f=filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns)
1477                 if f:
1478                         list_store = []
1479                         tuple_store = ()
1480                         tuple_fn = ()
1481                         for store_field in f:
1482                                 if not self._columns[store_field].store == True:
1483                                         dict_store = self._columns[store_field].store
1484                                         key = dict_store.keys()
1485                                         list_data=[]
1486                                         for i in key:
1487                                                 tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[i][0]),dict_store[i][1],i
1488                                                 list_data.append(tuple_store)
1489                                         #tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[key[0]][0]),dict_store[key[0]][1]
1490                                         for l in list_data:
1491                                                 list_store=[]
1492                                                 if l[5] in self.pool._store_function.keys():
1493                                                         self.pool._store_function[l[5]].append(l)
1494                                                         temp_list = list(set(self.pool._store_function[l[5]]))
1495                                                         self.pool._store_function[l[5]]=temp_list
1496                                                 else:
1497                                                         list_store.append(l)
1498                                                         self.pool._store_function[l[5]]=list_store
1499
1500                 for (key,_,msg) in self._sql_constraints:
1501                         self.pool._sql_error[self._table+'_'+key] = msg
1502
1503                 # Load manual fields
1504
1505                 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state','ir.model.fields'))
1506                 if cr.fetchone():
1507                         cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name,'manual'))
1508                         for field in cr.dictfetchall():
1509                                 if field['name'] in self._columns:
1510                                         continue
1511                                 attrs = {
1512                                         'string': field['field_description'],
1513                                         'required': bool(field['required']),
1514                                         'readonly': bool(field['readonly']),
1515                                         'domain': field['domain'] or None,
1516                                         'size': field['size'],
1517                                         'ondelete': field['on_delete'],
1518                                         'translate': (field['translate']),
1519                                         #'select': int(field['select_level'])
1520                                 }
1521                                 #if field['relation']:
1522                                 #       attrs['relation'] = field['relation']
1523                                 if field['ttype'] == 'selection':
1524                                         self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
1525                                 elif field['ttype'] == 'many2one':
1526                                         self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
1527                                 else:
1528                                         self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
1529
1530                 self._inherits_reload()
1531                 if not self._sequence:
1532                         self._sequence = self._table+'_id_seq'
1533                 for k in self._defaults:
1534                         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,)
1535                 for f in self._columns:
1536                         self._columns[f].restart()
1537
1538         def default_get(self, cr, uid, fields_list, context=None):
1539                 if not context:
1540                         context={}
1541                 value = {}
1542                 # get the default values for the inherited fields
1543                 for t in self._inherits.keys():
1544                         value.update(self.pool.get(t).default_get(cr, uid, fields_list,
1545                                 context))
1546
1547                 # get the default values defined in the object
1548                 for f in fields_list:
1549                         if f in self._defaults:
1550                                 value[f] = self._defaults[f](self, cr, uid, context)
1551                         fld_def = ((f in self._columns) and self._columns[f]) \
1552                                         or ((f in self._inherit_fields ) and self._inherit_fields[f][2]) \
1553                                         or False
1554                         if isinstance(fld_def, fields.property):
1555                                 property_obj = self.pool.get('ir.property')
1556                                 definition_id = fld_def._field_get(cr, uid, self._name, f)
1557                                 nid = property_obj.search(cr, uid, [('fields_id', '=',
1558                                         definition_id), ('res_id', '=', False)])
1559                                 if nid:
1560                                         prop_value = property_obj.browse(cr, uid, nid[0],
1561                                                         context=context).value
1562                                         value[f] = (prop_value and int(prop_value.split(',')[1])) \
1563                                                         or False
1564
1565                 # get the default values set by the user and override the default
1566                 # values defined in the object
1567                 ir_values_obj = self.pool.get('ir.values')
1568                 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1569                 for id, field, field_value in res:
1570                         if field in fields_list:
1571                                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1572                                 if fld_def._type in ('many2one', 'one2one'):
1573                                         obj = self.pool.get(fld_def._obj)
1574                                         if not obj.search(cr, uid, [('id', '=', field_value)]):
1575                                                 continue
1576                                 if fld_def._type in ('many2many'):
1577                                         obj = self.pool.get(fld_def._obj)
1578                                         field_value2 = []
1579                                         for i in range(len(field_value)):
1580                                                 if not obj.search(cr, uid, [('id', '=',
1581                                                         field_value[i])]):
1582                                                         continue
1583                                                 field_value2.append(field_value[i])
1584                                         field_value = field_value2
1585                                 if fld_def._type in ('one2many'):
1586                                         obj = self.pool.get(fld_def._obj)
1587                                         field_value2 = []
1588                                         for i in range(len(field_value)):
1589                                                 field_value2.append({})
1590                                                 for field2 in field_value[i]:
1591                                                         if obj._columns[field2]._type in ('many2one', 'one2one'):
1592                                                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1593                                                                 if not obj2.search(cr, uid,
1594                                                                                 [('id', '=', field_value[i][field2])]):
1595                                                                         continue
1596                                                         # TODO add test for many2many and one2many
1597                                                         field_value2[i][field2] = field_value[i][field2]
1598                                         field_value = field_value2
1599                                 value[field] = field_value
1600                 return value
1601
1602
1603         #
1604         # Update objects that uses this one to update their _inherits fields
1605         #
1606         def _inherits_reload_src(self):
1607                 for obj in self.pool.obj_pool.values():
1608                         if self._name in obj._inherits:
1609                                 obj._inherits_reload()
1610
1611         def _inherits_reload(self):
1612                 res = {}
1613                 for table in self._inherits:
1614                         res.update(self.pool.get(table)._inherit_fields)
1615                         for col in self.pool.get(table)._columns.keys():
1616                                 res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col])
1617                         for col in self.pool.get(table)._inherit_fields.keys():
1618                                 res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
1619                 self._inherit_fields=res
1620                 self._inherits_reload_src()
1621
1622         def fields_get(self, cr, user, fields=None, context=None):
1623                 read_access= self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False)
1624                 return super(orm, self).fields_get(cr, user, fields, context, read_access)
1625
1626         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1627                 if not context:
1628                         context={}
1629                 self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
1630                 if not fields:
1631                         fields = self._columns.keys() + self._inherit_fields.keys()
1632                 select = ids
1633                 if isinstance(ids, (int, long)):
1634                         select = [ids]
1635                 result =  self._read_flat(cr, user, select, fields, context, load)
1636                 for r in result:
1637                         for key,v in r.items():
1638                                 if v == None:
1639                                         r[key]=False
1640                 if isinstance(ids, (int, long)):
1641                         return result[0]
1642                 return result
1643
1644         def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
1645                 if not context:
1646                         context={}
1647                 if not ids:
1648                         return []
1649
1650                 if fields==None:
1651                         fields = self._columns.keys()
1652
1653                 # construct a clause for the rules :
1654                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1655
1656                 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
1657                 fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
1658
1659                 res = []
1660                 if len(fields_pre) :
1661                         fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
1662                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1663                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1664                                 if d1:
1665                                         cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \
1666                                                         (','.join(fields_pre2 + ['id']), self._table,
1667                                                                 ','.join([str(x) for x in sub_ids]), d1,
1668                                                                 self._order),d2)
1669                                         if not cr.rowcount == len({}.fromkeys(sub_ids)):
1670                                                 raise except_orm(_('AccessError'),
1671                                                                 _('You try to bypass an access rule (Document type: %s).') % self._description)
1672                                 else:
1673                                         cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \
1674                                                         (','.join(fields_pre2 + ['id']), self._table,
1675                                                                 ','.join([str(x) for x in sub_ids]),
1676                                                                 self._order))
1677                                 res.extend(cr.dictfetchall())
1678                 else:
1679                         res = map(lambda x: {'id': x}, ids)
1680
1681                 for f in fields_pre:
1682                         if self._columns[f].translate:
1683                                 ids = map(lambda x: x['id'], res)
1684                                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
1685                                 for r in res:
1686                                         r[f] = res_trans.get(r['id'], False) or r[f]
1687
1688                 for table in self._inherits:
1689                         col = self._inherits[table]
1690                         cols = intersect(self._inherit_fields.keys(), fields)
1691                         if not cols:
1692                                 continue
1693                         res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
1694
1695                         res3 = {}
1696                         for r in res2:
1697                                 res3[r['id']] = r
1698                                 del r['id']
1699
1700                         for record in res:
1701                                 record.update(res3[record[col]])
1702                                 if col not in fields:
1703                                         del record[col]
1704
1705                 # all fields which need to be post-processed by a simple function (symbol_get)
1706                 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
1707                 if fields_post:
1708                         # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
1709                         # to get the _symbol_get in each occurence
1710                         for r in res:
1711                                 for f in fields_post:
1712                                         r[f] = self.columns[f]._symbol_get(r[f])
1713                 ids = map(lambda x: x['id'], res)
1714
1715                 # all non inherited fields for which the attribute whose name is in load is False
1716                 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
1717                 for f in fields_post:
1718                         # get the value of that field for all records/ids
1719                         res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
1720                         for record in res:
1721                                 record[f] = res2[record['id']]
1722
1723                 readonly = None
1724                 for vals in res:
1725                         for field in vals.copy():
1726                                 fobj = None
1727                                 if field in self._columns:
1728                                         fobj = self._columns[field]
1729
1730                                 if not fobj:
1731                                         continue
1732                                 groups = fobj.read
1733                                 if groups:
1734                                         edit = False
1735                                         for group in groups:
1736                                                 module = group.split(".")[0]
1737                                                 grp = group.split(".")[1]
1738                                                 cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \
1739                                                                    (grp, module, 'res.groups', user))
1740                                                 readonly = cr.fetchall()
1741                                                 if readonly[0][0] >= 1:
1742                                                         edit = True
1743                                                         break
1744                                                 elif readonly[0][0] == 0:
1745                                                         edit = False
1746                                                 else:
1747                                                         edit = False
1748
1749                                         if not edit:
1750                                                 if type(vals[field]) == type([]):
1751                                                         vals[field] = []
1752                                                 elif type(vals[field]) == type(0.0):
1753                                                         vals[field] = 0
1754                                                 elif type(vals[field]) == type(''):
1755                                                         vals[field] = '=No Permission='
1756                                                 else:
1757                                                         vals[field] = False
1758                 return res
1759
1760         def perm_read(self, cr, user, ids, context=None, details=True):
1761                 if not context:
1762                         context={}
1763                 if not ids:
1764                         return []
1765                 fields = ''
1766                 if self._log_access:
1767                         fields =', u.create_uid, u.create_date, u.write_uid, u.write_date'
1768                 if isinstance(ids, (int, long)):
1769                         ids_str = str(ids)
1770                 else:
1771                         ids_str = string.join(map(lambda x:str(x), ids),',')
1772                 cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
1773                 res = cr.dictfetchall()
1774                 for r in res:
1775                         for key in r:
1776                                 r[key] = r[key] or False
1777                                 if key in ('write_uid','create_uid','uid') and details:
1778                                         if r[key]:
1779                                                 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
1780                 if isinstance(ids, (int, long)):
1781                         return res[ids]
1782                 return res
1783
1784         def unlink(self, cr, uid, ids, context=None):
1785                 if not context:
1786                         context={}
1787                 if not ids:
1788                         return True
1789                 if isinstance(ids, (int, long)):
1790                         ids = [ids]
1791
1792                 fn_list = []
1793                 if self._name in self.pool._store_function.keys():
1794                         list_store = self.pool._store_function[self._name]
1795                         fn_data = ()
1796                         id_change = []
1797                         for tuple_fn in list_store:
1798                                 for id in ids:
1799                                         id_change.append(self._store_get_ids(cr, uid, id,tuple_fn, context)[0])
1800                                 fn_data = id_change,tuple_fn
1801                                 fn_list.append(fn_data)
1802
1803                 delta= context.get('read_delta',False)
1804                 if delta and self._log_access:
1805                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1806                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1807                                 cr.execute("select  (now()  - min(write_date)) <= '%s'::interval " \
1808                                                 "from \"%s\" where id in (%s)" %
1809                                                 (delta, self._table, ",".join(map(str, sub_ids))) )
1810                         res= cr.fetchone()
1811                         if res and res[0]:
1812                                 raise except_orm(_('ConcurrencyException'),
1813                                                 _('This record was modified in the meanwhile'))
1814
1815                 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink')
1816
1817                 wf_service = netsvc.LocalService("workflow")
1818                 for id in ids:
1819                         wf_service.trg_delete(uid, self._name, id, cr)
1820
1821                 #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
1822                 #res = cr.dictfetchall()
1823                 #for key in self._inherits:
1824                 #       ids2 = [x[self._inherits[key]] for x in res]
1825                 #       self.pool.get(key).unlink(cr, uid, ids2)
1826
1827                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name)
1828                 if d1:
1829                         d1 = ' AND '+d1
1830
1831                 for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1832                         sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1833                         str_d = string.join(('%d',)*len(sub_ids),',')
1834                         if d1:
1835                                 cr.execute('SELECT id FROM "'+self._table+'" ' \
1836                                                 'WHERE id IN ('+str_d+')'+d1, sub_ids+d2)
1837                                 if not cr.rowcount == len({}.fromkeys(ids)):
1838                                         raise except_orm(_('AccessError'),
1839                                                         _('You try to bypass an access rule (Document type: %s).') % \
1840                                                                         self._description)
1841
1842                         if d1:
1843                                 cr.execute('delete from "'+self._table+'" ' \
1844                                                 'where id in ('+str_d+')'+d1, sub_ids+d2)
1845                         else:
1846                                 cr.execute('delete from "'+self._table+'" ' \
1847                                                 'where id in ('+str_d+')', sub_ids)
1848                 if fn_list:
1849                         for ids,tuple_fn in fn_list:
1850                                 self._store_set_values(cr, uid, ids,tuple_fn,id_change, context)
1851
1852                 return True
1853
1854         #
1855         # TODO: Validate
1856         #
1857         def write(self, cr, user, ids, vals, context=None):
1858                 readonly = None
1859                 for field in vals.copy():
1860                         fobj = None
1861                         if field in self._columns:
1862                                 fobj = self._columns[field]
1863                         else:
1864                                 fobj = self._inherit_fields[field][2]
1865                         if not fobj:
1866                                 continue
1867                         groups = fobj.write
1868
1869                         if groups:
1870                                 edit = False
1871                                 for group in groups:
1872                                         module = group.split(".")[0]
1873                                         grp = group.split(".")[1]
1874                                         cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \
1875                                                            (grp, module, 'res.groups', user))
1876                                         readonly = cr.fetchall()
1877                                         if readonly[0][0] >= 1:
1878                                                 edit = True
1879                                                 break
1880                                         elif readonly[0][0] == 0:
1881                                                 edit = False
1882                                         else:
1883                                                 edit = False
1884
1885                                 if not edit:
1886                                         vals.pop(field)
1887
1888                 if not context:
1889                         context={}
1890                 if not ids:
1891                         return True
1892                 if isinstance(ids, (int, long)):
1893                         ids = [ids]
1894                 delta= context.get('read_delta',False)
1895                 if delta and self._log_access:
1896                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1897                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1898                                 cr.execute("select  (now()  - min(write_date)) <= '%s'::interval " \
1899                                                 "from %s where id in (%s)" %
1900                                                 (delta,self._table, ",".join(map(str, sub_ids))))
1901                                 res= cr.fetchone()
1902                                 if res and res[0]:
1903                                         for field in vals:
1904                                                 if field in self._columns and self._columns[field]._classic_write:
1905                                                         raise except_orm(_('ConcurrencyException'),
1906                                                                         _('This record was modified in the meanwhile'))
1907
1908                 self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
1909
1910                 #for v in self._inherits.values():
1911                 #       assert v not in vals, (v, vals)
1912                 upd0=[]
1913                 upd1=[]
1914                 upd_todo=[]
1915                 updend=[]
1916                 direct = []
1917                 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
1918                 for field in vals:
1919                         if field in self._columns:
1920                                 if self._columns[field]._classic_write:
1921                                         if (not totranslate) or not self._columns[field].translate:
1922                                                 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
1923                                                 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
1924                                         direct.append(field)
1925                                 else:
1926                                         upd_todo.append(field)
1927                         else:
1928                                 updend.append(field)
1929                         if field in self._columns \
1930                                         and hasattr(self._columns[field], 'selection') \
1931                                         and vals[field]:
1932                                 if self._columns[field]._type == 'reference':
1933                                         val = vals[field].split(',')[0]
1934                                 else:
1935                                         val = vals[field]
1936                                 if isinstance(self._columns[field].selection, (tuple, list)):
1937                                         if val not in dict(self._columns[field].selection):
1938                                                 raise except_orm(_('ValidateError'),
1939                                                 _('The value "%s" for the field "%s" is not in the selection') \
1940                                                                 % (vals[field], field))
1941                                 else:
1942                                         if val not in dict(self._columns[field].selection(
1943                                                 self, cr, user, context=context)):
1944                                                 raise except_orm(_('ValidateError'),
1945                                                 _('The value "%s" for the field "%s" is not in the selection') \
1946                                                                 % (vals[field], field))
1947
1948                 if self._log_access:
1949                         upd0.append('write_uid=%d')
1950                         upd0.append('write_date=now()')
1951                         upd1.append(user)
1952
1953                 if len(upd0):
1954
1955                         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1956                         if d1:
1957                                 d1 = ' and '+d1
1958
1959                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1960                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1961                                 ids_str = string.join(map(str, sub_ids),',')
1962                                 if d1:
1963                                         cr.execute('SELECT id FROM "'+self._table+'" ' \
1964                                                         'WHERE id IN ('+ids_str+')'+d1, d2)
1965                                         if not cr.rowcount == len({}.fromkeys(sub_ids)):
1966                                                 raise except_orm(_('AccessError'),
1967                                                                 _('You try to bypass an access rule (Document type: %s).') % \
1968                                                                                 self._description)
1969                                 else:
1970                                         cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')')
1971                                         if not cr.rowcount == len({}.fromkeys(sub_ids)):
1972                                                 raise except_orm(_('AccessError'),
1973                                                                 _('You try to write on an record that doesn\'t exist ' \
1974                                                                                 '(Document type: %s).') % self._description)
1975                                 if d1:
1976                                         cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \
1977                                                         'where id in ('+ids_str+')'+d1, upd1+ d2)
1978                                 else:
1979                                         cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \
1980                                                         'where id in ('+ids_str+')', upd1)
1981
1982                         if totranslate:
1983                                 for f in direct:
1984                                         if self._columns[f].translate:
1985                                                 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model',  context['lang'], ids,vals[f])
1986
1987                 # call the 'set' method of fields which are not classic_write
1988                 upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
1989                 for field in upd_todo:
1990                         for id in ids:
1991                                 self._columns[field].set(cr, self, id, field, vals[field], user, context=context)
1992
1993                 for table in self._inherits:
1994                         col = self._inherits[table]
1995                         nids = []
1996                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1997                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1998                                 ids_str = string.join(map(str, sub_ids),',')
1999                                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2000                                                 'where id in ('+ids_str+')', upd1)
2001                                 nids.extend([x[0] for x in cr.fetchall()])
2002
2003                         v = {}
2004                         for val in updend:
2005                                 if self._inherit_fields[val][0]==table:
2006                                         v[val]=vals[val]
2007                         self.pool.get(table).write(cr, user, nids, v, context)
2008
2009                 self._validate(cr, user, ids)
2010
2011                 if context.has_key('read_delta'):
2012                         del context['read_delta']
2013
2014                 wf_service = netsvc.LocalService("workflow")
2015                 for id in ids:
2016                         wf_service.trg_write(user, self._name, id, cr)
2017                 self._update_function_stored(cr, user, ids, context=context)
2018
2019                 if self._name in self.pool._store_function.keys():
2020                         list_store = self.pool._store_function[self._name]
2021                         for tuple_fn in list_store:
2022                                 flag = False
2023                                 if not tuple_fn[3]:
2024                                         flag = True
2025                                 for field in tuple_fn[3]:
2026                                         if field in vals.keys():
2027                                                 flag = True
2028                                                 break
2029                                 if flag:
2030                                         id_change = self._store_get_ids(cr, user, ids[0],tuple_fn, context)
2031                                         self._store_set_values(cr, user, ids[0],tuple_fn,id_change, context)
2032
2033                 return True
2034
2035         #
2036         # TODO: Should set perm to user.xxx
2037         #
2038         def create(self, cr, user, vals, context=None):
2039                 """ create(cr, user, vals, context) -> int
2040                 cr = database cursor
2041                 user = user id
2042                 vals = dictionary of the form {'field_name':field_value, ...}
2043                 """
2044                 if not context:
2045                         context={}
2046                 self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
2047
2048                 default = []
2049
2050                 avoid_table = []
2051                 for (t,c) in self._inherits.items():
2052                         if c in vals:
2053                                 avoid_table.append(t)
2054                 for f in self._columns.keys(): # + self._inherit_fields.keys():
2055                         if not f in vals:
2056                                 default.append(f)
2057                 for f in self._inherit_fields.keys():
2058                         if (not f in vals) and (not self._inherit_fields[f][0] in avoid_table):
2059                                 default.append(f)
2060
2061                 if len(default):
2062                         vals.update(self.default_get(cr, user, default, context))
2063
2064                 tocreate = {}
2065                 for v in self._inherits:
2066                         if self._inherits[v] not in vals:
2067                                 tocreate[v] = {}
2068
2069                 (upd0, upd1, upd2) = ('', '', [])
2070                 upd_todo = []
2071
2072                 for v in vals.keys():
2073                         if v in self._inherit_fields:
2074                                 (table,col,col_detail) = self._inherit_fields[v]
2075                                 tocreate[table][v] = vals[v]
2076                                 del vals[v]
2077
2078                 cr.execute("SELECT nextval('"+self._sequence+"')")
2079                 id_new = cr.fetchone()[0]
2080                 for table in tocreate:
2081                         id = self.pool.get(table).create(cr, user, tocreate[table])
2082                         upd0 += ','+self._inherits[table]
2083                         upd1 += ',%d'
2084                         upd2.append(id)
2085
2086                 for field in vals:
2087                         if self._columns[field]._classic_write:
2088                                 upd0=upd0+',"'+field+'"'
2089                                 upd1=upd1+','+self._columns[field]._symbol_set[0]
2090                                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
2091                         else:
2092                                 upd_todo.append(field)
2093                         if field in self._columns \
2094                                         and hasattr(self._columns[field], 'selection') \
2095                                         and vals[field]:
2096                                 if self._columns[field]._type == 'reference':
2097                                         val = vals[field].split(',')[0]
2098                                 else:
2099                                         val = vals[field]
2100                                 if isinstance(self._columns[field].selection, (tuple, list)):
2101                                         if val not in dict(self._columns[field].selection):
2102                                                 raise except_orm(_('ValidateError'),
2103                                                 _('The value "%s" for the field "%s" is not in the selection') \
2104                                                                 % (vals[field], field))
2105                                 else:
2106                                         if val not in dict(self._columns[field].selection(
2107                                                 self, cr, user, context=context)):
2108                                                 raise except_orm(_('ValidateError'),
2109                                                 _('The value "%s" for the field "%s" is not in the selection') \
2110                                                                 % (vals[field], field))
2111                 if self._log_access:
2112                         upd0 += ',create_uid,create_date'
2113                         upd1 += ',%d,now()'
2114                         upd2.append(user)
2115                 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
2116                 upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
2117                 for field in upd_todo:
2118                         self._columns[field].set(cr, self, id_new, field, vals[field], user, context)
2119
2120                 self._validate(cr, user, [id_new])
2121
2122                 wf_service = netsvc.LocalService("workflow")
2123                 wf_service.trg_create(user, self._name, id_new, cr)
2124                 self._update_function_stored(cr, user, [id_new], context=context)
2125                 if self._name in self.pool._store_function.keys():
2126                         list_store = self.pool._store_function[self._name]
2127                         for tuple_fn in list_store:
2128                                 id_change = self._store_get_ids(cr, user, id_new,tuple_fn, context)
2129                                 self._store_set_values(cr, user, id_new,tuple_fn,id_change, context)
2130
2131                 return id_new
2132
2133         def _store_get_ids(self,cr, uid, ids,tuple_fn, context):
2134                 parent_id=getattr(self.pool.get(tuple_fn[0]),tuple_fn[4].func_name)(cr,uid,[ids])
2135                 return parent_id
2136
2137         def _store_set_values(self,cr,uid,ids,tuple_fn,parent_id,context):
2138                 name = tuple_fn[1]
2139                 table = tuple_fn[0]
2140                 args={}
2141                 vals_tot=getattr(self.pool.get(table),tuple_fn[2])(cr, uid, parent_id ,name, args, context)
2142                 write_dict = {}
2143                 for id in vals_tot.keys():
2144                         write_dict[name]=vals_tot[id]
2145                         self.pool.get(table).write(cr,uid,[id],write_dict)
2146                 return True
2147
2148         def _update_function_stored(self, cr, user, ids, context=None):
2149                 if not context:
2150                         context = {}
2151                 f = filter(lambda a: isinstance(self._columns[a], fields.function) \
2152                                 and self._columns[a].store, self._columns)
2153                 if f:
2154                         result=self.read(cr, user, ids, fields=f, context=context)
2155                         for res in result:
2156                                 upd0=[]
2157                                 upd1=[]
2158                                 for field in res:
2159                                         if field not in f:
2160                                                 continue
2161                                         value = res[field]
2162                                         if self._columns[field]._type in ('many2one', 'one2one'):
2163                                                 value = res[field][0]
2164                                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
2165                                         upd1.append(self._columns[field]._symbol_set[1](value))
2166                                 upd1.append(res['id'])
2167                                 cr.execute('update "' + self._table + '" set ' + \
2168                                                 string.join(upd0, ',') + ' where id = %d', upd1)
2169                 return True
2170
2171         #
2172         # TODO: Validate
2173         #
2174         def perm_write(self, cr, user, ids, fields, context=None):
2175                 raise _('This method does not exist anymore')
2176
2177         # TODO: ameliorer avec NULL
2178         def _where_calc(self, cr, user, args, active_test=True, context=None):
2179                 if not context:
2180                         context={}
2181                 args = args[:]
2182                 # if the object has a field named 'active', filter out all inactive
2183                 # records unless they were explicitely asked for
2184                 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2185                         i = 0
2186                         active_found = False
2187                         while i<len(args):
2188                                 if args[i][0]=='active':
2189                                         active_found = True
2190                                 i += 1
2191                         if not active_found:
2192                                 args.append(('active', '=', 1))
2193
2194                 i = 0
2195                 tables=['"'+self._table+'"']
2196                 joins=[]
2197                 while i<len(args):
2198                         table=self
2199                         assert args[i][1] in ('like','!=','ilike','=like', 'not like', 'not ilike', 'not in','inselect','child_of','in','=','<>','<','>','>=','<='), _('Error ! Bad clause operand "%s".') % (args[i][1],)
2200                         if args[i][1] == 'inselect':
2201                                 raise except_orm(_('ValidateError'),
2202                                                 _("The clause 'inselect' can not be used outside the orm!"))
2203                         if args[i][0] in self._inherit_fields:
2204                                 table=self.pool.get(self._inherit_fields[args[i][0]][0])
2205                                 if ('"'+table._table+'"' not in tables):
2206                                         tables.append('"'+table._table+'"')
2207                                         joins.append(('id', 'join', '%s.%s' % (self._table, self._inherits[table._name]), table))
2208                         fargs = args[i][0].split('.', 1)
2209                         field = table._columns.get(fargs[0],False)
2210                         if not field:
2211                                 if args[i][0] == 'id' and args[i][1] == 'child_of':
2212                                         ids2 = args[i][2]
2213                                         def _rec_get(ids, table, parent):
2214                                                 if not ids:
2215                                                         return []
2216                                                 ids2 = table.search(cr, user, [(parent, 'in', ids)], context=context)
2217                                                 return ids + _rec_get(ids2, table, parent)
2218                                         args[i] = (args[i][0], 'in', ids2 + _rec_get(ids2, table, table._parent_name), table)
2219                                 i+=1
2220                                 continue
2221                         if len(fargs) > 1:
2222                                 if field._type == 'many2one':
2223                                         args[i] = (fargs[0], 'in', self.pool.get(field._obj).search(cr, user, [(fargs[1], args[i][1], args[i][2])], context=context))
2224                                         i+=1
2225                                         continue
2226                                 else:
2227                                         i+=1
2228                                         continue
2229                         if field._properties:
2230                                 arg = [args.pop(i)]
2231                                 j = i
2232                                 while j<len(args):
2233                                         if args[j][0]==arg[0][0]:
2234                                                 arg.append(args.pop(j))
2235                                         else:
2236                                                 j+=1
2237                                 if field._fnct_search:
2238                                         args.extend(field.search(cr, user, self, arg[0][0], arg))
2239                                 if field.store:
2240                                         args.extend(arg)
2241                                 i+=1
2242                         elif field._type=='one2many':
2243                                 field_obj = self.pool.get(field._obj)
2244
2245                                 if isinstance(args[i][2], basestring):
2246                                         # get the ids of the records of the "distant" resource
2247                                         ids2 = [x[0] for x in field_obj.name_search(cr, user, args[i][2], [], args[i][1])]
2248                                 else:
2249                                         ids2 = args[i][2]
2250                                 if not ids2:
2251                                         args[i] = ('id','=','0')
2252                                 else:
2253                                         ids3 = []
2254                                         for j in range((len(ids2) / ID_MAX) + (len(ids2) % ID_MAX)):
2255                                                 sub_ids2 = ids2[ID_MAX * j:ID_MAX * (j + 1)]
2256                                                 if sub_ids2:
2257                                                         cr.execute('SELECT "'+field._fields_id+'" ' \
2258                                                                         'FROM "'+field_obj._table+'" ' \
2259                                                                         'WHERE id in ('+','.join(map(str,sub_ids2))+')')
2260                                                         ids3.extend([x[0] for x in cr.fetchall()])
2261
2262                                         args[i] = ('id', 'in', ids3)
2263                                 i+=1
2264
2265                         elif field._type=='many2many':
2266                                 #FIXME
2267                                 if args[i][1]=='child_of':
2268                                         if isinstance(args[i][2], basestring):
2269                                                 ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
2270                                         else:
2271                                                 ids2 = args[i][2]
2272                                         def _rec_get(ids, table, parent):
2273                                                 if not ids:
2274                                                         return []
2275                                                 ids2 = table.search(cr, user, [(parent, 'in', ids)], context=context)
2276                                                 return ids + _rec_get(ids2, table, parent)
2277                                         def _rec_convert(ids):
2278                                                 if self.pool.get(field._obj)==self:
2279                                                         return ids
2280                                                 if not len(ids): return []
2281                                                 cr.execute('select "'+field._id1+'" from "'+field._rel+'" where "'+field._id2+'" in ('+','.join(map(str,ids))+')')
2282                                                 ids = [x[0] for x in cr.fetchall()]
2283                                                 return ids
2284                                         args[i] = ('id','in',_rec_convert(ids2+_rec_get(ids2, self.pool.get(field._obj), table._parent_name)))
2285                                 else:
2286                                         if isinstance(args[i][2], basestring):
2287                                                 res_ids = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])]
2288                                         else:
2289                                                 res_ids = args[i][2]
2290                                         if not len(res_ids):
2291                                                 args[i] = ('id', 'in', [0])
2292                                         else:
2293                                                 cr.execute('select "'+field._id1+'" from "'+field._rel+'" where "'+field._id2+'" in ('+','.join(map(str, res_ids))+')')
2294                                                 args[i] = ('id', 'in', map(lambda x: x[0], cr.fetchall()))
2295                                 i+=1
2296
2297                         elif field._type=='many2one':
2298                                 if args[i][1]=='child_of':
2299                                         if isinstance(args[i][2], basestring):
2300                                                 ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
2301                                         else:
2302                                                 ids2 = args[i][2]
2303                                         def _rec_get(ids, table, parent):
2304                                                 if not ids:
2305                                                         return []
2306                                                 ids2 = table.search(cr, user, [(parent, 'in', ids)], context=context)
2307                                                 return ids + _rec_get(ids2, table, parent)
2308                                         if field._obj <> table._name:
2309                                                 args[i] = (args[i][0],'in',ids2+_rec_get(ids2, self.pool.get(field._obj), table._parent_name), table)
2310                                         else:
2311                                                 args[i] = ('id','in',ids2+_rec_get(ids2, table, args[i][0]), table)
2312                                 else:
2313                                         if isinstance(args[i][2], basestring):
2314                                                 res_ids = self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])
2315                                                 args[i] = (args[i][0],'in',map(lambda x: x[0], res_ids), table)
2316                                         else:
2317                                                 args[i] += (table,)
2318                                 i+=1
2319                         else:
2320                                 if field.translate:
2321                                         if args[i][1] in ('like', 'ilike', 'not like', 'not ilike'):
2322                                                 args[i] = (args[i][0], args[i][1], '%%%s%%' % args[i][2])
2323                                         query1 = '(SELECT res_id FROM ir_translation ' \
2324                                                         'WHERE name = %s AND lang = %s ' \
2325                                                                 'AND type = %s ' \
2326                                                                 'AND VALUE ' + args[i][1] + ' %s)'
2327                                         query2 = [table._name + ',' + args[i][0],
2328                                                         context.get('lang', False) or 'en_US',
2329                                                         'model',
2330                                                         args[i][2]]
2331                                         query1 += ' UNION '
2332                                         query1 += '(SELECT id FROM "' + table._table + '" ' \
2333                                                         'WHERE "' + args[i][0] + '" ' + args[i][1] + ' %s)'
2334                                         query2 += [args[i][2]]
2335                                         args[i] = ('id', 'inselect', (query1, query2), table)
2336                                 else:
2337                                         args[i] += (table,)
2338                                 i+=1
2339                 args.extend(joins)
2340
2341                 qu1, qu2 = [], []
2342                 for x in args:
2343                         table=self
2344                         if len(x) > 3:
2345                                 table=x[3]
2346                         if x[1] == 'inselect':
2347                                 qu1.append('(%s.%s in (%s))' % (table._table, x[0], x[2][0]))
2348                                 qu2 += x[2][1]
2349                         elif x[1]=='not in':
2350                                         if len(x[2])>0:
2351                                                 todel = []
2352                                                 for xitem in range(len(x[2])):
2353                                                         if x[2][xitem]==False and isinstance(x[2][xitem],bool):
2354                                                                 todel.append(xitem)
2355                                                 for xitem in todel[::-1]:
2356                                                         del x[2][xitem]
2357                                                 if x[0]=='id':
2358                                                         qu1.append('(%s.id not in (%s))' % (table._table, ','.join(['%d'] * len(x[2])),))
2359                                                 else:
2360                                                         qu1.append('(%s.%s not in (%s))' % (table._table, x[0], ','.join([table._columns[x[0]]._symbol_set[0]]*len(x[2]))))
2361                                                 if todel:
2362                                                         qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
2363                                                 qu2+=x[2]
2364                                         else:
2365                                                 qu1.append(' (1=0)')
2366                         elif x[1] != 'in':
2367 #FIXME: this replace all (..., '=', False) values with 'is null' and this is
2368 # not what we want for real boolean fields. The problem is, we can't change it
2369 # easily because we use False everywhere instead of None
2370 # NOTE FAB: we can't use None becStay tunes ! ause it is not accepted by XML-RPC, that's why
2371 # boolean (0-1), None -> False
2372 # Ged> boolean fields are not always = 0 or 1
2373                                 if (x[2] is False) and (x[1]=='='):
2374                                         qu1.append(x[0]+' is null')
2375                                 elif (x[2] is False) and (x[1]=='<>' or x[1]=='!='):
2376                                         qu1.append(x[0]+' is not null')
2377                                 else:
2378                                         if x[0]=='id':
2379                                                 if x[1]=='join':
2380                                                         qu1.append('(%s.%s = %s)' % (table._table, x[0], x[2]))
2381                                                 else:
2382                                                         qu1.append('(%s.%s %s %%s)' % (table._table, x[0], x[1]))
2383                                                         qu2.append(x[2])
2384                                         else:
2385                                                 add_null = False
2386                                                 if x[1] in ('like', 'ilike', 'not like', 'not ilike'):
2387                                                         if isinstance(x[2], str):
2388                                                                 str_utf8 = x[2]
2389                                                         elif isinstance(x[2], unicode):
2390                                                                 str_utf8 = x[2].encode('utf-8')
2391                                                         else:
2392                                                                 str_utf8 = str(x[2])
2393                                                         qu2.append('%%%s%%' % str_utf8)
2394                                                         if not str_utf8:
2395                                                                 add_null = True
2396                                                 else:
2397                                                         if x[0] in table._columns:
2398                                                                 qu2.append(table._columns[x[0]]._symbol_set[1](x[2]))
2399                                                 if x[1]=='=like':
2400                                                         x1 = 'like'
2401                                                 else:
2402                                                         x1 = x[1]
2403                                                 if x[0] in table._columns:
2404                                                         if x[1] in ('like', 'ilike', 'not like', 'not ilike'):
2405
2406                                                                 qu1.append('(%s.%s %s %s)' % (table._table,
2407                                                                         x[0], x1, '%s'))
2408                                                         else:
2409                                                                 qu1.append('(%s.%s %s %s)' % (table._table,
2410                                                                         x[0], x1,
2411                                                                         table._columns[x[0]]._symbol_set[0]))
2412                                                 else:
2413                                                         qu1.append('(%s.%s %s \'%s\')' % (table._table, x[0], x1, x[2]))
2414
2415                                                 if add_null:
2416                                                         qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
2417                         elif x[1]=='in':
2418                                 if len(x[2])>0:
2419                                         todel = []
2420                                         for xitem in range(len(x[2])):
2421                                                 if x[2][xitem]==False and isinstance(x[2][xitem],bool):
2422                                                         todel.append(xitem)
2423                                         for xitem in todel[::-1]:
2424                                                 del x[2][xitem]
2425                                         #TODO fix max_stack_depth
2426                                         if x[0]=='id':
2427                                                 qu1.append('(%s.id in (%s))' % (table._table, ','.join(['%d'] * len(x[2])),))
2428                                         else:
2429                                                 qu1.append('(%s.%s in (%s))' % (table._table, x[0], ','.join([table._columns[x[0]]._symbol_set[0]]*len(x[2]))))
2430                                         if todel:
2431                                                 qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
2432                                         qu2+=x[2]
2433                                 else:
2434                                         qu1.append(' (1=0)')
2435                 return (qu1,qu2,tables)
2436
2437         def _check_qorder(self, word):
2438                 if not regex_order.match(word):
2439                         raise except_orm(_('AccessError'), _('Bad query.'))
2440                 return True
2441
2442         def search(self, cr, user, args, offset=0, limit=None, order=None,
2443                         context=None, count=False):
2444                 if not context:
2445                         context = {}
2446                 # compute the where, order by, limit and offset clauses
2447                 (qu1,qu2,tables) = self._where_calc(cr, user, args, context=context)
2448
2449                 if len(qu1):
2450                         qu1 = ' where '+string.join(qu1,' and ')
2451                 else:
2452                         qu1 = ''
2453
2454                 if order:
2455                         self._check_qorder(order)
2456                 order_by = order or self._order
2457
2458                 limit_str = limit and ' limit %d' % limit or ''
2459                 offset_str = offset and ' offset %d' % offset or ''
2460
2461
2462                 # construct a clause for the rules :
2463                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2464                 if d1:
2465                         qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
2466                         qu2 += d2
2467
2468                 if count:
2469                         cr.execute('select count(%s.id) from ' % self._table +
2470                                         ','.join(tables) +qu1 + limit_str + offset_str, qu2)
2471                         res = cr.fetchall()
2472                         return res[0][0]
2473                 # execute the "main" query to fetch the ids we were searching for
2474                 cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
2475                 res = cr.fetchall()
2476                 return [x[0] for x in res]
2477
2478         # returns the different values ever entered for one field
2479         # this is used, for example, in the client when the user hits enter on
2480         # a char field
2481         def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
2482                 if not args:
2483                         args=[]
2484                 if field in self._inherit_fields:
2485                         return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid,field,value,args,offset,limit)
2486                 else:
2487                         return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
2488
2489         def name_get(self, cr, user, ids, context=None):
2490                 if not context:
2491                         context={}
2492                 if not ids:
2493                         return []
2494                 if isinstance(ids, (int, long)):
2495                         ids = [ids]
2496                 return [(r['id'], str(r[self._rec_name])) for r in self.read(cr, user, ids,
2497                         [self._rec_name], context, load='_classic_write')]
2498
2499         def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
2500                 if not args:
2501                         args=[]
2502                 if not context:
2503                         context={}
2504                 args=args[:]
2505                 if name:
2506                         args += [(self._rec_name,operator,name)]
2507                 ids = self.search(cr, user, args, limit=limit, context=context)
2508                 res = self.name_get(cr, user, ids, context)
2509                 return res
2510
2511         def copy(self, cr, uid, id, default=None, context=None):
2512                 if not context:
2513                         context={}
2514                 if not default:
2515                         default = {}
2516                 if 'state' not in default:
2517                         if 'state' in self._defaults:
2518                                 default['state'] = self._defaults['state'](self, cr, uid, context)
2519                 data = self.read(cr, uid, [id], context=context)[0]
2520                 fields = self.fields_get(cr, uid)
2521                 for f in fields:
2522                         ftype = fields[f]['type']
2523
2524                         if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
2525                                 del data[f]
2526
2527                         if f in default:
2528                                 data[f] = default[f]
2529                         elif ftype == 'function':
2530                                 del data[f]
2531                         elif ftype == 'many2one':
2532                                 try:
2533                                         data[f] = data[f] and data[f][0]
2534                                 except:
2535                                         pass
2536                         elif ftype in ('one2many', 'one2one'):
2537                                 res = []
2538                                 rel = self.pool.get(fields[f]['relation'])
2539                                 for rel_id in data[f]:
2540                                         # the lines are first duplicated using the wrong (old)
2541                                         # parent but then are reassigned to the correct one thanks
2542                                         # to the (4, ...)
2543                                         res.append((4, rel.copy(cr, uid, rel_id, context=context)))
2544                                 data[f] = res
2545                         elif ftype == 'many2many':
2546                                 data[f] = [(6, 0, data[f])]
2547                 del data['id']
2548                 for v in self._inherits:
2549                         del data[self._inherits[v]]
2550                 return self.create(cr, uid, data)
2551
2552         def read_string(self, cr, uid, id, langs, fields=None, context=None):
2553                 if not context:
2554                         context={}
2555                 res = {}
2556                 res2 = {}
2557                 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read')
2558                 if not fields:
2559                         fields = self._columns.keys() + self._inherit_fields.keys()
2560                 for lang in langs:
2561                         res[lang] = {'code': lang}
2562                         for f in fields:
2563                                 if f in self._columns:
2564                                         res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
2565                                         if res_trans:
2566                                                 res[lang][f]=res_trans
2567                                         else:
2568                                                 res[lang][f]=self._columns[f].string
2569                 for table in self._inherits:
2570                         cols = intersect(self._inherit_fields.keys(), fields)
2571                         res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
2572                 for lang in res2:
2573                         if lang in res:
2574                                 res[lang]={'code': lang}
2575                         for f in res2[lang]:
2576                                 res[lang][f]=res2[lang][f]
2577                 return res
2578
2579         def write_string(self, cr, uid, id, langs, vals, context=None):
2580                 if not context:
2581                         context={}
2582                 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write')
2583                 for lang in langs:
2584                         for field in vals:
2585                                 if field in self._columns:
2586                                         self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field])
2587                 for table in self._inherits:
2588                         cols = intersect(self._inherit_fields.keys(), vals)
2589                         if cols:
2590                                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2591                 return True
2592
2593         def check_recursion(self, cr, uid, ids, parent=None):
2594                 if not parent:
2595                         parent = self._parent_name
2596                 ids_parent = ids[:]
2597                 while len(ids_parent):
2598                         ids_parent2 = []
2599                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
2600                                 sub_ids_parent = ids_parent[ID_MAX * i:ID_MAX * (i + 1)]
2601                                 cr.execute('SELECT distinct "'+parent+'"'+
2602                                         ' FROM "'+self._table+'" ' \
2603                                         'WHERE id in ('+','.join(map(str, sub_ids_parent))+')')
2604                                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
2605                         ids_parent = ids_parent2
2606                         for i in ids_parent:
2607                                 if i in ids:
2608                                         return False
2609                 return True
2610
2611 # vim:noexpandtab:ts=4