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