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