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