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