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