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