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