Small change, According to the E-Tiny Requirenments
[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 '+str(id)+ ', expected an integer.'
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 cols.' % (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):
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                 read_access= model_access_obj.check(cr, user, self._name, 'write',
667                                 raise_exception=False)
668                 for f in self._columns.keys():
669                         res[f] = {'type': self._columns[f]._type}
670                         for arg in ('string', 'readonly', 'states', 'size', 'required',
671                                         'change_default', 'translate', 'help', 'select'):
672                                 if getattr(self._columns[f], arg):
673                                         res[f][arg] = getattr(self._columns[f], arg)
674                         if not read_access:
675                                 res[f]['readonly']= True
676                                 res[f]['states'] = {}
677                         for arg in ('digits', 'invisible'):
678                                 if hasattr(self._columns[f], arg) \
679                                                 and getattr(self._columns[f], arg):
680                                         res[f][arg] = getattr(self._columns[f], arg)
681
682                         # translate the field label
683                         res_trans = translation_obj._get_source(cr, user,
684                                         self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
685                         if res_trans:
686                                 res[f]['string'] = res_trans
687                         help_trans = translation_obj._get_source(cr, user,
688                                         self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
689                         if help_trans:
690                                 res[f]['help'] = help_trans
691
692                         if hasattr(self._columns[f], 'selection'):
693                                 if isinstance(self._columns[f].selection, (tuple, list)):
694                                         sel = self._columns[f].selection
695                                         # translate each selection option
696                                         sel2 = []
697                                         for (key,val) in sel:
698                                                 val2 = translation_obj._get_source(cr, user,
699                                                                 self._name + ',' + f, 'selection',
700                                                                 context.get('lang', False) or 'en_US', val)
701                                                 sel2.append((key, val2 or val))
702                                         sel = sel2
703                                         res[f]['selection'] = sel
704                                 else:
705                                         # call the 'dynamic selection' function
706                                         res[f]['selection'] = self._columns[f].selection(self, cr,
707                                                         user, context)
708                         if res[f]['type'] in ('one2many', 'many2many',
709                                         'many2one', 'one2one'):
710                                 res[f]['relation'] = self._columns[f]._obj
711                                 res[f]['domain'] = self._columns[f]._domain
712                                 res[f]['context'] = self._columns[f]._context
713
714                 if fields:
715                         # filter out fields which aren't in the fields list
716                         for r in res.keys():
717                                 if r not in fields:
718                                         del res[r]
719                 return res
720
721         #
722         # Overload this method if you need a window title which depends on the context
723         #
724         def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
725                 return False
726
727         def __view_look_dom(self, cr, user, node, context=None):
728                 if not context:
729                         context={}
730                 result = False
731                 fields = {}
732                 childs = True
733
734                 if node.nodeType==node.ELEMENT_NODE and node.localName=='field':
735                         if node.hasAttribute('name'):
736                                 attrs = {}
737                                 try:
738                                         if node.getAttribute('name') in self._columns:
739                                                 relation = self._columns[node.getAttribute('name')]._obj
740                                         else:
741                                                 relation = self._inherit_fields[node.getAttribute('name')][2]._obj
742                                 except:
743                                         relation = False
744
745                                 if relation:
746                                         childs = False
747                                         views = {}
748                                         for f in node.childNodes:
749                                                 if f.nodeType==f.ELEMENT_NODE and f.localName in ('form','tree','graph'):
750                                                         node.removeChild(f)
751                                                         xarch,xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, context)
752                                                         views[str(f.localName)] = {
753                                                                 'arch': xarch,
754                                                                 'fields': xfields
755                                                         }
756                                         attrs = {'views': views}
757                                 fields[node.getAttribute('name')] = attrs
758
759                 elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form','tree'):
760                         result = self.view_header_get(cr, user, False, node.localName, context)
761                         if result:
762                                 node.setAttribute('string', result.decode('utf-8'))
763                 if node.nodeType==node.ELEMENT_NODE and node.hasAttribute('groups'):
764                         groups = None
765                         group_str = node.getAttribute('groups')
766                         if ',' in group_str:
767                                 groups = group_str.split(',');
768                                 readonly = False
769                                 access_pool = self.pool.get('ir.model.access')
770                                 for group in groups:
771                                         readonly = readonly and access_pool.check_groups(cr, user, group)
772
773                                 if readonly:
774                                         parent = node.parentNode
775                                         parent.removeChild(node)
776
777                         else:
778                                 if not self.pool.get('ir.model.access').check_groups(cr, user, group_str):
779                                         parent = node.parentNode
780                                         parent.removeChild(node)
781
782                 if node.nodeType == node.ELEMENT_NODE:
783                         # translate view
784                         if ('lang' in context) and not result:
785                                 if node.hasAttribute('string') and node.getAttribute('string'):
786                                         trans = tools.translate(cr, user, self._name, 'view', context['lang'], node.getAttribute('string').encode('utf8'))
787                                         if trans:
788                                                 node.setAttribute('string', trans.decode('utf8'))
789                                 if node.hasAttribute('sum') and node.getAttribute('sum'):
790                                         trans = tools.translate(cr, user, self._name, 'view', context['lang'], node.getAttribute('sum').encode('utf8'))
791                                         if trans:
792                                                 node.setAttribute('sum', trans.decode('utf8'))
793                         #
794                         # Add view for properties !
795                         #
796                         if node.localName=='properties':
797                                 parent = node.parentNode
798                                 doc = node.ownerDocument
799                                 models = map(lambda x: "'"+x+"'", [self._name] + self._inherits.keys())
800                                 cr.execute('select id,name,group_name from ir_model_fields where model in ('+','.join(models)+') and view_load order by group_name, id')
801                                 oldgroup = None
802                                 res = cr.fetchall()
803                                 for id, fname, gname in res:
804                                         if oldgroup != gname:
805                                                 child = doc.createElement('separator')
806                                                 child.setAttribute('string', gname.decode('utf-8'))
807                                                 child.setAttribute('colspan', "4")
808                                                 oldgroup = gname
809                                                 parent.insertBefore(child, node)
810
811                                         child = doc.createElement('field')
812                                         child.setAttribute('name', fname.decode('utf-8'))
813                                         parent.insertBefore(child, node)
814                                 parent.removeChild(node)
815
816                 if childs:
817                         for f in node.childNodes:
818                                 fields.update(self.__view_look_dom(cr, user, f,context))
819                 return fields
820
821         def __view_look_dom_arch(self, cr, user, node, context=None):
822                 if not context:
823                         context={}
824                 fields_def = self.__view_look_dom(cr, user, node, context=context)
825
826                 buttons = xpath.Evaluate('//button', node)
827                 if buttons:
828                         for button in buttons:
829                                 if button.getAttribute('type') == 'object':
830                                         continue
831
832                                 ok = True
833
834                                 serv = netsvc.LocalService('object_proxy')
835                                 user_roles = serv.execute_cr(cr, user, 'res.users', 'read', [user], ['roles_id'])[0]['roles_id']
836                                 cr.execute("select role_id from wkf_transition where signal='%s'" % button.getAttribute('name'))
837                                 roles = cr.fetchall()
838                                 for role in roles:
839                                         if role[0]:
840                                                 ok = ok and serv.execute_cr(cr, user, 'res.roles', 'check', user_roles, role[0])
841
842                                 if not ok:
843                                         button.setAttribute('readonly', '1')
844                                 else:
845                                         button.setAttribute('readonly', '0')
846
847                 arch = node.toxml(encoding="utf-8").replace('\t', '')
848
849                 fields = self.fields_get(cr, user, fields_def.keys(), context)
850                 for field in fields_def:
851                         fields[field].update(fields_def[field])
852                 return arch, fields
853
854
855         def __get_default_calendar_view(self):
856                 """Generate a default calendar view (For internal use only).
857                 """
858
859                 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
860                             '<calendar string="%s" date_start="%s"') % (self._description, self._date_name)
861
862                 if 'user_id' in self._columns:
863                         arch += ' color="user_id"'
864
865                 elif 'partner_id' in self._columns:
866                         arch += ' color="partner_id"'
867
868                 if 'date_stop' in self._columns:
869                         arch += ' date_stop="date_stop"'
870
871                 elif 'date_end' in self._columns:
872                         arch += ' date_stop="date_end"'
873
874                 elif 'date_delay' in self._columns:
875                         arch += ' date_delay="date_delay"'
876
877                 elif 'planned_hours' in self._columns:
878                         arch += ' date_delay="planned_hours"'
879
880                 arch += ('>\n'
881                                  '  <field name="%s"/>\n'
882                                  '</calendar>') % (self._rec_name)
883
884                 return arch
885
886         #
887         # if view_id, view_type is not required
888         #
889         def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False):
890                 if not context:
891                         context={}
892                 def _inherit_apply(src, inherit):
893                         def _find(node, node2):
894                                 # Check if xpath query or normal inherit (with field matching)
895                                 if node2.nodeType==node2.ELEMENT_NODE and node2.localName=='xpath':
896                                         res = xpath.Evaluate(node2.getAttribute('expr'), node)
897                                         return res and res[0]
898                                 else:
899                                         if node.nodeType==node.ELEMENT_NODE and node.localName==node2.localName:
900                                                 res = True
901                                                 for attr in node2.attributes.keys():
902                                                         if attr=='position':
903                                                                 continue
904                                                         if node.hasAttribute(attr):
905                                                                 if node.getAttribute(attr)==node2.getAttribute(attr):
906                                                                         continue
907                                                         res = False
908                                                 if res:
909                                                         return node
910                                         for child in node.childNodes:
911                                                 res = _find(child, node2)
912                                                 if res: return res
913                                 return None
914
915                         doc_src = dom.minidom.parseString(src)
916                         doc_dest = dom.minidom.parseString(inherit)
917                         toparse = doc_dest.childNodes
918                         while len(toparse):
919                                 node2 = toparse.pop(0)
920                                 if not node2.nodeType==node2.ELEMENT_NODE:
921                                         continue
922                                 if node2.localName=='data':
923                                         toparse += node2.childNodes
924                                         continue
925                                 node = _find(doc_src, node2)
926                                 if node:
927                                         pos = 'inside'
928                                         if node2.hasAttribute('position'):
929                                                 pos = node2.getAttribute('position')
930                                         if pos=='replace':
931                                                 parent = node.parentNode
932                                                 for child in node2.childNodes:
933                                                         if child.nodeType==child.ELEMENT_NODE:
934                                                                 parent.insertBefore(child, node)
935                                                 parent.removeChild(node)
936                                         else:
937                                                 for child in node2.childNodes:
938                                                         if child.nodeType==child.ELEMENT_NODE:
939                                                                 if pos=='inside':
940                                                                         node.appendChild(child)
941                                                                 elif pos=='after':
942                                                                         sib = node.nextSibling
943                                                                         if sib:
944                                                                                 node.parentNode.insertBefore(child, sib)
945                                                                         else:
946                                                                                 node.parentNode.appendChild(child)
947                                                                 elif pos=='before':
948                                                                         node.parentNode.insertBefore(child, node)
949                                                                 else:
950                                                                         raise AttributeError, 'Unknown position in inherited view %s !' % pos
951                                 else:
952                                         attrs = ''.join([
953                                                 ' %s="%s"' % (attr, node2.getAttribute(attr))
954                                                 for attr in node2.attributes.keys()
955                                                 if attr != 'position'
956                                         ])
957                                         tag = "<%s%s>" % (node2.localName, attrs)
958                                         raise AttributeError, "Couldn't find tag '%s' in parent view !" % tag
959                         return doc_src.toxml(encoding="utf-8").replace('\t', '')
960
961                 result = {'type':view_type, 'model':self._name}
962
963                 ok = True
964                 model = True
965                 sql_res = False
966                 base = False
967                 while ok:
968                         sql_res_usr = None
969                         if view_id:
970                                 #check for user specific view and get the view according to that
971                                 where = (model and (" and model='%s'" % (self._name,))) or ''
972                                 cr.execute('select arch,name,field_parent,ref_id,type,inherit_id from ir_ui_view_user where ref_id=%d and user_id=%d'+where, (view_id, user))
973                                 sql_res_usr = cr.fetchone()
974
975                                 if not sql_res_usr:
976                                         where = (model and (" and model='%s'" % (self._name,))) or ''
977                                         cr.execute('select arch,name,field_parent,id,type,inherit_id from ir_ui_view where id=%d'+where, (view_id,))
978                                 else:
979                                         base = True
980                         else:
981                                 cr.execute('select arch,name,field_parent,ref_id,type,inherit_id from ir_ui_view_user where model=%s and type=%s and user_id=%d order by priority', (self._name, view_type, user))
982                                 sql_res_usr = cr.fetchone()
983                                 if not sql_res_usr:
984                                         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))
985                                 else:
986                                         base = True
987                         
988                         if sql_res_usr is None:
989                                 sql_res = cr.fetchone()
990                         else:
991                                 sql_res = sql_res_usr
992                         if not sql_res:
993                                 break
994                         ok = sql_res[5]
995                         view_id = ok or sql_res[3]
996                         model = False
997
998                 # if a view was found
999                 if sql_res:
1000                         result['type'] = sql_res[4]
1001                         result['view_id'] = sql_res[3]
1002                         result['arch'] = sql_res[0]
1003
1004                         def _inherit_apply_rec(result, inherit_id):
1005                                 # get all views which inherit from (ie modify) this view
1006
1007                                 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s and id not in (select ref_id from ir_ui_view_user where inherit_id=%s and model=%s and user_id=%s)' , (inherit_id, self._name, inherit_id, self._name, user)) 
1008                                 sql_inherit_part1 = cr.fetchall()
1009                                 
1010                                 cr.execute('select arch,ref_id from ir_ui_view_user where inherit_id=%d and model=%s and user_id=%d order by priority', (inherit_id, self._name, user))
1011                                 sql_inherit = cr.fetchall()
1012                                 
1013                                 sql_inherit = sql_inherit_part1 + sql_inherit
1014                                 for (inherit,id) in sql_inherit:
1015                                         result = _inherit_apply(result, inherit)
1016                                         result = _inherit_apply_rec(result, id)
1017                                 return result
1018                         
1019                         if not base:
1020                                 result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
1021
1022                         result['name'] = sql_res[1]
1023                         result['field_parent'] = sql_res[2] or False
1024                 else:
1025                         # otherwise, build some kind of default view
1026                         if view_type == 'form':
1027                                 res = self.fields_get(cr, user, context=context)
1028                                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1029                                 '''<form string="%s">''' % (self._description,)
1030                                 for x in res:
1031                                         if res[x]['type'] not in ('one2many', 'many2many'):
1032                                                 xml += '<field name="%s"/>' % (x,)
1033                                                 if res[x]['type'] == 'text':
1034                                                         xml += "<newline/>"
1035                                 xml += "</form>"
1036                         elif view_type == 'tree':
1037                                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1038                                 '''<tree string="%s"><field name="%s"/></tree>''' \
1039                                 % (self._description, self._rec_name)
1040                         elif view_type == 'calendar':
1041                                 xml = self.__get_default_calendar_view()
1042                         else:
1043                                 xml = ''
1044                         result['arch'] = xml
1045                         result['name'] = 'default'
1046                         result['field_parent'] = False
1047                         result['view_id'] = 0
1048
1049                 doc = dom.minidom.parseString(result['arch'].encode('utf-8'))
1050                 xarch, xfields = self.__view_look_dom_arch(cr, user, doc, context=context)
1051                 result['arch'] = xarch
1052                 result['fields'] = xfields
1053                 if toolbar:
1054                         def clean(x):
1055                                 x = x[2]
1056                                 for key in ('report_sxw_content', 'report_rml_content',
1057                                                 'report_sxw', 'report_rml',
1058                                                 'report_sxw_content_data', 'report_rml_content_data'):
1059                                         if key in x:
1060                                                 del x[key]
1061                                 return x
1062                         ir_values_obj = self.pool.get('ir.values')
1063                         resprint = ir_values_obj.get(cr, user, 'action',
1064                                         'client_print_multi', [(self._name, False)], False,
1065                                         context)
1066                         resaction = ir_values_obj.get(cr, user, 'action',
1067                                         'client_action_multi', [(self._name, False)], False,
1068                                         context)
1069
1070                         resrelate = ir_values_obj.get(cr, user, 'action',
1071                                         'client_action_relate', [(self._name, False)], False,
1072                                         context)
1073                         resprint = map(clean, resprint)
1074                         resaction = map(clean, resaction)
1075                         resaction = filter(lambda x: not x.get('multi',False), resaction)
1076                         resprint = filter(lambda x: not x.get('multi',False), resprint)
1077                         resrelate = map(lambda x:x[2], resrelate)
1078
1079                         for x in resprint+resaction+resrelate:
1080                                 x['string'] = x['name']
1081
1082                         result['toolbar'] = {
1083                                 'print': resprint,
1084                                 'action': resaction,
1085                                 'relate': resrelate
1086                         }
1087                 return result
1088
1089         _view_look_dom_arch=__view_look_dom_arch
1090
1091         def search_count(self, cr, user, args, context=None):
1092                 if not context:
1093                         context = {}
1094                 res = self.search(cr, user, args, context=context, count=True)
1095                 if isinstance(res, list):
1096                         return len(res)
1097                 return res
1098
1099         def search(self, cr, user, args, offset=0, limit=None, order=None,
1100                         context=None, count=False):
1101                 raise 'The search method is not implemented on this object !'
1102
1103         def name_get(self, cr, user, ids, context=None):
1104                 raise 'The name_get method is not implemented on this object !'
1105
1106         def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
1107                 raise 'The name_search method is not implemented on this object !'
1108
1109         def copy(self, cr, uid, id, default=None, context=None):
1110                 raise 'The copy method is not implemented on this object !'
1111
1112 class orm_memory(orm_template):
1113         _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']
1114         _inherit_fields = {}
1115         _max_count = 200
1116         _max_hours = 1
1117         _check_time = 20
1118         def __init__(self, cr):
1119                 super(orm_memory, self).__init__(cr)
1120                 self.datas = {}
1121                 self.next_id = 0
1122                 self.check_id = 0
1123                 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1124
1125         def clear(self):
1126                 self.check_id+=1
1127                 if self.check_id % self._check_time:
1128                         return True
1129                 tounlink = []
1130                 max = time.time() - self._max_hours * 60 * 60
1131                 for id in self.datas:
1132                         if self.datas[id]['internal.date_access'] < max:
1133                                 tounlink.append(id)
1134                 self.unlink(tounlink)
1135                 if len(self.datas)>self._max_count:
1136                         sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1137                         sorted.sort()
1138                         ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1139                         self.unlink(ids)
1140                 return True
1141
1142         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1143                 self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
1144                 if not fields:
1145                         fields = self._columns.keys()
1146                 result = []
1147                 for id in ids:
1148                         r = {'id': id}
1149                         for f in fields:
1150                                 r[f] = self.datas[id].get(f, False)
1151                         result.append(r)
1152                         self.datas[id]['internal.date_access'] = time.time()
1153                 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
1154                 for f in fields_post:
1155                         res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=False)
1156                         for record in result:
1157                                 record[f] = res2[record['id']]
1158                 return result
1159
1160         def write(self, cr, user, ids, vals, context=None):
1161                 self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
1162                 vals2 = {}
1163                 upd_todo = []
1164                 for field in vals:
1165                         if self._columns[field]._classic_write:
1166                                 vals2[field] = vals[field]
1167                         else:
1168                                 upd_todo.append(field)
1169                 for id_new in ids:
1170                         self.datas[id_new].update(vals2)
1171                         self.datas[id_new]['internal.date_access'] = time.time()
1172                         for field in upd_todo:
1173                                 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1174                 self._validate(cr, user, [id_new])
1175                 wf_service = netsvc.LocalService("workflow")
1176                 wf_service.trg_write(user, self._name, id_new, cr)
1177                 self.clear()
1178                 return id_new
1179
1180         def create(self, cr, user, vals, context=None):
1181                 self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
1182                 self.next_id += 1
1183                 id_new = self.next_id
1184                 default = []
1185                 for f in self._columns.keys():
1186                         if not f in vals:
1187                                 default.append(f)
1188                 if len(default):
1189                         vals.update(self.default_get(cr, user, default, context))
1190                 vals2 = {}
1191                 upd_todo = []
1192                 for field in vals:
1193                         if self._columns[field]._classic_write:
1194                                 vals2[field] = vals[field]
1195                         else:
1196                                 upd_todo.append(field)
1197                 self.datas[id_new] = vals2
1198                 self.datas[id_new]['internal.date_access'] = time.time()
1199                 for field in upd_todo:
1200                         self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1201                 self._validate(cr, user, [id_new])
1202                 wf_service = netsvc.LocalService("workflow")
1203                 wf_service.trg_create(user, self._name, id_new, cr)
1204                 self.clear()
1205                 return id_new
1206
1207         def default_get(self, cr, uid, fields_list, context=None):
1208                 if not context:
1209                         context={}
1210                 value = {}
1211                 # get the default values for the inherited fields
1212                 for f in fields_list:
1213                         if f in self._defaults:
1214                                 value[f] = self._defaults[f](self, cr, uid, context)
1215                         fld_def = ((f in self._columns) and self._columns[f]) \
1216                                         or ((f in self._inherit_fields ) and self._inherit_fields[f][2]) \
1217                                         or False
1218
1219                 # get the default values set by the user and override the default
1220                 # values defined in the object
1221                 ir_values_obj = self.pool.get('ir.values')
1222                 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1223                 for id, field, field_value in res:
1224                         if field in fields_list:
1225                                 fld_def = (field in self._columns)
1226                                 if fld_def._type in ('many2one', 'one2one'):
1227                                         obj = self.pool.get(fld_def._obj)
1228                                         if not obj.search(cr, uid, [('id', '=', field_value)]):
1229                                                 continue
1230                                 if fld_def._type in ('many2many'):
1231                                         obj = self.pool.get(fld_def._obj)
1232                                         field_value2 = []
1233                                         for i in range(len(field_value)):
1234                                                 if not obj.search(cr, uid, [('id', '=',
1235                                                         field_value[i])]):
1236                                                         continue
1237                                                 field_value2.append(field_value[i])
1238                                         field_value = field_value2
1239                                 if fld_def._type in ('one2many'):
1240                                         obj = self.pool.get(fld_def._obj)
1241                                         field_value2 = []
1242                                         for i in range(len(field_value)):
1243                                                 field_value2.append({})
1244                                                 for field2 in field_value[i]:
1245                                                         if obj._columns[field2]._type in ('many2one', 'one2one'):
1246                                                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1247                                                                 if not obj2.search(cr, uid,
1248                                                                                 [('id', '=', field_value[i][field2])]):
1249                                                                         continue
1250                                                         # TODO add test for many2many and one2many
1251                                                         field_value2[i][field2] = field_value[i][field2]
1252                                         field_value = field_value2
1253                                 value[field] = field_value
1254                 return value
1255
1256         def search(self, cr, user, args, offset=0, limit=None, order=None,
1257                         context=None, count=False):
1258                 return self.datas.keys()
1259
1260         def unlink(self, cr, uid, ids, context=None):
1261                 for id in ids:
1262                         if id in self.datas:
1263                                 del self.datas[id]
1264                 if len(ids):
1265                         cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str,ids))+')', (self._name, ))
1266                 return True
1267
1268         def perm_read(self, cr, user, ids, context=None, details=True):
1269                 result = []
1270                 for id in ids:
1271                         result.append({
1272                                 'create_uid': (user, 'Root'),
1273                                 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'),
1274                                 'write_uid': False,
1275                                 'write_date': False,
1276                                 'id': id
1277                         })
1278                 return result
1279
1280 class orm(orm_template):
1281
1282         _sql_constraints = []
1283
1284         _log_access = True
1285         _table = None
1286         _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']
1287         def _auto_init(self, cr, context={}):
1288                 logger = netsvc.Logger()
1289                 create = False
1290                 self._field_create(cr, context=context)
1291                 if not hasattr(self, "_auto") or self._auto:
1292                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
1293                         if not cr.rowcount:
1294                                 cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table)
1295                                 create = True
1296                         cr.commit()
1297                         if self._log_access:
1298                                 logs = {
1299                                         'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1300                                         'create_date': 'TIMESTAMP',
1301                                         'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1302                                         'write_date': 'TIMESTAMP'
1303                                 }
1304                                 for k in logs:
1305                                         cr.execute(
1306                                                 """
1307                                                 SELECT c.relname
1308                                                 FROM pg_class c, pg_attribute a
1309                                                 WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid
1310                                                 """ % (self._table, k))
1311                                         if not cr.rowcount:
1312                                                 cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" %
1313                                                         (self._table, k, logs[k]))
1314                                                 cr.commit()
1315
1316                         # iterate on the database columns to drop the NOT NULL constraints
1317                         # of fields which were required but have been removed
1318                         cr.execute(
1319                                 "SELECT a.attname, a.attnotnull "\
1320                                 "FROM pg_class c, pg_attribute a "\
1321                                 "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table)
1322                         db_columns = cr.dictfetchall()
1323                         for column in db_columns:
1324                                 if column['attname'] not in ('id', 'oid', 'tableoid', 'ctid', 'xmin', 'xmax', 'cmin', 'cmax'):
1325                                         if column['attnotnull'] and column['attname'] not in self._columns:
1326                                                 cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, column['attname']))
1327
1328                         # iterate on the "object columns"
1329                         for k in self._columns:
1330                                 if k in ('id','write_uid','write_date','create_uid','create_date'):
1331                                         continue
1332                                         #raise 'Can not define a column %s. Reserved keyword !' % (k,)
1333                                 f = self._columns[k]
1334
1335                                 if isinstance(f, fields.one2many):
1336                                         cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
1337                                         if cr.fetchone():
1338                                                 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))
1339                                                 res = cr.fetchone()[0]
1340                                                 if not res:
1341                                                         cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (%s) REFERENCES \"%s\" ON DELETE SET NULL" % (self._obj, f._fields_id, f._table))
1342                                 elif isinstance(f, fields.many2many):
1343                                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
1344                                         if not cr.dictfetchall():
1345                                                 #FIXME: Remove this try/except
1346                                                 try:
1347                                                         ref = self.pool.get(f._obj)._table
1348                                                 except AttributeError:
1349                                                         ref = f._obj.replace('.','_')
1350                                                 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))
1351                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel,f._id1,f._rel,f._id1))
1352                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel,f._id2,f._rel,f._id2))
1353                                                 cr.commit()
1354                                 else:
1355                                         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))
1356                                         res = cr.dictfetchall()
1357                                         if not res:
1358                                                 if not isinstance(f,fields.function) or f.store:
1359
1360                                                         # add the missing field
1361                                                         cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % (self._table, k, get_pg_type(f)[1]))
1362
1363                                                         # initialize it
1364                                                         if not create and k in self._defaults:
1365                                                                 default = self._defaults[k](self, cr, 1, {})
1366                                                                 if not default:
1367                                                                         cr.execute("UPDATE \"%s\" SET \"%s\"=NULL" % (self._table, k))
1368                                                                 else:
1369                                                                         cr.execute("UPDATE \"%s\" SET \"%s\"='%s'" % (self._table, k, default))
1370                                                         if isinstance(f,fields.function):
1371                                                                 cr.execute('select id from '+self._table)
1372                                                                 ids_lst = map(lambda x: x[0], cr.fetchall())
1373                                                                 while ids_lst:
1374                                                                         iids = ids_lst[:40]
1375                                                                         ids_lst = ids_lst[40:]
1376                                                                         res = f.get(cr, self, iids, k, 1, {})
1377                                                                         for r in res.items():
1378                                                                                 cr.execute("UPDATE \"%s\" SET \"%s\"='%s' where id=%d"% (self._table, k, r[1],r[0]))
1379
1380                                                         # and add constraints if needed
1381                                                         if isinstance(f, fields.many2one):
1382                                                                 #FIXME: Remove this try/except
1383                                                                 try:
1384                                                                         ref = self.pool.get(f._obj)._table
1385                                                                 except AttributeError:
1386                                                                         ref = f._obj.replace('.','_')
1387                                                                 # ir_actions is inherited so foreign key doesn't work on it
1388                                                                 if ref <> 'ir_actions':
1389                                                                         cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (\"%s\") REFERENCES \"%s\" ON DELETE %s" % (self._table, k, ref, f.ondelete))
1390                                                         if f.select:
1391                                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k))
1392                                                         if f.required:
1393                                                                 cr.commit()
1394                                                                 try:
1395                                                                         cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k))
1396                                                                 except:
1397                                                                         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))
1398                                                         cr.commit()
1399                                         elif len(res)==1:
1400                                                 f_pg_def = res[0]
1401                                                 f_pg_type = f_pg_def['typname']
1402                                                 f_pg_size = f_pg_def['size']
1403                                                 f_pg_notnull = f_pg_def['attnotnull']
1404                                                 if isinstance(f, fields.function) and not f.store:
1405                                                         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))
1406                                                         f_obj_type = None
1407                                                 else:
1408                                                         f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
1409
1410                                                 if f_obj_type:
1411                                                         if f_pg_type != f_obj_type:
1412                                                                 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))
1413                                                         if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
1414                                                                 # columns with the name 'type' cannot be changed for an unknown reason?!
1415                                                                 if k != 'type':
1416                                                                         if f_pg_size > f.size:
1417                                                                                 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))
1418 #TODO: check si y a des donnees qui vont poser probleme (select char_length(...))
1419 #TODO: issue a log message even if f_pg_size < f.size
1420                                                                         cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table,k))
1421                                                                         cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table,k,f.size))
1422                                                                         cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size))
1423                                                                         cr.execute("ALTER TABLE \"%s\" DROP COLUMN temp_change_size" % (self._table,))
1424                                                                         cr.commit()
1425                                                         # if the field is required and hasn't got a NOT NULL constraint
1426                                                         if f.required and f_pg_notnull == 0:
1427                                                                 # set the field to the default value if any
1428                                                                 if self._defaults.has_key(k):
1429                                                                         default = self._defaults[k](self, cr, 1, {})
1430                                                                         if not (default is False):
1431                                                                                 cr.execute("UPDATE \"%s\" SET \"%s\"='%s' WHERE %s is NULL" % (self._table, k, default, k))
1432                                                                                 cr.commit()
1433                                                                 # add the NOT NULL constraint
1434                                                                 try:
1435                                                                         cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k))
1436                                                                         cr.commit()
1437                                                                 except:
1438                                                                         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))
1439                                                                 cr.commit()
1440                                                         elif not f.required and f_pg_notnull == 1:
1441                                                                 cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table,k))
1442                                                                 cr.commit()
1443                                                         cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table))
1444                                                         res = cr.dictfetchall()
1445                                                         if not res and f.select:
1446                                                                 cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k))
1447                                                                 cr.commit()
1448                                                         if res and not f.select:
1449                                                                 cr.execute("DROP INDEX \"%s_%s_index\"" % (self._table, k))
1450                                                                 cr.commit()
1451                                                         if isinstance(f, fields.many2one):
1452                                                                 ref = self.pool.get(f._obj)._table
1453                                                                 if ref != 'ir_actions':
1454                                                                         cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, ' \
1455                                                                                                 'pg_attribute as att1, pg_attribute as att2 ' \
1456                                                                                         'WHERE con.conrelid = cl1.oid ' \
1457                                                                                                 'AND cl1.relname = %s ' \
1458                                                                                                 'AND con.confrelid = cl2.oid ' \
1459                                                                                                 'AND cl2.relname = %s ' \
1460                                                                                                 'AND array_lower(con.conkey, 1) = 1 ' \
1461                                                                                                 'AND con.conkey[1] = att1.attnum ' \
1462                                                                                                 'AND att1.attrelid = cl1.oid ' \
1463                                                                                                 'AND att1.attname = %s ' \
1464                                                                                                 'AND array_lower(con.confkey, 1) = 1 ' \
1465                                                                                                 'AND con.confkey[1] = att2.attnum ' \
1466                                                                                                 'AND att2.attrelid = cl2.oid ' \
1467                                                                                                 'AND att2.attname = %s ' \
1468                                                                                                 'AND con.contype = \'f\'', (self._table, ref, k, 'id'))
1469                                                                         res = cr.dictfetchall()
1470                                                                         if res:
1471                                                                                 confdeltype = {
1472                                                                                         'RESTRICT': 'r',
1473                                                                                         'NO ACTION': 'a',
1474                                                                                         'CASCADE': 'c',
1475                                                                                         'SET NULL': 'n',
1476                                                                                         'SET DEFAULT': 'd',
1477                                                                                 }
1478                                                                                 if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'):
1479                                                                                         cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
1480                                                                                         cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
1481                                                                                         cr.commit()
1482                                         else:
1483                                                 print "ERROR"
1484                 else:
1485                         cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
1486                         create = not bool(cr.fetchone())
1487
1488                 for (key,con,_) in self._sql_constraints:
1489                         cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key))
1490                         if not cr.dictfetchall():
1491                                 try:
1492                                         cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table,self._table,key, con,))
1493                                         cr.commit()
1494                                 except:
1495                                         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,))
1496
1497                 if create:
1498                         if hasattr(self,"_sql"):
1499                                 for line in self._sql.split(';'):
1500                                         line2 = line.replace('\n','').strip()
1501                                         if line2:
1502                                                 cr.execute(line2)
1503                                                 cr.commit()
1504
1505         def __init__(self, cr):
1506                 super(orm, self).__init__(cr)
1507                 f=filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns)
1508                 if f:
1509                         list_store = []
1510                         tuple_store = ()
1511                         tuple_fn = ()
1512                         for store_field in f:
1513                                 if not self._columns[store_field].store == True:
1514                                         dict_store = self._columns[store_field].store
1515                                         key = dict_store.keys()
1516                                         list_data=[]
1517                                         for i in key:
1518                                                 tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[i][0]),dict_store[i][1],i
1519                                                 list_data.append(tuple_store)
1520                                         #tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[key[0]][0]),dict_store[key[0]][1]
1521                                         for l in list_data:
1522                                                 list_store=[]
1523                                                 if l[5] in self.pool._store_function.keys():
1524                                                         self.pool._store_function[l[5]].append(l)
1525                                                         temp_list = list(set(self.pool._store_function[l[5]]))
1526                                                         self.pool._store_function[l[5]]=temp_list
1527                                                 else:
1528                                                         list_store.append(l)
1529                                                         self.pool._store_function[l[5]]=list_store
1530
1531                 for (key,_,msg) in self._sql_constraints:
1532                         self.pool._sql_error[self._table+'_'+key] = msg
1533
1534                 # Load manual fields
1535
1536                 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state','ir.model.fields'))
1537                 if cr.fetchone():
1538                         cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name,'manual'))
1539                         for field in cr.dictfetchall():
1540                                 if field['name'] in self._columns:
1541                                         continue
1542                                 attrs = {
1543                                         'string': field['field_description'],
1544                                         'required': bool(field['required']),
1545                                         'readonly': bool(field['readonly']),
1546                                         'domain': field['domain'] or None,
1547                                         'size': field['size'],
1548                                         'ondelete': field['on_delete'],
1549                                         'translate': (field['translate']),
1550                                         #'select': int(field['select_level'])
1551                                 }
1552                                 #if field['relation']:
1553                                 #       attrs['relation'] = field['relation']
1554                                 if field['ttype'] == 'selection':
1555                                         self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
1556                                 elif field['ttype'] == 'many2one':
1557                                         self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
1558                                 else:
1559                                         self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
1560
1561                 self._inherits_reload()
1562                 if not self._sequence:
1563                         self._sequence = self._table+'_id_seq'
1564                 for k in self._defaults:
1565                         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,)
1566                 for f in self._columns:
1567                         self._columns[f].restart()
1568
1569         def default_get(self, cr, uid, fields_list, context=None):
1570                 if not context:
1571                         context={}
1572                 value = {}
1573                 # get the default values for the inherited fields
1574                 for t in self._inherits.keys():
1575                         value.update(self.pool.get(t).default_get(cr, uid, fields_list,
1576                                 context))
1577
1578                 # get the default values defined in the object
1579                 for f in fields_list:
1580                         if f in self._defaults:
1581                                 value[f] = self._defaults[f](self, cr, uid, context)
1582                         fld_def = ((f in self._columns) and self._columns[f]) \
1583                                         or ((f in self._inherit_fields ) and self._inherit_fields[f][2]) \
1584                                         or False
1585                         if isinstance(fld_def, fields.property):
1586                                 property_obj = self.pool.get('ir.property')
1587                                 definition_id = fld_def._field_get(cr, uid, self._name, f)
1588                                 nid = property_obj.search(cr, uid, [('fields_id', '=',
1589                                         definition_id), ('res_id', '=', False)])
1590                                 if nid:
1591                                         prop_value = property_obj.browse(cr, uid, nid[0],
1592                                                         context=context).value
1593                                         value[f] = (prop_value and int(prop_value.split(',')[1])) \
1594                                                         or False
1595
1596                 # get the default values set by the user and override the default
1597                 # values defined in the object
1598                 ir_values_obj = self.pool.get('ir.values')
1599                 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1600                 for id, field, field_value in res:
1601                         if field in fields_list:
1602                                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1603                                 if fld_def._type in ('many2one', 'one2one'):
1604                                         obj = self.pool.get(fld_def._obj)
1605                                         if not obj.search(cr, uid, [('id', '=', field_value)]):
1606                                                 continue
1607                                 if fld_def._type in ('many2many'):
1608                                         obj = self.pool.get(fld_def._obj)
1609                                         field_value2 = []
1610                                         for i in range(len(field_value)):
1611                                                 if not obj.search(cr, uid, [('id', '=',
1612                                                         field_value[i])]):
1613                                                         continue
1614                                                 field_value2.append(field_value[i])
1615                                         field_value = field_value2
1616                                 if fld_def._type in ('one2many'):
1617                                         obj = self.pool.get(fld_def._obj)
1618                                         field_value2 = []
1619                                         for i in range(len(field_value)):
1620                                                 field_value2.append({})
1621                                                 for field2 in field_value[i]:
1622                                                         if obj._columns[field2]._type in ('many2one', 'one2one'):
1623                                                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1624                                                                 if not obj2.search(cr, uid,
1625                                                                                 [('id', '=', field_value[i][field2])]):
1626                                                                         continue
1627                                                         # TODO add test for many2many and one2many
1628                                                         field_value2[i][field2] = field_value[i][field2]
1629                                         field_value = field_value2
1630                                 value[field] = field_value
1631                 return value
1632
1633
1634         #
1635         # Update objects that uses this one to update their _inherits fields
1636         #
1637         def _inherits_reload_src(self):
1638                 for obj in self.pool.obj_pool.values():
1639                         if self._name in obj._inherits:
1640                                 obj._inherits_reload()
1641
1642         def _inherits_reload(self):
1643                 res = {}
1644                 for table in self._inherits:
1645                         res.update(self.pool.get(table)._inherit_fields)
1646                         for col in self.pool.get(table)._columns.keys():
1647                                 res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col])
1648                         for col in self.pool.get(table)._inherit_fields.keys():
1649                                 res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
1650                 self._inherit_fields=res
1651                 self._inherits_reload_src()
1652
1653         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1654                 if not context:
1655                         context={}
1656                 self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
1657                 if not fields:
1658                         fields = self._columns.keys() + self._inherit_fields.keys()
1659                 select = ids
1660                 if isinstance(ids, (int, long)):
1661                         select = [ids]
1662                 result =  self._read_flat(cr, user, select, fields, context, load)
1663                 for r in result:
1664                         for key,v in r.items():
1665                                 if v == None:
1666                                         r[key]=False
1667                 if isinstance(ids, (int, long)):
1668                         return result[0]
1669                 return result
1670
1671         def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
1672                 if not context:
1673                         context={}
1674                 if not ids:
1675                         return []
1676
1677                 if fields==None:
1678                         fields = self._columns.keys()
1679
1680                 # construct a clause for the rules :
1681                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1682
1683                 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
1684                 fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
1685
1686                 res = []
1687                 if len(fields_pre) :
1688                         fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
1689                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1690                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1691                                 if d1:
1692                                         cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \
1693                                                         (','.join(fields_pre2 + ['id']), self._table,
1694                                                                 ','.join([str(x) for x in sub_ids]), d1,
1695                                                                 self._order),d2)
1696                                         if not cr.rowcount == len({}.fromkeys(sub_ids)):
1697                                                 raise except_orm('AccessError',
1698                                                                 'You try to bypass an access rule (Document type: %s).' % self._description)
1699                                 else:
1700                                         cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \
1701                                                         (','.join(fields_pre2 + ['id']), self._table,
1702                                                                 ','.join([str(x) for x in sub_ids]),
1703                                                                 self._order))
1704                                 res.extend(cr.dictfetchall())
1705                 else:
1706                         res = map(lambda x: {'id': x}, ids)
1707
1708                 for f in fields_pre:
1709                         if self._columns[f].translate:
1710                                 ids = map(lambda x: x['id'], res)
1711                                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
1712                                 for r in res:
1713                                         r[f] = res_trans.get(r['id'], False) or r[f]
1714
1715                 for table in self._inherits:
1716                         col = self._inherits[table]
1717                         cols = intersect(self._inherit_fields.keys(), fields)
1718                         if not cols:
1719                                 continue
1720                         res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
1721
1722                         res3 = {}
1723                         for r in res2:
1724                                 res3[r['id']] = r
1725                                 del r['id']
1726
1727                         for record in res:
1728                                 record.update(res3[record[col]])
1729                                 if col not in fields:
1730                                         del record[col]
1731
1732                 # all fields which need to be post-processed by a simple function (symbol_get)
1733                 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
1734                 if fields_post:
1735                         # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
1736                         # to get the _symbol_get in each occurence
1737                         for r in res:
1738                                 for f in fields_post:
1739                                         r[f] = self.columns[f]._symbol_get(r[f])
1740                 ids = map(lambda x: x['id'], res)
1741
1742                 # all non inherited fields for which the attribute whose name is in load is False
1743                 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
1744                 for f in fields_post:
1745                         # get the value of that field for all records/ids
1746                         res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
1747                         for record in res:
1748                                 record[f] = res2[record['id']]
1749
1750                 readonly = None
1751                 for vals in res:
1752                         for field in vals.copy():
1753                                 fobj = None
1754                                 if field in self._columns:
1755                                         fobj = self._columns[field]
1756
1757                                 if not fobj:
1758                                         continue
1759                                 groups = fobj.read
1760                                 if groups:
1761                                         edit = False
1762                                         for group in groups:
1763                                                 module = group.split(".")[0]
1764                                                 grp = group.split(".")[1]
1765                                                 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" % \
1766                                                                    (grp, module, 'res.groups', user))
1767                                                 readonly = cr.fetchall()
1768                                                 if readonly[0][0] >= 1:
1769                                                         edit = True
1770                                                         break
1771                                                 elif readonly[0][0] == 0:
1772                                                         edit = False
1773                                                 else:
1774                                                         edit = False
1775
1776                                         if not edit:
1777                                                 if type(vals[field]) == type([]):
1778                                                         vals[field] = []
1779                                                 elif type(vals[field]) == type(0.0):
1780                                                         vals[field] = 0
1781                                                 elif type(vals[field]) == type(''):
1782                                                         vals[field] = '=No Permission='
1783                                                 else:
1784                                                         vals[field] = False
1785                 return res
1786
1787         def perm_read(self, cr, user, ids, context=None, details=True):
1788                 if not context:
1789                         context={}
1790                 if not ids:
1791                         return []
1792                 fields = ''
1793                 if self._log_access:
1794                         fields =', u.create_uid, u.create_date, u.write_uid, u.write_date'
1795                 if isinstance(ids, (int, long)):
1796                         ids_str = str(ids)
1797                 else:
1798                         ids_str = string.join(map(lambda x:str(x), ids),',')
1799                 cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
1800                 res = cr.dictfetchall()
1801                 for r in res:
1802                         for key in r:
1803                                 r[key] = r[key] or False
1804                                 if key in ('write_uid','create_uid','uid') and details:
1805                                         if r[key]:
1806                                                 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
1807                 if isinstance(ids, (int, long)):
1808                         return res[ids]
1809                 return res
1810
1811         def unlink(self, cr, uid, ids, context=None):
1812                 if not context:
1813                         context={}
1814                 if not ids:
1815                         return True
1816                 if isinstance(ids, (int, long)):
1817                         ids = [ids]
1818
1819                 fn_list = []
1820                 if self._name in self.pool._store_function.keys():
1821                         list_store = self.pool._store_function[self._name]
1822                         fn_data = ()
1823                         id_change = []
1824                         for tuple_fn in list_store:
1825                                 for id in ids:
1826                                         id_change.append(self._store_get_ids(cr, uid, id,tuple_fn, context)[0])
1827                                 fn_data = id_change,tuple_fn
1828                                 fn_list.append(fn_data)
1829
1830                 delta= context.get('read_delta',False)
1831                 if delta and self._log_access:
1832                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1833                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1834                                 cr.execute("select  (now()  - min(write_date)) <= '%s'::interval " \
1835                                                 "from \"%s\" where id in (%s)" %
1836                                                 (delta, self._table, ",".join(map(str, sub_ids))) )
1837                         res= cr.fetchone()
1838                         if res and res[0]:
1839                                 raise except_orm('ConcurrencyException',
1840                                                 'This record was modified in the meanwhile')
1841
1842                 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink')
1843
1844                 wf_service = netsvc.LocalService("workflow")
1845                 for id in ids:
1846                         wf_service.trg_delete(uid, self._name, id, cr)
1847
1848                 #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
1849                 #res = cr.dictfetchall()
1850                 #for key in self._inherits:
1851                 #       ids2 = [x[self._inherits[key]] for x in res]
1852                 #       self.pool.get(key).unlink(cr, uid, ids2)
1853
1854                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name)
1855                 if d1:
1856                         d1 = ' AND '+d1
1857
1858                 for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1859                         sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1860                         str_d = string.join(('%d',)*len(sub_ids),',')
1861                         if d1:
1862                                 cr.execute('SELECT id FROM "'+self._table+'" ' \
1863                                                 'WHERE id IN ('+str_d+')'+d1, sub_ids+d2)
1864                                 if not cr.rowcount == len({}.fromkeys(ids)):
1865                                         raise except_orm('AccessError',
1866                                                         'You try to bypass an access rule (Document type: %s).' % \
1867                                                                         self._description)
1868
1869                         cr.execute('delete from inherit ' \
1870                                         'where (obj_type=%s and obj_id in ('+str_d+')) ' \
1871                                                 'or (inst_type=%s and inst_id in ('+str_d+'))',
1872                                                 (self._name,)+tuple(sub_ids)+(self._name,)+tuple(sub_ids))
1873                         if d1:
1874                                 cr.execute('delete from "'+self._table+'" ' \
1875                                                 'where id in ('+str_d+')'+d1, sub_ids+d2)
1876                         else:
1877                                 cr.execute('delete from "'+self._table+'" ' \
1878                                                 'where id in ('+str_d+')', sub_ids)
1879                 if fn_list:
1880                         for ids,tuple_fn in fn_list:
1881                                 self._store_set_values(cr, uid, ids,tuple_fn,id_change, context)
1882
1883                 return True
1884
1885         #
1886         # TODO: Validate
1887         #
1888         def write(self, cr, user, ids, vals, context=None):
1889                 readonly = None
1890                 for field in vals.copy():
1891                         fobj = None
1892                         if field in self._columns:
1893                                 fobj = self._columns[field]
1894                         else:
1895                                 fobj = self._inherit_fields[field][2]
1896                         if not fobj:
1897                                 continue
1898                         groups = fobj.write
1899
1900                         if groups:
1901                                 edit = False
1902                                 for group in groups:
1903                                         module = group.split(".")[0]
1904                                         grp = group.split(".")[1]
1905                                         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" % \
1906                                                            (grp, module, 'res.groups', user))
1907                                         readonly = cr.fetchall()
1908                                         if readonly[0][0] >= 1:
1909                                                 edit = True
1910                                                 break
1911                                         elif readonly[0][0] == 0:
1912                                                 edit = False
1913                                         else:
1914                                                 edit = False
1915
1916                                 if not edit:
1917                                         vals.pop(field)
1918
1919                 if not context:
1920                         context={}
1921                 if not ids:
1922                         return True
1923                 if isinstance(ids, (int, long)):
1924                         ids = [ids]
1925                 delta= context.get('read_delta',False)
1926                 if delta and self._log_access:
1927                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1928                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1929                                 cr.execute("select  (now()  - min(write_date)) <= '%s'::interval " \
1930                                                 "from %s where id in (%s)" %
1931                                                 (delta,self._table, ",".join(map(str, sub_ids))))
1932                                 res= cr.fetchone()
1933                                 if res and res[0]:
1934                                         for field in vals:
1935                                                 if field in self._columns and self._columns[field]._classic_write:
1936                                                         raise except_orm('ConcurrencyException',
1937                                                                         'This record was modified in the meanwhile')
1938
1939                 self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
1940
1941                 #for v in self._inherits.values():
1942                 #       assert v not in vals, (v, vals)
1943                 upd0=[]
1944                 upd1=[]
1945                 upd_todo=[]
1946                 updend=[]
1947                 direct = []
1948                 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
1949                 for field in vals:
1950                         if field in self._columns:
1951                                 if self._columns[field]._classic_write:
1952                                         if (not totranslate) or not self._columns[field].translate:
1953                                                 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
1954                                                 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
1955                                         direct.append(field)
1956                                 else:
1957                                         upd_todo.append(field)
1958                         else:
1959                                 updend.append(field)
1960                         if field in self._columns \
1961                                         and hasattr(self._columns[field], 'selection') \
1962                                         and vals[field]:
1963                                 if self._columns[field]._type == 'reference':
1964                                         val = vals[field].split(',')[0]
1965                                 else:
1966                                         val = vals[field]
1967                                 if isinstance(self._columns[field].selection, (tuple, list)):
1968                                         if val not in dict(self._columns[field].selection):
1969                                                 raise except_orm('ValidateError',
1970                                                 'The value "%s" for the field "%s" is not in the selection' \
1971                                                                 % (vals[field], field))
1972                                 else:
1973                                         if val not in dict(self._columns[field].selection(
1974                                                 self, cr, user, context=context)):
1975                                                 raise except_orm('ValidateError',
1976                                                 'The value "%s" for the field "%s" is not in the selection' \
1977                                                                 % (vals[field], field))
1978
1979                 if self._log_access:
1980                         upd0.append('write_uid=%d')
1981                         upd0.append('write_date=now()')
1982                         upd1.append(user)
1983
1984                 if len(upd0):
1985
1986                         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1987                         if d1:
1988                                 d1 = ' and '+d1
1989
1990                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
1991                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
1992                                 ids_str = string.join(map(str, sub_ids),',')
1993                                 if d1:
1994                                         cr.execute('SELECT id FROM "'+self._table+'" ' \
1995                                                         'WHERE id IN ('+ids_str+')'+d1, d2)
1996                                         if not cr.rowcount == len({}.fromkeys(sub_ids)):
1997                                                 raise except_orm('AccessError',
1998                                                                 'You try to bypass an access rule (Document type: %s).'% \
1999                                                                                 self._description)
2000                                 else:
2001                                         cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')')
2002                                         if not cr.rowcount == len({}.fromkeys(sub_ids)):
2003                                                 raise except_orm('AccessError',
2004                                                                 'You try to write on an record that doesn\'t exist ' \
2005                                                                                 '(Document type: %s).' % self._description)
2006                                 if d1:
2007                                         cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \
2008                                                         'where id in ('+ids_str+')'+d1, upd1+ d2)
2009                                 else:
2010                                         cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \
2011                                                         'where id in ('+ids_str+')', upd1)
2012
2013                         if totranslate:
2014                                 for f in direct:
2015                                         if self._columns[f].translate:
2016                                                 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model',  context['lang'], ids,vals[f])
2017
2018                 # call the 'set' method of fields which are not classic_write
2019                 upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
2020                 for field in upd_todo:
2021                         for id in ids:
2022                                 self._columns[field].set(cr, self, id, field, vals[field], user, context=context)
2023
2024                 for table in self._inherits:
2025                         col = self._inherits[table]
2026                         nids = []
2027                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
2028                                 sub_ids = ids[ID_MAX * i:ID_MAX * (i + 1)]
2029                                 ids_str = string.join(map(str, sub_ids),',')
2030                                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2031                                                 'where id in ('+ids_str+')', upd1)
2032                                 nids.extend([x[0] for x in cr.fetchall()])
2033
2034                         v = {}
2035                         for val in updend:
2036                                 if self._inherit_fields[val][0]==table:
2037                                         v[val]=vals[val]
2038                         self.pool.get(table).write(cr, user, nids, v, context)
2039
2040                 self._validate(cr, user, ids)
2041
2042                 if context.has_key('read_delta'):
2043                         del context['read_delta']
2044
2045                 wf_service = netsvc.LocalService("workflow")
2046                 for id in ids:
2047                         wf_service.trg_write(user, self._name, id, cr)
2048                 self._update_function_stored(cr, user, ids, context=context)
2049
2050                 if self._name in self.pool._store_function.keys():
2051                         list_store = self.pool._store_function[self._name]
2052                         for tuple_fn in list_store:
2053                                 flag = False
2054                                 if not tuple_fn[3]:
2055                                         flag = True
2056                                 for field in tuple_fn[3]:
2057                                         if field in vals.keys():
2058                                                 flag = True
2059                                                 break
2060                                 if flag:
2061                                         id_change = self._store_get_ids(cr, user, ids[0],tuple_fn, context)
2062                                         self._store_set_values(cr, user, ids[0],tuple_fn,id_change, context)
2063
2064                 return True
2065
2066         #
2067         # TODO: Should set perm to user.xxx
2068         #
2069         def create(self, cr, user, vals, context=None):
2070                 """ create(cr, user, vals, context) -> int
2071                 cr = database cursor
2072                 user = user id
2073                 vals = dictionary of the form {'field_name':field_value, ...}
2074                 """
2075                 if not context:
2076                         context={}
2077                 self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
2078
2079                 default = []
2080
2081                 avoid_table = []
2082                 for (t,c) in self._inherits.items():
2083                         if c in vals:
2084                                 avoid_table.append(t)
2085                 for f in self._columns.keys(): # + self._inherit_fields.keys():
2086                         if not f in vals:
2087                                 default.append(f)
2088                 for f in self._inherit_fields.keys():
2089                         if (not f in vals) and (not self._inherit_fields[f][0] in avoid_table):
2090                                 default.append(f)
2091
2092                 if len(default):
2093                         vals.update(self.default_get(cr, user, default, context))
2094
2095                 tocreate = {}
2096                 for v in self._inherits:
2097                         if self._inherits[v] not in vals:
2098                                 tocreate[v] = {}
2099
2100                 (upd0, upd1, upd2) = ('', '', [])
2101                 upd_todo = []
2102
2103                 for v in vals.keys():
2104                         if v in self._inherit_fields:
2105                                 (table,col,col_detail) = self._inherit_fields[v]
2106                                 tocreate[table][v] = vals[v]
2107                                 del vals[v]
2108
2109                 cr.execute("SELECT nextval('"+self._sequence+"')")
2110                 id_new = cr.fetchone()[0]
2111                 for table in tocreate:
2112                         id = self.pool.get(table).create(cr, user, tocreate[table])
2113                         upd0 += ','+self._inherits[table]
2114                         upd1 += ',%d'
2115                         upd2.append(id)
2116                         cr.execute('insert into inherit (obj_type,obj_id,inst_type,inst_id) values (%s,%d,%s,%d)', (table,id,self._name,id_new))
2117
2118                 for field in vals:
2119                         if self._columns[field]._classic_write:
2120                                 upd0=upd0+',"'+field+'"'
2121                                 upd1=upd1+','+self._columns[field]._symbol_set[0]
2122                                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
2123                         else:
2124                                 upd_todo.append(field)
2125                         if field in self._columns \
2126                                         and hasattr(self._columns[field], 'selection') \
2127                                         and vals[field]:
2128                                 if self._columns[field]._type == 'reference':
2129                                         val = vals[field].split(',')[0]
2130                                 else:
2131                                         val = vals[field]
2132                                 if isinstance(self._columns[field].selection, (tuple, list)):
2133                                         if val not in dict(self._columns[field].selection):
2134                                                 raise except_orm('ValidateError',
2135                                                 'The value "%s" for the field "%s" is not in the selection' \
2136                                                                 % (vals[field], field))
2137                                 else:
2138                                         if val not in dict(self._columns[field].selection(
2139                                                 self, cr, user, context=context)):
2140                                                 raise except_orm('ValidateError',
2141                                                 'The value "%s" for the field "%s" is not in the selection' \
2142                                                                 % (vals[field], field))
2143                 if self._log_access:
2144                         upd0 += ',create_uid,create_date'
2145                         upd1 += ',%d,now()'
2146                         upd2.append(user)
2147                 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
2148                 upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
2149                 for field in upd_todo:
2150                         self._columns[field].set(cr, self, id_new, field, vals[field], user, context)
2151
2152                 self._validate(cr, user, [id_new])
2153
2154                 wf_service = netsvc.LocalService("workflow")
2155                 wf_service.trg_create(user, self._name, id_new, cr)
2156                 self._update_function_stored(cr, user, [id_new], context=context)
2157                 if self._name in self.pool._store_function.keys():
2158                         list_store = self.pool._store_function[self._name]
2159                         for tuple_fn in list_store:
2160                                 id_change = self._store_get_ids(cr, user, id_new,tuple_fn, context)
2161                                 self._store_set_values(cr, user, id_new,tuple_fn,id_change, context)
2162
2163                 return id_new
2164
2165         def _store_get_ids(self,cr, uid, ids,tuple_fn, context):
2166                 parent_id=getattr(self.pool.get(tuple_fn[0]),tuple_fn[4].func_name)(cr,uid,[ids])
2167                 return parent_id
2168
2169         def _store_set_values(self,cr,uid,ids,tuple_fn,parent_id,context):
2170                 name = tuple_fn[1]
2171                 table = tuple_fn[0]
2172                 args={}
2173                 vals_tot=getattr(self.pool.get(table),tuple_fn[2])(cr, uid, parent_id ,name, args, context)
2174                 write_dict = {}
2175                 for id in vals_tot.keys():
2176                         write_dict[name]=vals_tot[id]
2177                         self.pool.get(table).write(cr,uid,[id],write_dict)
2178                 return True
2179
2180         def _update_function_stored(self, cr, user, ids, context=None):
2181                 if not context:
2182                         context = {}
2183                 f = filter(lambda a: isinstance(self._columns[a], fields.function) \
2184                                 and self._columns[a].store, self._columns)
2185                 if f:
2186                         result=self.read(cr, user, ids, fields=f, context=context)
2187                         for res in result:
2188                                 upd0=[]
2189                                 upd1=[]
2190                                 for field in res:
2191                                         if field not in f:
2192                                                 continue
2193                                         value = res[field]
2194                                         if self._columns[field]._type in ('many2one', 'one2one'):
2195                                                 value = res[field][0]
2196                                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
2197                                         upd1.append(self._columns[field]._symbol_set[1](value))
2198                                 upd1.append(res['id'])
2199                                 cr.execute('update "' + self._table + '" set ' + \
2200                                                 string.join(upd0, ',') + ' where id = %d', upd1)
2201                 return True
2202
2203         #
2204         # TODO: Validate
2205         #
2206         def perm_write(self, cr, user, ids, fields, context=None):
2207                 raise 'This method does not exist anymore'
2208
2209         # TODO: ameliorer avec NULL
2210         def _where_calc(self, cr, user, args, active_test=True, context=None):
2211                 if not context:
2212                         context={}
2213                 args = args[:]
2214                 # if the object has a field named 'active', filter out all inactive
2215                 # records unless they were explicitely asked for
2216                 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2217                         i = 0
2218                         active_found = False
2219                         while i<len(args):
2220                                 if args[i][0]=='active':
2221                                         active_found = True
2222                                 i += 1
2223                         if not active_found:
2224                                 args.append(('active', '=', 1))
2225
2226                 i = 0
2227                 tables=['"'+self._table+'"']
2228                 joins=[]
2229                 while i<len(args):
2230                         table=self
2231                         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],)
2232                         if args[i][1] == 'inselect':
2233                                 raise except_orm('ValidateError',
2234                                                 'The clause \'inselect\' can not be used outside the orm!')
2235                         if args[i][0] in self._inherit_fields:
2236                                 table=self.pool.get(self._inherit_fields[args[i][0]][0])
2237                                 if ('"'+table._table+'"' not in tables):
2238                                         tables.append('"'+table._table+'"')
2239                                         joins.append(('id', 'join', '%s.%s' % (self._table, self._inherits[table._name]), table))
2240                         fargs = args[i][0].split('.', 1)
2241                         field = table._columns.get(fargs[0],False)
2242                         if not field:
2243                                 if args[i][0] == 'id' and args[i][1] == 'child_of':
2244                                         ids2 = args[i][2]
2245                                         def _rec_get(ids, table, parent):
2246                                                 if not ids:
2247                                                         return []
2248                                                 ids2 = table.search(cr, user, [(parent, 'in', ids)], context=context)
2249                                                 return ids + _rec_get(ids2, table, parent)
2250                                         args[i] = (args[i][0], 'in', ids2 + _rec_get(ids2, table, table._parent_name), table)
2251                                 i+=1
2252                                 continue
2253                         if len(fargs) > 1:
2254                                 if field._type == 'many2one':
2255                                         args[i] = (fargs[0], 'in', self.pool.get(field._obj).search(cr, user, [(fargs[1], args[i][1], args[i][2])], context=context))
2256                                         i+=1
2257                                         continue
2258                                 else:
2259                                         i+=1
2260                                         continue
2261                         if field._properties:
2262                                 arg = [args.pop(i)]
2263                                 j = i
2264                                 while j<len(args):
2265                                         if args[j][0]==arg[0][0]:
2266                                                 arg.append(args.pop(j))
2267                                         else:
2268                                                 j+=1
2269                                 if field._fnct_search:
2270                                         args.extend(field.search(cr, user, self, arg[0][0], arg))
2271                                 if field.store:
2272                                         args.extend(arg)
2273                                 i+=1
2274                         elif field._type=='one2many':
2275                                 field_obj = self.pool.get(field._obj)
2276
2277                                 if isinstance(args[i][2], basestring):
2278                                         # get the ids of the records of the "distant" resource
2279                                         ids2 = [x[0] for x in field_obj.name_search(cr, user, args[i][2], [], args[i][1])]
2280                                 else:
2281                                         ids2 = args[i][2]
2282                                 if not ids2:
2283                                         args[i] = ('id','=','0')
2284                                 else:
2285                                         ids3 = []
2286                                         for j in range((len(ids2) / ID_MAX) + (len(ids2) % ID_MAX)):
2287                                                 sub_ids2 = ids2[ID_MAX * j:ID_MAX * (j + 1)]
2288                                                 if sub_ids2:
2289                                                         cr.execute('SELECT "'+field._fields_id+'" ' \
2290                                                                         'FROM "'+field_obj._table+'" ' \
2291                                                                         'WHERE id in ('+','.join(map(str,sub_ids2))+')')
2292                                                         ids3.extend([x[0] for x in cr.fetchall()])
2293
2294                                         args[i] = ('id', 'in', ids3)
2295                                 i+=1
2296
2297                         elif field._type=='many2many':
2298                                 #FIXME
2299                                 if args[i][1]=='child_of':
2300                                         if isinstance(args[i][2], basestring):
2301                                                 ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
2302                                         else:
2303                                                 ids2 = args[i][2]
2304                                         def _rec_get(ids, table, parent):
2305                                                 if not ids:
2306                                                         return []
2307                                                 ids2 = table.search(cr, user, [(parent, 'in', ids)], context=context)
2308                                                 return ids + _rec_get(ids2, table, parent)
2309                                         def _rec_convert(ids):
2310                                                 if self.pool.get(field._obj)==self:
2311                                                         return ids
2312                                                 if not len(ids): return []
2313                                                 cr.execute('select "'+field._id1+'" from "'+field._rel+'" where "'+field._id2+'" in ('+','.join(map(str,ids))+')')
2314                                                 ids = [x[0] for x in cr.fetchall()]
2315                                                 return ids
2316                                         args[i] = ('id','in',_rec_convert(ids2+_rec_get(ids2, self.pool.get(field._obj), table._parent_name)))
2317                                 else:
2318                                         if isinstance(args[i][2], basestring):
2319                                                 res_ids = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])]
2320                                         else:
2321                                                 res_ids = args[i][2]
2322                                         if not len(res_ids):
2323                                                 args[i] = ('id', 'in', [0])
2324                                         else:
2325                                                 cr.execute('select "'+field._id1+'" from "'+field._rel+'" where "'+field._id2+'" in ('+','.join(map(str, res_ids))+')')
2326                                                 args[i] = ('id', 'in', map(lambda x: x[0], cr.fetchall()))
2327                                 i+=1
2328
2329                         elif field._type=='many2one':
2330                                 if args[i][1]=='child_of':
2331                                         if isinstance(args[i][2], basestring):
2332                                                 ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
2333                                         else:
2334                                                 ids2 = args[i][2]
2335                                         def _rec_get(ids, table, parent):
2336                                                 if not ids:
2337                                                         return []
2338                                                 ids2 = table.search(cr, user, [(parent, 'in', ids)], context=context)
2339                                                 return ids + _rec_get(ids2, table, parent)
2340                                         if field._obj <> table._name:
2341                                                 args[i] = (args[i][0],'in',ids2+_rec_get(ids2, self.pool.get(field._obj), table._parent_name), table)
2342                                         else:
2343                                                 args[i] = ('id','in',ids2+_rec_get(ids2, table, args[i][0]), table)
2344                                 else:
2345                                         if isinstance(args[i][2], basestring):
2346                                                 res_ids = self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])
2347                                                 args[i] = (args[i][0],'in',map(lambda x: x[0], res_ids), table)
2348                                         else:
2349                                                 args[i] += (table,)
2350                                 i+=1
2351                         else:
2352                                 if field.translate:
2353                                         if args[i][1] in ('like', 'ilike', 'not like', 'not ilike'):
2354                                                 args[i] = (args[i][0], args[i][1], '%%%s%%' % args[i][2])
2355                                         query1 = '(SELECT res_id FROM ir_translation ' \
2356                                                         'WHERE name = %s AND lang = %s ' \
2357                                                                 'AND type = %s ' \
2358                                                                 'AND VALUE ' + args[i][1] + ' %s)'
2359                                         query2 = [table._name + ',' + args[i][0],
2360                                                         context.get('lang', False) or 'en_US',
2361                                                         'model',
2362                                                         args[i][2]]
2363                                         query1 += ' UNION '
2364                                         query1 += '(SELECT id FROM "' + table._table + '" ' \
2365                                                         'WHERE "' + args[i][0] + '" ' + args[i][1] + ' %s)'
2366                                         query2 += [args[i][2]]
2367                                         args[i] = ('id', 'inselect', (query1, query2), table)
2368                                 else:
2369                                         args[i] += (table,)
2370                                 i+=1
2371                 args.extend(joins)
2372
2373                 qu1, qu2 = [], []
2374                 for x in args:
2375                         table=self
2376                         if len(x) > 3:
2377                                 table=x[3]
2378                         if x[1] == 'inselect':
2379                                 qu1.append('(%s.%s in (%s))' % (table._table, x[0], x[2][0]))
2380                                 qu2 += x[2][1]
2381                         elif x[1]=='not in':
2382                                         if len(x[2])>0:
2383                                                 todel = []
2384                                                 for xitem in range(len(x[2])):
2385                                                         if x[2][xitem]==False and isinstance(x[2][xitem],bool):
2386                                                                 todel.append(xitem)
2387                                                 for xitem in todel[::-1]:
2388                                                         del x[2][xitem]
2389                                                 if x[0]=='id':
2390                                                         qu1.append('(%s.id not in (%s))' % (table._table, ','.join(['%d'] * len(x[2])),))
2391                                                 else:
2392                                                         qu1.append('(%s.%s not in (%s))' % (table._table, x[0], ','.join([table._columns[x[0]]._symbol_set[0]]*len(x[2]))))
2393                                                 if todel:
2394                                                         qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
2395                                                 qu2+=x[2]
2396                                         else:
2397                                                 qu1.append(' (1=0)')
2398                         elif x[1] != 'in':
2399 #FIXME: this replace all (..., '=', False) values with 'is null' and this is
2400 # not what we want for real boolean fields. The problem is, we can't change it
2401 # easily because we use False everywhere instead of None
2402 # NOTE FAB: we can't use None because it is not accepted by XML-RPC, that's why
2403 # boolean (0-1), None -> False
2404 # Ged> boolean fields are not always = 0 or 1
2405                                 if (x[2] is False) and (x[1]=='='):
2406                                         qu1.append(x[0]+' is null')
2407                                 elif (x[2] is False) and (x[1]=='<>' or x[1]=='!='):
2408                                         qu1.append(x[0]+' is not null')
2409                                 else:
2410                                         if x[0]=='id':
2411                                                 if x[1]=='join':
2412                                                         qu1.append('(%s.%s = %s)' % (table._table, x[0], x[2]))
2413                                                 else:
2414                                                         qu1.append('(%s.%s %s %%s)' % (table._table, x[0], x[1]))
2415                                                         qu2.append(x[2])
2416                                         else:
2417                                                 add_null = False
2418                                                 if x[1] in ('like', 'ilike', 'not like', 'not ilike'):
2419                                                         if isinstance(x[2], str):
2420                                                                 str_utf8 = x[2]
2421                                                         elif isinstance(x[2], unicode):
2422                                                                 str_utf8 = x[2].encode('utf-8')
2423                                                         else:
2424                                                                 str_utf8 = str(x[2])
2425                                                         qu2.append('%%%s%%' % str_utf8)
2426                                                         if not str_utf8:
2427                                                                 add_null = True
2428                                                 else:
2429                                                         if x[0] in table._columns:
2430                                                                 qu2.append(table._columns[x[0]]._symbol_set[1](x[2]))
2431                                                 if x[1]=='=like':
2432                                                         x1 = 'like'
2433                                                 else:
2434                                                         x1 = x[1]
2435                                                 if x[0] in table._columns:
2436                                                         if x[1] in ('like', 'ilike', 'not like', 'not ilike'):
2437
2438                                                                 qu1.append('(%s.%s %s %s)' % (table._table,
2439                                                                         x[0], x1, '%s'))
2440                                                         else:
2441                                                                 qu1.append('(%s.%s %s %s)' % (table._table,
2442                                                                         x[0], x1,
2443                                                                         table._columns[x[0]]._symbol_set[0]))
2444                                                 else:
2445                                                         qu1.append('(%s.%s %s \'%s\')' % (table._table, x[0], x1, x[2]))
2446
2447                                                 if add_null:
2448                                                         qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
2449                         elif x[1]=='in':
2450                                 if len(x[2])>0:
2451                                         todel = []
2452                                         for xitem in range(len(x[2])):
2453                                                 if x[2][xitem]==False and isinstance(x[2][xitem],bool):
2454                                                         todel.append(xitem)
2455                                         for xitem in todel[::-1]:
2456                                                 del x[2][xitem]
2457                                         #TODO fix max_stack_depth
2458                                         if x[0]=='id':
2459                                                 qu1.append('(%s.id in (%s))' % (table._table, ','.join(['%d'] * len(x[2])),))
2460                                         else:
2461                                                 qu1.append('(%s.%s in (%s))' % (table._table, x[0], ','.join([table._columns[x[0]]._symbol_set[0]]*len(x[2]))))
2462                                         if todel:
2463                                                 qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
2464                                         qu2+=x[2]
2465                                 else:
2466                                         qu1.append(' (1=0)')
2467                 return (qu1,qu2,tables)
2468
2469         def _check_qorder(self, word):
2470                 if not regex_order.match(word):
2471                         raise except_orm('AccessError', 'Bad query.')
2472                 return True
2473
2474         def search(self, cr, user, args, offset=0, limit=None, order=None,
2475                         context=None, count=False):
2476                 if not context:
2477                         context = {}
2478                 # compute the where, order by, limit and offset clauses
2479                 (qu1,qu2,tables) = self._where_calc(cr, user, args, context=context)
2480
2481                 if len(qu1):
2482                         qu1 = ' where '+string.join(qu1,' and ')
2483                 else:
2484                         qu1 = ''
2485
2486                 if order:
2487                         self._check_qorder(order)
2488                 order_by = order or self._order
2489
2490                 limit_str = limit and ' limit %d' % limit or ''
2491                 offset_str = offset and ' offset %d' % offset or ''
2492
2493
2494                 # construct a clause for the rules :
2495                 d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2496                 if d1:
2497                         qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
2498                         qu2 += d2
2499
2500                 if count:
2501                         cr.execute('select count(%s.id) from ' % self._table +
2502                                         ','.join(tables) +qu1 + limit_str + offset_str, qu2)
2503                         res = cr.fetchall()
2504                         return res[0][0]
2505                 # execute the "main" query to fetch the ids we were searching for
2506                 cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
2507                 res = cr.fetchall()
2508                 return [x[0] for x in res]
2509
2510         # returns the different values ever entered for one field
2511         # this is used, for example, in the client when the user hits enter on
2512         # a char field
2513         def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
2514                 if not args:
2515                         args=[]
2516                 if field in self._inherit_fields:
2517                         return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid,field,value,args,offset,limit)
2518                 else:
2519                         return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
2520
2521         def name_get(self, cr, user, ids, context=None):
2522                 if not context:
2523                         context={}
2524                 if not ids:
2525                         return []
2526                 if isinstance(ids, (int, long)):
2527                         ids = [ids]
2528                 return [(r['id'], str(r[self._rec_name])) for r in self.read(cr, user, ids,
2529                         [self._rec_name], context, load='_classic_write')]
2530
2531         def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
2532                 if not args:
2533                         args=[]
2534                 if not context:
2535                         context={}
2536                 args=args[:]
2537                 if name:
2538                         args += [(self._rec_name,operator,name)]
2539                 ids = self.search(cr, user, args, limit=limit, context=context)
2540                 res = self.name_get(cr, user, ids, context)
2541                 return res
2542
2543         def copy(self, cr, uid, id, default=None, context=None):
2544                 if not context:
2545                         context={}
2546                 if not default:
2547                         default = {}
2548                 if 'state' not in default:
2549                         if 'state' in self._defaults:
2550                                 default['state'] = self._defaults['state'](self, cr, uid, context)
2551                 data = self.read(cr, uid, [id], context=context)[0]
2552                 fields = self.fields_get(cr, uid)
2553                 for f in fields:
2554                         ftype = fields[f]['type']
2555
2556                         if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
2557                                 del data[f]
2558
2559                         if f in default:
2560                                 data[f] = default[f]
2561                         elif ftype == 'function':
2562                                 del data[f]
2563                         elif ftype == 'many2one':
2564                                 try:
2565                                         data[f] = data[f] and data[f][0]
2566                                 except:
2567                                         pass
2568                         elif ftype in ('one2many', 'one2one'):
2569                                 res = []
2570                                 rel = self.pool.get(fields[f]['relation'])
2571                                 for rel_id in data[f]:
2572                                         # the lines are first duplicated using the wrong (old)
2573                                         # parent but then are reassigned to the correct one thanks
2574                                         # to the (4, ...)
2575                                         res.append((4, rel.copy(cr, uid, rel_id, context=context)))
2576                                 data[f] = res
2577                         elif ftype == 'many2many':
2578                                 data[f] = [(6, 0, data[f])]
2579                 del data['id']
2580                 for v in self._inherits:
2581                         del data[self._inherits[v]]
2582                 return self.create(cr, uid, data)
2583
2584         def read_string(self, cr, uid, id, langs, fields=None, context=None):
2585                 if not context:
2586                         context={}
2587                 res = {}
2588                 res2 = {}
2589                 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read')
2590                 if not fields:
2591                         fields = self._columns.keys() + self._inherit_fields.keys()
2592                 for lang in langs:
2593                         res[lang] = {'code': lang}
2594                         for f in fields:
2595                                 if f in self._columns:
2596                                         res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
2597                                         if res_trans:
2598                                                 res[lang][f]=res_trans
2599                                         else:
2600                                                 res[lang][f]=self._columns[f].string
2601                 for table in self._inherits:
2602                         cols = intersect(self._inherit_fields.keys(), fields)
2603                         res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
2604                 for lang in res2:
2605                         if lang in res:
2606                                 res[lang]={'code': lang}
2607                         for f in res2[lang]:
2608                                 res[lang][f]=res2[lang][f]
2609                 return res
2610
2611         def write_string(self, cr, uid, id, langs, vals, context=None):
2612                 if not context:
2613                         context={}
2614                 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write')
2615                 for lang in langs:
2616                         for field in vals:
2617                                 if field in self._columns:
2618                                         self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field])
2619                 for table in self._inherits:
2620                         cols = intersect(self._inherit_fields.keys(), vals)
2621                         if cols:
2622                                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2623                 return True
2624
2625         def check_recursion(self, cr, uid, ids, parent=None):
2626                 if not parent:
2627                         parent = self._parent_name
2628                 ids_parent = ids[:]
2629                 while len(ids_parent):
2630                         ids_parent2 = []
2631                         for i in range((len(ids) / ID_MAX) + ((len(ids) % ID_MAX) and 1 or 0)):
2632                                 sub_ids_parent = ids_parent[ID_MAX * i:ID_MAX * (i + 1)]
2633                                 cr.execute('SELECT distinct "'+parent+'"'+
2634                                         ' FROM "'+self._table+'" ' \
2635                                         'WHERE id in ('+','.join(map(str, sub_ids_parent))+')')
2636                                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
2637                         ids_parent = ids_parent2
2638                         for i in ids_parent:
2639                                 if i in ids:
2640                                         return False
2641                 return True
2642
2643 # vim:noexpandtab:ts=4