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