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