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