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