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