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