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