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