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