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