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