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