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