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