430745deaa057cefd6541a8b0885e363688e1d40
[odoo/odoo.git] / bin / osv / orm.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 #
23 # Object relationnal mapping to postgresql module
24 #    . Hierarchical structure
25 #    . Constraints consistency, validations
26 #    . Object meta Data depends on its status
27 #    . Optimised processing by complex query (multiple actions at once)
28 #    . Default fields value
29 #    . Permissions optimisation
30 #    . Persistant object: DB postgresql
31 #    . Datas conversions
32 #    . Multi-level caching system
33 #    . 2 different inheritancies
34 #    . Fields:
35 #         - classicals (varchar, integer, boolean, ...)
36 #         - relations (one2many, many2one, many2many)
37 #         - functions
38 #
39 #
40
41 import calendar
42 import copy
43 import datetime
44 import logging
45 import pickle
46 import random
47 import re
48 import string
49 import sys
50 import time
51 import traceback
52 import types
53
54 import fields
55 import netsvc
56 import tools
57 from tools.translate import _
58
59 import copy
60 import sys
61
62 try:
63     from lxml import etree
64 except ImportError:
65     sys.stderr.write("ERROR: Import lxml module\n")
66     sys.stderr.write("ERROR: Try to install the python-lxml package\n")
67
68 from tools.config import config
69
70 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
71
72
73 POSTGRES_CONFDELTYPES = {
74     'RESTRICT': 'r',
75     'NO ACTION': 'a',
76     'CASCADE': 'c',
77     'SET NULL': 'n',
78     'SET DEFAULT': 'd',
79 }
80
81 def last_day_of_current_month():
82     today = datetime.date.today()
83     last_day = str(calendar.monthrange(today.year, today.month)[1])
84     return time.strftime('%Y-%m-' + last_day)
85
86 def intersect(la, lb):
87     return filter(lambda x: x in lb, la)
88
89
90 class except_orm(Exception):
91     def __init__(self, name, value):
92         self.name = name
93         self.value = value
94         self.args = (name, value)
95
96 class BrowseRecordError(Exception):
97     pass
98
99 # Readonly python database object browser
100 class browse_null(object):
101
102     def __init__(self):
103         self.id = False
104
105     def __getitem__(self, name):
106         return None
107
108     def __getattr__(self, name):
109         return None  # XXX: return self ?
110
111     def __int__(self):
112         return False
113
114     def __str__(self):
115         return ''
116
117     def __nonzero__(self):
118         return False
119
120     def __unicode__(self):
121         return u''
122
123
124 #
125 # TODO: execute an object method on browse_record_list
126 #
127 class browse_record_list(list):
128
129     def __init__(self, lst, context=None):
130         if not context:
131             context = {}
132         super(browse_record_list, self).__init__(lst)
133         self.context = context
134
135
136 class browse_record(object):
137     logger = netsvc.Logger()
138
139     def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}):
140         '''
141         table : the object (inherited from orm)
142         context : a dictionary with an optional context
143         '''
144         if not context:
145             context = {}
146         self._list_class = list_class or browse_record_list
147         self._cr = cr
148         self._uid = uid
149         self._id = id
150         self._table = table
151         self._table_name = self._table._name
152         self.__logger = logging.getLogger(
153             'osv.browse_record.' + self._table_name)
154         self._context = context
155         self._fields_process = fields_process
156
157         cache.setdefault(table._name, {})
158         self._data = cache[table._name]
159
160         if not (id and isinstance(id, (int, long,))):
161             raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
162 #        if not table.exists(cr, uid, id, context):
163 #            raise BrowseRecordError(_('Object %s does not exists') % (self,))
164
165         if id not in self._data:
166             self._data[id] = {'id': id}
167
168         self._cache = cache
169
170     def __getitem__(self, name):
171         if name == 'id':
172             return self._id
173         if name not in self._data[self._id]:
174             # build the list of fields we will fetch
175
176             # fetch the definition of the field which was asked for
177             if name in self._table._columns:
178                 col = self._table._columns[name]
179             elif name in self._table._inherit_fields:
180                 col = self._table._inherit_fields[name][2]
181             elif hasattr(self._table, str(name)):
182                 if isinstance(getattr(self._table, name), (types.MethodType, types.LambdaType, types.FunctionType)):
183                     return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv)
184                 else:
185                     return getattr(self._table, name)
186             else:
187                 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
188                     "Field '%s' does not exist in object '%s': \n%s" % (
189                         name, self, ''.join(traceback.format_exc())))
190                 raise KeyError("Field '%s' does not exist in object '%s'" % (
191                     name, self))
192
193             # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
194             if col._prefetch:
195                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
196                 ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
197                 # gen the list of inherited fields
198                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
199                 # complete the field list with the inherited fields which are classic or many2one
200                 ffields += filter(lambda x: x[1]._classic_write, inherits)
201             # otherwise we fetch only that field
202             else:
203                 ffields = [(name, col)]
204             ids = filter(lambda id: name not in self._data[id], self._data.keys())
205             # read the data
206             fffields = map(lambda x: x[0], ffields)
207             datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write")
208             if self._fields_process:
209                 lang = self._context.get('lang', 'en_US') or 'en_US'
210                 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
211                 if not lang_obj_ids:
212                     raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
213                 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
214
215                 for n, f in ffields:
216                     if f._type in self._fields_process:
217                         for d in datas:
218                             d[n] = self._fields_process[f._type](d[n])
219                             if d[n]:
220                                 d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj)
221
222
223             if not datas:
224                 # Where did those ids come from? Perhaps old entries in ir_model_dat?
225                 self.__logger.warn("No datas found for ids %s in %s",
226                                    ids, self)
227                 raise KeyError('Field %s not found in %s'%(name,self))
228             # create browse records for 'remote' objects
229             for data in datas:
230                 if len(str(data['id']).split('-')) > 1:
231                     data['id'] = int(str(data['id']).split('-')[0])
232                 new_data = {}
233                 for n, f in ffields:
234                     if f._type in ('many2one', 'one2one'):
235                         if data[n]:
236                             obj = self._table.pool.get(f._obj)
237                             compids = False
238                             if type(data[n]) in (type([]),type( (1,) )):
239                                 ids2 = data[n][0]
240                             else:
241                                 ids2 = data[n]
242                             if ids2:
243                                 # FIXME: this happen when a _inherits object
244                                 #        overwrite a field of it parent. Need
245                                 #        testing to be sure we got the right
246                                 #        object and not the parent one.
247                                 if not isinstance(ids2, browse_record):
248                                     new_data[n] = browse_record(self._cr,
249                                         self._uid, ids2, obj, self._cache,
250                                         context=self._context,
251                                         list_class=self._list_class,
252                                         fields_process=self._fields_process)
253                             else:
254                                 new_data[n] = browse_null()
255                         else:
256                             new_data[n] = browse_null()
257                     elif f._type in ('one2many', 'many2many') and len(data[n]):
258                         new_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)
259                     elif f._type in ('reference'):
260                         if data[n]:
261                             if isinstance(data[n], browse_record):
262                                 new_data[n] = data[n]
263                             else:
264                                 ref_obj, ref_id = data[n].split(',')
265                                 ref_id = long(ref_id)
266                                 obj = self._table.pool.get(ref_obj)
267                                 compids = False
268                                 new_data[n] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
269                         else:
270                             new_data[n] = browse_null()
271                     else:
272                         new_data[n] = data[n]
273                 self._data[data['id']].update(new_data)
274         if not name in self._data[self._id]:
275             #how did this happen?
276             self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
277                     "Ffields: %s, datas: %s"%(fffields, datas))
278             self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
279                     "Data: %s, Table: %s"%(self._data[self._id], self._table))
280             raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
281         return self._data[self._id][name]
282
283     def __getattr__(self, name):
284         try:
285             return self[name]
286         except KeyError, e:
287             raise AttributeError(e)
288
289     def __contains__(self, name):
290         return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
291
292     def __hasattr__(self, name):
293         return name in self
294
295     def __int__(self):
296         return self._id
297
298     def __str__(self):
299         return "browse_record(%s, %d)" % (self._table_name, self._id)
300
301     def __eq__(self, other):
302         return (self._table_name, self._id) == (other._table_name, other._id)
303
304     def __ne__(self, other):
305         return (self._table_name, self._id) != (other._table_name, other._id)
306
307     # we need to define __unicode__ even though we've already defined __str__
308     # because we have overridden __getattr__
309     def __unicode__(self):
310         return unicode(str(self))
311
312     def __hash__(self):
313         return hash((self._table_name, self._id))
314
315     __repr__ = __str__
316
317
318 def get_pg_type(f):
319     '''
320     returns a tuple
321     (type returned by postgres when the column was created, type expression to create the column)
322     '''
323
324     type_dict = {
325             fields.boolean: 'bool',
326             fields.integer: 'int4',
327             fields.integer_big: 'int8',
328             fields.text: 'text',
329             fields.date: 'date',
330             fields.time: 'time',
331             fields.datetime: 'timestamp',
332             fields.binary: 'bytea',
333             fields.many2one: 'int4',
334             }
335     if type(f) in type_dict:
336         f_type = (type_dict[type(f)], type_dict[type(f)])
337     elif isinstance(f, fields.float):
338         if f.digits:
339             f_type = ('numeric', 'NUMERIC')
340         else:
341             f_type = ('float8', 'DOUBLE PRECISION')
342     elif isinstance(f, (fields.char, fields.reference)):
343         f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
344     elif isinstance(f, fields.selection):
345         if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
346             f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
347         elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
348             f_size = -1
349         else:
350             f_size = getattr(f, 'size', None) or 16
351
352         if f_size == -1:
353             f_type = ('int4', 'INTEGER')
354         else:
355             f_type = ('varchar', 'VARCHAR(%d)' % f_size)
356     elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict:
357         t = eval('fields.'+(f._type))
358         f_type = (type_dict[t], type_dict[t])
359     elif isinstance(f, fields.function) and f._type == 'float':
360         if f.digits:
361             f_type = ('numeric', 'NUMERIC')
362         else:
363             f_type = ('float8', 'DOUBLE PRECISION')
364     elif isinstance(f, fields.function) and f._type == 'selection':
365         f_type = ('text', 'text')
366     elif isinstance(f, fields.function) and f._type == 'char':
367         f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
368     else:
369         logger = netsvc.Logger()
370         logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
371         f_type = None
372     return f_type
373
374
375 class orm_template(object):
376     _name = None
377     _columns = {}
378     _constraints = []
379     _defaults = {}
380     _rec_name = 'name'
381     _parent_name = 'parent_id'
382     _parent_store = False
383     _parent_order = False
384     _date_name = 'date'
385     _order = 'id'
386     _sequence = None
387     _description = None
388     _inherits = {}
389     _table = None
390     _invalids = set()
391
392     CONCURRENCY_CHECK_FIELD = '__last_update'
393
394     def view_init(self, cr , uid , fields_list, context=None):
395         """Override this method to do specific things when a view on the object is opened."""
396         pass
397
398     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
399         raise _('The read_group method is not implemented on this object !')
400
401     def _field_create(self, cr, context={}):
402         cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
403         if not cr.rowcount:
404             cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
405             model_id = cr.fetchone()[0]
406             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'))
407         else:
408             model_id = cr.fetchone()[0]
409         if 'module' in context:
410             name_id = 'model_'+self._name.replace('.','_')
411             cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module']))
412             if not cr.rowcount:
413                 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
414                     (name_id, context['module'], 'ir.model', model_id)
415                 )
416
417         cr.commit()
418
419         cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
420         cols = {}
421         for rec in cr.dictfetchall():
422             cols[rec['name']] = rec
423
424         for (k, f) in self._columns.items():
425             vals = {
426                 'model_id': model_id,
427                 'model': self._name,
428                 'name': k,
429                 'field_description': f.string.replace("'", " "),
430                 'ttype': f._type,
431                 'relation': f._obj or 'NULL',
432                 'view_load': (f.view_load and 1) or 0,
433                 'select_level': tools.ustr(f.select or 0),
434                 'readonly':(f.readonly and 1) or 0,
435                 'required':(f.required and 1) or 0,
436                 'selectable' : (f.selectable and 1) or 0,
437                 'relation_field': (f._type=='one2many' and isinstance(f,fields.one2many)) and f._fields_id or '',
438             }
439             # When its a custom field,it does not contain f.select
440             if context.get('field_state','base') == 'manual':
441                 if context.get('field_name','') == k:
442                     vals['select_level'] = context.get('select','0')
443                 #setting value to let the problem NOT occur next time
444                 else:
445                     vals['select_level'] = cols[k]['select_level']
446
447             if k not in cols:
448                 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
449                 id = cr.fetchone()[0]
450                 vals['id'] = id
451                 cr.execute("""INSERT INTO ir_model_fields (
452                     id, model_id, model, name, field_description, ttype,
453                     relation,view_load,state,select_level,relation_field
454                 ) VALUES (
455                     %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
456                 )""", (
457                     id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
458                      vals['relation'], bool(vals['view_load']), 'base',
459                     vals['select_level'],vals['relation_field']
460                 ))
461                 if 'module' in context:
462                     name1 = 'field_' + self._table + '_' + k
463                     cr.execute("select name from ir_model_data where name=%s", (name1,))
464                     if cr.fetchone():
465                         name1 = name1 + "_" + str(id)
466                     cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
467                         (name1, context['module'], 'ir.model.fields', id)
468                     )
469             else:
470                 for key, val in vals.items():
471                     if cols[k][key] != vals[key]:
472                         cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
473                         cr.commit()
474                         cr.execute("""UPDATE ir_model_fields SET
475                             model_id=%s, field_description=%s, ttype=%s, relation=%s,
476                             view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
477                         WHERE
478                             model=%s AND name=%s""", (
479                                 vals['model_id'], vals['field_description'], vals['ttype'],
480                                 vals['relation'], bool(vals['view_load']),
481                                 vals['select_level'], bool(vals['readonly']),bool(vals['required']),bool(vals['selectable']),vals['relation_field'],vals['model'], vals['name']
482                             ))
483                         continue
484         cr.commit()
485
486     def _auto_init(self, cr, context={}):
487         self._field_create(cr, context)
488
489     def __init__(self, cr):
490         if not self._name and not hasattr(self, '_inherit'):
491             name = type(self).__name__.split('.')[0]
492             msg = "The class %s has to have a _name attribute" % name
493
494             logger = netsvc.Logger()
495             logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
496             raise except_orm('ValueError', msg )
497
498         if not self._description:
499             self._description = self._name
500         if not self._table:
501             self._table = self._name.replace('.', '_')
502
503     def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
504         if not context:
505             context = {}
506         self._list_class = list_class or browse_record_list
507         cache = {}
508         # need to accepts ints and longs because ids coming from a method
509         # launched by button in the interface have a type long...
510         if isinstance(select, (int, long)):
511             return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
512         elif isinstance(select, list):
513             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)
514         else:
515             return browse_null()
516
517     def __export_row(self, cr, uid, row, fields, context=None):
518
519         def check_type(field_type):
520             if field_type == 'float':
521                 return 0.0
522             elif field_type == 'integer':
523                 return 0
524             elif field_type == 'boolean':
525                 return False
526             return ''
527
528         def selection_field(in_field):
529             col_obj = self.pool.get(in_field.keys()[0])
530             if f[i] in col_obj._columns.keys():
531                 return  col_obj._columns[f[i]]
532             elif f[i] in col_obj._inherits.keys():
533                 selection_field(col_obj._inherits)
534             else:
535                 return False
536
537
538         lines = []
539         data = map(lambda x: '', range(len(fields)))
540         done = []
541         for fpos in range(len(fields)):
542             f = fields[fpos]
543             if f:
544                 r = row
545                 i = 0
546                 while i < len(f):
547                     if f[i] == 'db_id':
548                         r = r['id']
549                     elif f[i] == 'id':
550                         model_data = self.pool.get('ir.model.data')
551                         data_ids = model_data.search(cr, uid, [('model','=',r._table_name),('res_id','=',r['id'])])
552                         if len(data_ids):
553                             d = model_data.read(cr, uid, data_ids, ['name','module'])[0]
554                             if d['module']:
555                                 r = '%s.%s'%(d['module'],d['name'])
556                             else:
557                                 r = d['name']
558                         else:
559                             break
560                     else:
561                         r = r[f[i]]
562                         # To display external name of selection field when its exported
563                         if not context.get('import_comp',False):# Allow external name only if its not import compatible
564                             cols = False
565                             if f[i] in self._columns.keys():
566                                 cols = self._columns[f[i]]
567                             elif f[i] in self._inherit_fields.keys():
568                                 cols = selection_field(self._inherits)
569                             if cols and cols._type == 'selection':
570                                 sel_list = cols.selection
571                                 if type(sel_list) == type([]):
572                                     r = [x[1] for x in sel_list if r==x[0]][0]
573
574                     if not r:
575                         if f[i] in self._columns:
576                             r = check_type(self._columns[f[i]]._type)
577                         elif f[i] in self._inherit_fields:
578                             r = check_type(self._inherit_fields[f[i]][2]._type)
579                         data[fpos] = r
580                         break
581                     if isinstance(r, (browse_record_list, list)):
582                         first = True
583                         fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
584                                 or [], fields)
585                         if fields2 in done:
586                             break
587                         done.append(fields2)
588                         for row2 in r:
589                             lines2 = self.__export_row(cr, uid, row2, fields2,
590                                     context)
591                             if first:
592                                 for fpos2 in range(len(fields)):
593                                     if lines2 and lines2[0][fpos2]:
594                                         data[fpos2] = lines2[0][fpos2]
595                                 if not data[fpos]:
596                                     dt = ''
597                                     for rr in r :
598                                         if isinstance(rr.name, browse_record):
599                                             rr = rr.name
600                                         rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id])
601                                         rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
602                                         dt += tools.ustr(rr_name or '') + ','
603                                     data[fpos] = dt[:-1]
604                                     break
605                                 lines += lines2[1:]
606                                 first = False
607                             else:
608                                 lines += lines2
609                         break
610                     i += 1
611                 if i == len(f):
612                     if isinstance(r, browse_record):
613                         r = self.pool.get(r._table_name).name_get(cr, uid, [r.id])
614                         r = r and r[0] and r[0][1] or ''
615                     data[fpos] = tools.ustr(r or '')
616         return [data] + lines
617
618     def export_data(self, cr, uid, ids, fields_to_export, context=None):
619         if not context:
620             context = {}
621         imp_comp = context.get('import_comp',False)
622         cols = self._columns.copy()
623         for f in self._inherit_fields:
624             cols.update({f: self._inherit_fields[f][2]})
625         fields_to_export = map(lambda x: x.split('/'), fields_to_export)
626         fields_export = fields_to_export+[]
627         warning = ''
628         warning_fields = []
629         for field in fields_export:
630             if imp_comp and len(field)>1:
631                 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
632             elif len (field) <=1:
633                 if imp_comp and cols.get(field and field[0],False):
634                     if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
635                                      or isinstance(cols[field[0]], fields.related)\
636                                      or isinstance(cols[field[0]], fields.one2many)):
637                         warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
638         datas = []
639         if imp_comp and len(warning_fields):
640             warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' %('\n'.join(warning_fields))
641             cr.rollback()
642             return {'warning' : warning}
643         for row in self.browse(cr, uid, ids, context):
644             datas += self.__export_row(cr, uid, row, fields_to_export, context)
645         return {'datas':datas}
646
647     def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
648         if not context:
649             context = {}
650         fields = map(lambda x: x.split('/'), fields)
651         logger = netsvc.Logger()
652         ir_model_data_obj = self.pool.get('ir.model.data')
653
654         def _check_db_id(self, model_name, db_id):
655             obj_model = self.pool.get(model_name)
656             ids = obj_model.search(cr, uid, [('id','=',int(db_id))])
657             if not len(ids):
658                 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
659             return True
660
661         def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
662             line = datas[position]
663             row = {}
664             translate = {}
665             todo = []
666             warning = []
667             data_id = False
668             data_res_id = False
669             is_xml_id = False
670             is_db_id = False
671             ir_model_data_obj = self.pool.get('ir.model.data')
672             #
673             # Import normal fields
674             #
675             for i in range(len(fields)):
676                 if i >= len(line):
677                     raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
678                 if not line[i]:
679                     continue
680
681                 field = fields[i]
682                 if prefix and not prefix[0] in field:
683                     continue
684
685                 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
686                         # Database ID
687                         res = False
688                         if line[i]:
689                             field_name = field[0].split(':')[0]
690                             model_rel =  fields_def[field_name]['relation']
691
692                             if fields_def[field[len(prefix)][:-6]]['type']=='many2many':
693                                 res_id = []
694                                 for db_id in line[i].split(config.get('csv_internal_sep')):
695                                     try:
696                                         _check_db_id(self, model_rel, db_id)
697                                         res_id.append(db_id)
698                                     except Exception,e:
699                                         warning += [tools.exception_to_unicode(e)]
700                                         logger.notifyChannel("import", netsvc.LOG_ERROR,
701                                                   tools.exception_to_unicode(e))
702                                 if len(res_id):
703                                     res = [(6, 0, res_id)]
704                             else:
705                                 try:
706                                     _check_db_id(self, model_rel, line[i])
707                                     res = line[i]
708                                 except Exception,e:
709                                     warning += [tools.exception_to_unicode(e)]
710                                     logger.notifyChannel("import", netsvc.LOG_ERROR,
711                                               tools.exception_to_unicode(e))
712                         row[field_name] = res or False
713                         continue
714
715                 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
716                     res_id = False
717                     if line[i]:
718                         if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
719                             res_id = []
720                             for word in line[i].split(config.get('csv_internal_sep')):
721                                 if '.' in word:
722                                     module, xml_id = word.rsplit('.', 1)
723                                 else:
724                                     module, xml_id = current_module, word
725                                 id = ir_model_data_obj._get_id(cr, uid, module,
726                                         xml_id)
727                                 res_id2 = ir_model_data_obj.read(cr, uid, [id],
728                                         ['res_id'])[0]['res_id']
729                                 if res_id2:
730                                     res_id.append(res_id2)
731                             if len(res_id):
732                                 res_id = [(6, 0, res_id)]
733                         else:
734                             if '.' in line[i]:
735                                 module, xml_id = line[i].rsplit('.', 1)
736                             else:
737                                 module, xml_id = current_module, line[i]
738                             id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
739
740                             res_res_id = ir_model_data_obj.read(cr, uid, [id],
741                                                 ['res_id'])
742                             if res_res_id:
743                                 res_id = res_res_id[0]['res_id']
744                     row[field[-1][:-3]] = res_id or False
745                     continue
746                 if (len(field) == len(prefix)+1) and \
747                         len(field[len(prefix)].split(':lang=')) == 2:
748                     f, lang = field[len(prefix)].split(':lang=')
749                     translate.setdefault(lang, {})[f]=line[i] or False
750                     continue
751                 if (len(field) == len(prefix)+1) and \
752                         (prefix == field[0:len(prefix)]):
753                     if field[len(prefix)] == "id":
754                         # XML ID
755                         db_id = False
756                         is_xml_id = data_id = line[i]
757                         d =  data_id.split('.')
758                         module = len(d)>1 and d[0] or ''
759                         name = len(d)>1 and d[1] or d[0]
760                         data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('name','=',name)])
761                         if len(data_ids):
762                             d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
763                             db_id = d['res_id']
764                         if is_db_id and not db_id:
765                            data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('res_id','=',is_db_id)])
766                            if not len(data_ids):
767                                ir_model_data_obj.create(cr, uid, {'module':module, 'model':model_name, 'name':name, 'res_id':is_db_id})
768                                db_id = is_db_id
769                         if is_db_id and int(db_id) != int(is_db_id):
770                             warning += [_("Id is not the same than existing one: %s")%(is_db_id)]
771                             logger.notifyChannel("import", netsvc.LOG_ERROR,
772                                     _("Id is not the same than existing one: %s")%(is_db_id))
773                         continue
774
775                     if field[len(prefix)] == "db_id":
776                         # Database ID
777                         try:
778                             _check_db_id(self, model_name, line[i])
779                             data_res_id = is_db_id = int(line[i])
780                         except Exception,e:
781                             warning += [tools.exception_to_unicode(e)]
782                             logger.notifyChannel("import", netsvc.LOG_ERROR,
783                                       tools.exception_to_unicode(e))
784                             continue
785                         data_ids = ir_model_data_obj.search(cr, uid, [('model','=',model_name),('res_id','=',line[i])])
786                         if len(data_ids):
787                             d = ir_model_data_obj.read(cr, uid, data_ids, ['name','module'])[0]
788                             data_id = d['name']
789                             if d['module']:
790                                 data_id = '%s.%s'%(d['module'],d['name'])
791                             else:
792                                 data_id = d['name']
793                         if is_xml_id and not data_id:
794                             data_id = is_xml_id
795                         if is_xml_id and is_xml_id!=data_id:
796                             warning += [_("Id is not the same than existing one: %s")%(line[i])]
797                             logger.notifyChannel("import", netsvc.LOG_ERROR,
798                                     _("Id is not the same than existing one: %s")%(line[i]))
799
800                         continue
801                     if fields_def[field[len(prefix)]]['type'] == 'integer':
802                         res = line[i] and int(line[i])
803                     elif fields_def[field[len(prefix)]]['type'] == 'boolean':
804                         res = line[i].lower() not in ('0', 'false', 'off')
805                     elif fields_def[field[len(prefix)]]['type'] == 'float':
806                         res = line[i] and float(line[i])
807                     elif fields_def[field[len(prefix)]]['type'] == 'selection':
808                         res = False
809                         if isinstance(fields_def[field[len(prefix)]]['selection'],
810                                 (tuple, list)):
811                             sel = fields_def[field[len(prefix)]]['selection']
812                         else:
813                             sel = fields_def[field[len(prefix)]]['selection'](self,
814                                     cr, uid, context)
815                         for key, val in sel:
816                             if line[i] in [tools.ustr(key),tools.ustr(val)]: #Acepting key or value for selection field
817                                 res = key
818                                 break
819                         if line[i] and not res:
820                             logger.notifyChannel("import", netsvc.LOG_WARNING,
821                                     _("key '%s' not found in selection field '%s'") % \
822                                             (line[i], field[len(prefix)]))
823
824                             warning += [_("Key/value '%s' not found in selection field '%s'")%(line[i],field[len(prefix)])]
825
826                     elif fields_def[field[len(prefix)]]['type']=='many2one':
827                         res = False
828                         if line[i]:
829                             relation = fields_def[field[len(prefix)]]['relation']
830                             res2 = self.pool.get(relation).name_search(cr, uid,
831                                     line[i], [], operator='=', context=context)
832                             res = (res2 and res2[0][0]) or False
833                             if not res:
834                                 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
835                                 logger.notifyChannel("import", netsvc.LOG_WARNING,
836                                         _("Relation not found: %s on '%s'")%(line[i],relation))
837                     elif fields_def[field[len(prefix)]]['type']=='many2many':
838                         res = []
839                         if line[i]:
840                             relation = fields_def[field[len(prefix)]]['relation']
841                             for word in line[i].split(config.get('csv_internal_sep')):
842                                 res2 = self.pool.get(relation).name_search(cr,
843                                         uid, word, [], operator='=', context=context)
844                                 res3 = (res2 and res2[0][0]) or False
845                                 if not res3:
846                                     warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
847                                     logger.notifyChannel("import",
848                                             netsvc.LOG_WARNING,
849                                             _("Relation not found: %s on '%s'")%(line[i],relation))
850                                 else:
851                                     res.append(res3)
852                             if len(res):
853                                 res = [(6, 0, res)]
854                     else:
855                         res = line[i] or False
856                     row[field[len(prefix)]] = res
857                 elif (prefix==field[0:len(prefix)]):
858                     if field[0] not in todo:
859                         todo.append(field[len(prefix)])
860             #
861             # Import one2many, many2many fields
862             #
863             nbrmax = 1
864             for field in todo:
865                 relation_obj = self.pool.get(fields_def[field]['relation'])
866                 newfd = relation_obj.fields_get(
867                         cr, uid, context=context)
868                 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
869                 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
870                 nbrmax = max(nbrmax, max2)
871                 warning = warning + w2
872                 reduce(lambda x, y: x and y, newrow)
873                 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
874                         [(0, 0, newrow)]) or []
875                 i = max2
876                 while (position+i)<len(datas):
877                     ok = True
878                     for j in range(len(fields)):
879                         field2 = fields[j]
880                         if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
881                             ok = False
882                     if not ok:
883                         break
884
885                     (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
886                             self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
887                     warning = warning+w2
888                     if reduce(lambda x, y: x or y, newrow.values()):
889                         row[field].append((0, 0, newrow))
890                     i += max2
891                     nbrmax = max(nbrmax, i)
892
893             if len(prefix)==0:
894                 for i in range(max(nbrmax, 1)):
895                     #if datas:
896                     datas.pop(0)
897             result = (row, nbrmax, warning, translate, data_id, data_res_id)
898             return result
899
900         fields_def = self.fields_get(cr, uid, context=context)
901         done = 0
902
903         initial_size = len(datas)
904         if config.get('import_partial', False) and filename:
905             data = pickle.load(file(config.get('import_partial')))
906             original_value =  data.get(filename, 0)
907         counter = 0
908         while len(datas):
909             counter += 1
910             res = {}
911             #try:
912             (res, other, warning, translate, data_id, res_id) = \
913                     process_liness(self, datas, [], current_module, self._name, fields_def)
914             if len(warning):
915                 cr.rollback()
916                 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
917
918             try:
919                 id = ir_model_data_obj._update(cr, uid, self._name,
920                      current_module, res, xml_id=data_id, mode=mode,
921                      noupdate=noupdate, res_id=res_id, context=context)
922             except Exception, e:
923                 import psycopg2
924                 import osv
925                 cr.rollback()
926                 if isinstance(e,psycopg2.IntegrityError):
927                     msg= _('Insertion Failed! ')
928                     for key in self.pool._sql_error.keys():
929                         if key in e[0]:
930                             msg = self.pool._sql_error[key]
931                             break
932                     return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
933                 if isinstance(e, osv.orm.except_orm ):
934                     msg = _('Insertion Failed! ' + e[1])
935                     return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
936                 #Raising Uncaught exception
937                 raise
938             for lang in translate:
939                 context2 = context.copy()
940                 context2['lang'] = lang
941                 self.write(cr, uid, [id], translate[lang], context2)
942             if config.get('import_partial', False) and filename and (not (counter%100)) :
943                 data = pickle.load(file(config.get('import_partial')))
944                 data[filename] = initial_size - len(datas) + original_value
945                 pickle.dump(data, file(config.get('import_partial'),'wb'))
946                 cr.commit()
947
948             #except Exception, e:
949             #    logger.notifyChannel("import", netsvc.LOG_ERROR, e)
950             #    cr.rollback()
951             #    try:
952             #        return (-1, res, e[0], warning)
953             #    except:
954             #        return (-1, res, e[0], '')
955             done += 1
956         #
957         # TODO: Send a request with the result and multi-thread !
958         #
959         return (done, 0, 0, 0)
960
961     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
962         raise _('The read method is not implemented on this object !')
963
964     def get_invalid_fields(self,cr,uid):
965         return list(self._invalids)
966
967     def _validate(self, cr, uid, ids, context=None):
968         context = context or {}
969         lng = context.get('lang', False) or 'en_US'
970         trans = self.pool.get('ir.translation')
971         error_msgs = []
972         for constraint in self._constraints:
973             fun, msg, fields = constraint
974             if not fun(self, cr, uid, ids):
975                 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
976                 error_msgs.append(
977                         _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
978                 )
979                 self._invalids.update(fields)
980         if error_msgs:
981             cr.rollback()
982             raise except_orm('ValidateError', '\n'.join(error_msgs))
983         else:
984             self._invalids.clear()
985
986     def default_get(self, cr, uid, fields_list, context=None):
987         """ Set default values for the object's fields.
988
989         Returns a dict of {field_name:default_value}
990
991         Arguments:
992         `fields_list`: the fields for which the object doesn't have
993                        any value yet, and default values need to be
994                        provided. If fields outside this list are
995                        returned, the user-provided values will be
996                        overwritten.
997         """
998         return {}
999
1000     def perm_read(self, cr, user, ids, context=None, details=True):
1001         raise _('The perm_read method is not implemented on this object !')
1002
1003     def unlink(self, cr, uid, ids, context=None):
1004         raise _('The unlink method is not implemented on this object !')
1005
1006     def write(self, cr, user, ids, vals, context=None):
1007         raise _('The write method is not implemented on this object !')
1008
1009     def create(self, cr, user, vals, context=None):
1010         raise _('The create method is not implemented on this object !')
1011
1012     # returns the definition of each field in the object
1013     # the optional fields parameter can limit the result to some fields
1014     def fields_get_keys(self, cr, user, context=None, read_access=True):
1015         if context is None:
1016             context = {}
1017         res = self._columns.keys()
1018         for parent in self._inherits:
1019             res.extend(self.pool.get(parent).fields_get_keys(cr, user, fields, context))
1020         return res
1021
1022     def fields_get(self, cr, user, fields=None, context=None, read_access=True):
1023         if context is None:
1024             context = {}
1025         res = {}
1026         translation_obj = self.pool.get('ir.translation')
1027         model_access_obj = self.pool.get('ir.model.access')
1028         for parent in self._inherits:
1029             res.update(self.pool.get(parent).fields_get(cr, user, fields, context))
1030
1031         if self._columns.keys():
1032             for f in self._columns.keys():
1033                 if fields and f not in fields:
1034                     continue
1035                 res[f] = {'type': self._columns[f]._type}
1036                 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1037                         'change_default', 'translate', 'help', 'select', 'selectable','parent_field'):
1038                     if getattr(self._columns[f], arg):
1039                         res[f][arg] = getattr(self._columns[f], arg)
1040                 if not read_access:
1041                     res[f]['readonly'] = True
1042                     res[f]['states'] = {}
1043                 for arg in ('digits', 'invisible','filters'):
1044                     if getattr(self._columns[f], arg, None):
1045                         res[f][arg] = getattr(self._columns[f], arg)
1046
1047                 #TODO: optimize
1048                 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1049                 if res_trans:
1050                     res[f]['string'] = res_trans
1051                 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1052                 if help_trans:
1053                     res[f]['help'] = help_trans
1054
1055                 if hasattr(self._columns[f], 'selection'):
1056                     if isinstance(self._columns[f].selection, (tuple, list)):
1057                         sel = self._columns[f].selection
1058                         # translate each selection option
1059                         sel2 = []
1060                         for (key, val) in sel:
1061                             val2 = None
1062                             if val:
1063                                 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1064                             sel2.append((key, val2 or val))
1065                         sel = sel2
1066                         res[f]['selection'] = sel
1067                     else:
1068                         # call the 'dynamic selection' function
1069                         res[f]['selection'] = self._columns[f].selection(self, cr,
1070                                 user, context)
1071                 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1072                     res[f]['relation'] = self._columns[f]._obj
1073                     res[f]['domain'] = self._columns[f]._domain
1074                     res[f]['context'] = self._columns[f]._context
1075         else:
1076             #TODO : read the fields from the database
1077             pass
1078
1079         if fields:
1080             # filter out fields which aren't in the fields list
1081             for r in res.keys():
1082                 if r not in fields:
1083                     del res[r]
1084         return res
1085
1086     #
1087     # Overload this method if you need a window title which depends on the context
1088     #
1089     def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1090         return False
1091
1092     def __view_look_dom(self, cr, user, node, view_id, context=None):
1093         if not context:
1094             context = {}
1095         result = False
1096         fields = {}
1097         childs = True
1098
1099         if node.tag == 'field':
1100             if node.get('name'):
1101                 attrs = {}
1102                 try:
1103                     if node.get('name') in self._columns:
1104                         column = self._columns[node.get('name')]
1105                     else:
1106                         column = self._inherit_fields[node.get('name')][2]
1107                 except:
1108                     column = False
1109
1110                 if column:
1111                     relation = column._obj
1112                     childs = False
1113                     views = {}
1114                     for f in node:
1115                         if f.tag in ('form', 'tree', 'graph'):
1116                             node.remove(f)
1117                             ctx = context.copy()
1118                             ctx['base_model_name'] = self._name
1119                             xarch, xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, view_id, ctx)
1120                             views[str(f.tag)] = {
1121                                 'arch': xarch,
1122                                 'fields': xfields
1123                             }
1124                     attrs = {'views': views}
1125                     if node.get('widget') and node.get('widget') == 'selection':
1126                         # We can not use the 'string' domain has it is defined according to the record !
1127                         dom = []
1128                         if column._domain and not isinstance(column._domain, (str, unicode)):
1129                             dom = column._domain
1130                         dom += eval(node.get('domain','[]'), {'uid':user, 'time':time})
1131                         context.update(eval(node.get('context','{}')))
1132                         attrs['selection'] = self.pool.get(relation).name_search(cr, user, '', dom, context=context)
1133                         if (node.get('required') and not int(node.get('required'))) or not column.required:
1134                             attrs['selection'].append((False,''))
1135                 fields[node.get('name')] = attrs
1136
1137         elif node.tag in ('form', 'tree'):
1138             result = self.view_header_get(cr, user, False, node.tag, context)
1139             if result:
1140                 node.set('string', result)
1141
1142         elif node.tag == 'calendar':
1143             for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1144                 if node.get(additional_field):
1145                     fields[node.get(additional_field)] = {}
1146
1147         if 'groups' in node.attrib:
1148             if node.get('groups'):
1149                 groups = node.get('groups').split(',')
1150                 readonly = False
1151                 access_pool = self.pool.get('ir.model.access')
1152                 for group in groups:
1153                     readonly = readonly or access_pool.check_groups(cr, user, group)
1154                 if not readonly:
1155                     node.set('invisible', '1')
1156             del(node.attrib['groups'])
1157
1158         # translate view
1159         if ('lang' in context) and not result:
1160             if node.get('string'):
1161                 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string').encode('utf8'))
1162                 if not trans and ('base_model_name' in context):
1163                     trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string').encode('utf8'))
1164                 if trans:
1165                     node.set('string', trans)
1166             if node.get('sum'):
1167                 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum').encode('utf8'))
1168                 if trans:
1169                     node.set('sum', trans)
1170
1171         if childs:
1172             for f in node:
1173                 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1174
1175         return fields
1176
1177     def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1178         fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1179
1180         rolesobj = self.pool.get('res.roles')
1181         usersobj = self.pool.get('res.users')
1182
1183         buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1184         for button in buttons:
1185             can_click = True
1186             if user != 1:   # admin user has all roles
1187                 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
1188                 # TODO handle the case of more than one workflow for a model
1189                 cr.execute("""SELECT DISTINCT t.role_id
1190                                 FROM wkf
1191                           INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1192                           INNER JOIN wkf_transition t ON (t.act_to = a.id)
1193                                WHERE wkf.osv = %s
1194                                  AND t.signal = %s
1195                            """, (self._name, button.get('name'),))
1196                 roles = cr.fetchall()
1197
1198                 # draft -> valid = signal_next (role X)
1199                 # draft -> cancel = signal_cancel (no role)
1200                 #
1201                 # valid -> running = signal_next (role Y)
1202                 # valid -> cancel = signal_cancel (role Z)
1203                 #
1204                 # running -> done = signal_next (role Z)
1205                 # running -> cancel = signal_cancel (role Z)
1206
1207                 # As we don't know the object state, in this scenario,
1208                 #   the button "signal_cancel" will be always shown as there is no restriction to cancel in draft
1209                 #   the button "signal_next" will be show if the user has any of the roles (X Y or Z)
1210                 # The verification will be made later in workflow process...
1211                 if roles:
1212                     can_click = any((not role) or rolesobj.check(cr, user, user_roles, role) for (role,) in roles)
1213
1214             button.set('readonly', str(int(not can_click)))
1215
1216         arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1217
1218         #code for diagram view.
1219         fields={}
1220         if node.tag=='diagram':
1221             if node.getchildren()[0].tag=='node':
1222                node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1223             if node.getchildren()[1].tag=='arrow':
1224                arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1225             for key,value in node_fields.items():
1226                 fields[key]=value
1227             for key,value in arrow_fields.items():
1228                 fields[key]=value
1229         else:
1230             fields = self.fields_get(cr, user, fields_def.keys(), context)
1231
1232         for field in fields_def:
1233             if field == 'id':
1234                 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1235                 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1236             elif field in fields:
1237                 fields[field].update(fields_def[field])
1238             else:
1239                 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))
1240                 res = cr.fetchall()[:]
1241                 model = res[0][1]
1242                 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1243                 msg = "\n * ".join([r[0] for r in res])
1244                 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1245                 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1246                 raise except_orm('View error', msg)
1247         return arch, fields
1248
1249     def __get_default_calendar_view(self):
1250         """Generate a default calendar view (For internal use only).
1251         """
1252
1253         arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1254                 '<calendar string="%s"') % (self._description)
1255
1256         if (self._date_name not in self._columns):
1257                 date_found = False
1258                 for dt in ['date','date_start','x_date','x_date_start']:
1259                     if dt in self._columns:
1260                         self._date_name = dt
1261                         date_found = True
1262                         break
1263
1264                 if not date_found:
1265                     raise except_orm(_('Invalid Object Architecture!'),_("Insufficient fields for Calendar View!"))
1266
1267         if self._date_name:
1268             arch +=' date_start="%s"' % (self._date_name)
1269
1270         for color in ["user_id","partner_id","x_user_id","x_partner_id"]:
1271             if color in self._columns:
1272                 arch += ' color="' + color + '"'
1273                 break
1274
1275         dt_stop_flag = False
1276
1277         for dt_stop in ["date_stop","date_end","x_date_stop","x_date_end"]:
1278             if dt_stop in self._columns:
1279                 arch += ' date_stop="' + dt_stop + '"'
1280                 dt_stop_flag = True
1281                 break
1282
1283         if not dt_stop_flag:
1284             for dt_delay in ["date_delay","planned_hours","x_date_delay","x_planned_hours"]:
1285                if dt_delay in self._columns:
1286                    arch += ' date_delay="' + dt_delay + '"'
1287                    break
1288
1289         arch += ('>\n'
1290                  '  <field name="%s"/>\n'
1291                  '</calendar>') % (self._rec_name)
1292
1293         return arch
1294
1295     def __get_default_search_view(self, cr, uid, context={}):
1296
1297         def encode(s):
1298             if isinstance(s, unicode):
1299                 return s.encode('utf8')
1300             return s
1301
1302         view = self.fields_view_get(cr, uid, False, 'form', context)
1303
1304         root = etree.fromstring(encode(view['arch']))
1305         res = etree.XML("<search string='%s'></search>" % root.get("string", ""))
1306         node = etree.Element("group")
1307         res.append(node)
1308
1309         fields = root.xpath("//field[@select=1]")
1310         for field in fields:
1311             node.append(field)
1312
1313         return etree.tostring(res, encoding="utf-8").replace('\t', '')
1314
1315     #
1316     # if view_id, view_type is not required
1317     #
1318     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1319         if not context:
1320             context = {}
1321
1322         def encode(s):
1323             if isinstance(s, unicode):
1324                 return s.encode('utf8')
1325             return s
1326
1327         def _inherit_apply(src, inherit):
1328             def _find(node, node2):
1329                 if node2.tag == 'xpath':
1330                     res = node.xpath(node2.get('expr'))
1331                     if res:
1332                         return res[0]
1333                     else:
1334                         return None
1335                 else:
1336                     for n in node.getiterator(node2.tag):
1337                         res = True
1338                         for attr in node2.attrib:
1339                             if attr == 'position':
1340                                 continue
1341                             if n.get(attr):
1342                                 if n.get(attr) == node2.get(attr):
1343                                     continue
1344                             res = False
1345                         if res:
1346                             return n
1347                 return None
1348
1349             # End: _find(node, node2)
1350
1351             doc_dest = etree.fromstring(encode(inherit))
1352             toparse = [ doc_dest ]
1353
1354             while len(toparse):
1355                 node2 = toparse.pop(0)
1356                 if node2.tag == 'data':
1357                     toparse += [ c for c in doc_dest ]
1358                     continue
1359                 node = _find(src, node2)
1360                 if node is not None:
1361                     pos = 'inside'
1362                     if node2.get('position'):
1363                         pos = node2.get('position')
1364                     if pos == 'replace':
1365                         parent = node.getparent()
1366                         if parent is None:
1367                             src = copy.deepcopy(node2[0])
1368                         else:
1369                             for child in node2:
1370                                 node.addprevious(child)
1371                             node.getparent().remove(node)
1372                     elif pos == 'attributes':
1373                         for child in node2.getiterator('attribute'):
1374                             attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1375                             if attribute[1]:
1376                                 node.set(attribute[0], attribute[1])
1377                             else:
1378                                 del(node.attrib[attribute[0]])
1379                     else:
1380                         sib = node.getnext()
1381                         for child in node2:
1382                             if pos == 'inside':
1383                                 node.append(child)
1384                             elif pos == 'after':
1385                                 if sib is None:
1386                                     node.addnext(child)
1387                                 else:
1388                                     sib.addprevious(child)
1389                             elif pos == 'before':
1390                                 node.addprevious(child)
1391                             else:
1392                                 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1393                 else:
1394                     attrs = ''.join([
1395                         ' %s="%s"' % (attr, node2.get(attr))
1396                         for attr in node2.attrib
1397                         if attr != 'position'
1398                     ])
1399                     tag = "<%s%s>" % (node2.tag, attrs)
1400                     raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1401             return src
1402         # End: _inherit_apply(src, inherit)
1403
1404         result = {'type': view_type, 'model': self._name}
1405
1406         ok = True
1407         model = True
1408         sql_res = False
1409         while ok:
1410             view_ref = context.get(view_type + '_view_ref', False)
1411             if view_ref:
1412                 if '.' in view_ref:
1413                     module, view_ref = view_ref.split('.', 1)
1414                     cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1415                     view_ref_res = cr.fetchone()
1416                     if view_ref_res:
1417                         view_id = view_ref_res[0]
1418
1419             if view_id:
1420                 where = (model and (" and model='%s'" % (self._name,))) or ''
1421                 cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s'+where, (view_id,))
1422             else:
1423                 cr.execute('''SELECT
1424                         arch,name,field_parent,id,type,inherit_id
1425                     FROM
1426                         ir_ui_view
1427                     WHERE
1428                         model=%s AND
1429                         type=%s AND
1430                         inherit_id IS NULL
1431                     ORDER BY priority''', (self._name, view_type))
1432             sql_res = cr.fetchone()
1433
1434             if not sql_res:
1435                 break
1436
1437             ok = sql_res[5]
1438             view_id = ok or sql_res[3]
1439             model = False
1440
1441         # if a view was found
1442         if sql_res:
1443             result['type'] = sql_res[4]
1444             result['view_id'] = sql_res[3]
1445             result['arch'] = sql_res[0]
1446
1447             def _inherit_apply_rec(result, inherit_id):
1448                 # get all views which inherit from (ie modify) this view
1449                 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1450                 sql_inherit = cr.fetchall()
1451                 for (inherit, id) in sql_inherit:
1452                     result = _inherit_apply(result, inherit)
1453                     result = _inherit_apply_rec(result, id)
1454                 return result
1455
1456             inherit_result = etree.fromstring(encode(result['arch']))
1457             result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1458
1459             result['name'] = sql_res[1]
1460             result['field_parent'] = sql_res[2] or False
1461         else:
1462
1463             # otherwise, build some kind of default view
1464             if view_type == 'form':
1465                 res = self.fields_get(cr, user, context=context)
1466                 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1467                      '<form string="%s">' % (self._description,)
1468                 for x in res:
1469                     if res[x]['type'] not in ('one2many', 'many2many'):
1470                         xml += '<field name="%s"/>' % (x,)
1471                         if res[x]['type'] == 'text':
1472                             xml += "<newline/>"
1473                 xml += "</form>"
1474
1475             elif view_type == 'tree':
1476                 _rec_name = self._rec_name
1477                 if _rec_name not in self._columns:
1478                     _rec_name = self._columns.keys()[0]
1479                 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1480                        '<tree string="%s"><field name="%s"/></tree>' \
1481                        % (self._description, self._rec_name)
1482
1483             elif view_type == 'calendar':
1484                 xml = self.__get_default_calendar_view()
1485
1486             elif view_type == 'search':
1487                 xml = self.__get_default_search_view(cr, user, context)
1488
1489             else:
1490                 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1491                 raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type)
1492             result['arch'] = etree.fromstring(encode(xml))
1493             result['name'] = 'default'
1494             result['field_parent'] = False
1495             result['view_id'] = 0
1496
1497         xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1498         result['arch'] = xarch
1499         result['fields'] = xfields
1500
1501         if submenu:
1502             if context and context.get('active_id',False):
1503                 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1504                 if data_menu:
1505                     act_id = data_menu.id
1506                     if act_id:
1507                         data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1508                         result['submenu'] = getattr(data_action,'menus', False)
1509         if toolbar:
1510             def clean(x):
1511                 x = x[2]
1512                 for key in ('report_sxw_content', 'report_rml_content',
1513                         'report_sxw', 'report_rml',
1514                         'report_sxw_content_data', 'report_rml_content_data'):
1515                     if key in x:
1516                         del x[key]
1517                 return x
1518             ir_values_obj = self.pool.get('ir.values')
1519             resprint = ir_values_obj.get(cr, user, 'action',
1520                     'client_print_multi', [(self._name, False)], False,
1521                     context)
1522             resaction = ir_values_obj.get(cr, user, 'action',
1523                     'client_action_multi', [(self._name, False)], False,
1524                     context)
1525
1526             resrelate = ir_values_obj.get(cr, user, 'action',
1527                     'client_action_relate', [(self._name, False)], False,
1528                     context)
1529             resprint = map(clean, resprint)
1530             resaction = map(clean, resaction)
1531             resaction = filter(lambda x: not x.get('multi', False), resaction)
1532             resprint = filter(lambda x: not x.get('multi', False), resprint)
1533             resrelate = map(lambda x: x[2], resrelate)
1534
1535             for x in resprint+resaction+resrelate:
1536                 x['string'] = x['name']
1537
1538             result['toolbar'] = {
1539                 'print': resprint,
1540                 'action': resaction,
1541                 'relate': resrelate
1542             }
1543         if result['type']=='form' and result['arch'].count("default_focus")>1:
1544                 msg = "Form View contain more than one default_focus attribute"
1545                 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1546                 raise except_orm('View Error !',msg)
1547         return result
1548
1549     _view_look_dom_arch = __view_look_dom_arch
1550
1551     def search_count(self, cr, user, args, context=None):
1552         if not context:
1553             context = {}
1554         res = self.search(cr, user, args, context=context, count=True)
1555         if isinstance(res, list):
1556             return len(res)
1557         return res
1558
1559     def search(self, cr, user, args, offset=0, limit=None, order=None,
1560             context=None, count=False):
1561         raise _('The search method is not implemented on this object !')
1562
1563     def name_get(self, cr, user, ids, context=None):
1564         raise _('The name_get method is not implemented on this object !')
1565
1566     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1567         raise _('The name_search method is not implemented on this object !')
1568
1569     def copy(self, cr, uid, id, default=None, context=None):
1570         raise _('The copy method is not implemented on this object !')
1571
1572     def exists(self, cr, uid, id, context=None):
1573         raise _('The exists method is not implemented on this object !')
1574
1575     def read_string(self, cr, uid, id, langs, fields=None, context=None):
1576         res = {}
1577         res2 = {}
1578         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1579         if not fields:
1580             fields = self._columns.keys() + self._inherit_fields.keys()
1581         #FIXME: collect all calls to _get_source into one SQL call.
1582         for lang in langs:
1583             res[lang] = {'code': lang}
1584             for f in fields:
1585                 if f in self._columns:
1586                     res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1587                     if res_trans:
1588                         res[lang][f] = res_trans
1589                     else:
1590                         res[lang][f] = self._columns[f].string
1591         for table in self._inherits:
1592             cols = intersect(self._inherit_fields.keys(), fields)
1593             res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1594         for lang in res2:
1595             if lang in res:
1596                 res[lang]['code'] = lang
1597             for f in res2[lang]:
1598                 res[lang][f] = res2[lang][f]
1599         return res
1600
1601     def write_string(self, cr, uid, id, langs, vals, context=None):
1602         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1603         #FIXME: try to only call the translation in one SQL
1604         for lang in langs:
1605             for field in vals:
1606                 if field in self._columns:
1607                     self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field])
1608         for table in self._inherits:
1609             cols = intersect(self._inherit_fields.keys(), vals)
1610             if cols:
1611                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1612         return True
1613
1614     def _check_removed_columns(self, cr, log=False):
1615         raise NotImplementedError()
1616
1617 class orm_memory(orm_template):
1618     _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', 'exists']
1619     _inherit_fields = {}
1620     _max_count = 200
1621     _max_hours = 1
1622     _check_time = 20
1623
1624     def __init__(self, cr):
1625         super(orm_memory, self).__init__(cr)
1626         self.datas = {}
1627         self.next_id = 0
1628         self.check_id = 0
1629         cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1630
1631     def vaccum(self, cr, uid):
1632         self.check_id += 1
1633         if self.check_id % self._check_time:
1634             return True
1635         tounlink = []
1636         max = time.time() - self._max_hours * 60 * 60
1637         for id in self.datas:
1638             if self.datas[id]['internal.date_access'] < max:
1639                 tounlink.append(id)
1640         self.unlink(cr, uid, tounlink)
1641         if len(self.datas)>self._max_count:
1642             sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1643             sorted.sort()
1644             ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1645             self.unlink(cr, uid, ids)
1646         return True
1647
1648     def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1649         if not context:
1650             context = {}
1651         if not fields_to_read:
1652             fields_to_read = self._columns.keys()
1653         result = []
1654         if self.datas:
1655             ids_orig = ids
1656             if isinstance(ids, (int, long)):
1657                 ids = [ids]
1658             for id in ids:
1659                 r = {'id': id}
1660                 for f in fields_to_read:
1661                     if id in self.datas:
1662                         r[f] = self.datas[id].get(f, False)
1663                         if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1664                             r[f] = len(r[f])
1665                 result.append(r)
1666                 if id in self.datas:
1667                     self.datas[id]['internal.date_access'] = time.time()
1668             fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1669             for f in fields_post:
1670                 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1671                 for record in result:
1672                     record[f] = res2[record['id']]
1673             if isinstance(ids_orig, (int, long)):
1674                 return result[0]
1675         return result
1676
1677     def write(self, cr, user, ids, vals, context=None):
1678         if not ids:
1679             return True
1680         vals2 = {}
1681         upd_todo = []
1682         for field in vals:
1683             if self._columns[field]._classic_write:
1684                 vals2[field] = vals[field]
1685             else:
1686                 upd_todo.append(field)
1687         for id_new in ids:
1688             self.datas[id_new].update(vals2)
1689             self.datas[id_new]['internal.date_access'] = time.time()
1690             for field in upd_todo:
1691                 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1692         self._validate(cr, user, [id_new], context)
1693         wf_service = netsvc.LocalService("workflow")
1694         wf_service.trg_write(user, self._name, id_new, cr)
1695         return id_new
1696
1697     def create(self, cr, user, vals, context=None):
1698         self.vaccum(cr, user)
1699         self.next_id += 1
1700         id_new = self.next_id
1701         default = []
1702         for f in self._columns.keys():
1703             if not f in vals:
1704                 default.append(f)
1705         if len(default):
1706             vals.update(self.default_get(cr, user, default, context))
1707         vals2 = {}
1708         upd_todo = []
1709         for field in vals:
1710             if self._columns[field]._classic_write:
1711                 vals2[field] = vals[field]
1712             else:
1713                 upd_todo.append(field)
1714         self.datas[id_new] = vals2
1715         self.datas[id_new]['internal.date_access'] = time.time()
1716
1717         for field in upd_todo:
1718             self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1719         self._validate(cr, user, [id_new], context)
1720         wf_service = netsvc.LocalService("workflow")
1721         wf_service.trg_create(user, self._name, id_new, cr)
1722         return id_new
1723
1724     def default_get(self, cr, uid, fields_list, context=None):
1725         self.view_init(cr, uid, fields_list, context)
1726         if not context:
1727             context = {}
1728         value = {}
1729         # get the default values for the inherited fields
1730         for f in fields_list:
1731             if f in self._defaults:
1732                 if callable(self._defaults[f]):
1733                     value[f] = self._defaults[f](self, cr, uid, context)
1734                 else:
1735                     value[f] = self._defaults[f]
1736
1737             fld_def = ((f in self._columns) and self._columns[f]) \
1738                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1739                     or False
1740
1741         # get the default values set by the user and override the default
1742         # values defined in the object
1743         ir_values_obj = self.pool.get('ir.values')
1744         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1745         for id, field, field_value in res:
1746             if field in fields_list:
1747                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1748                 if fld_def._type in ('many2one', 'one2one'):
1749                     obj = self.pool.get(fld_def._obj)
1750                     if not obj.search(cr, uid, [('id', '=', field_value)]):
1751                         continue
1752                 if fld_def._type in ('many2many'):
1753                     obj = self.pool.get(fld_def._obj)
1754                     field_value2 = []
1755                     for i in range(len(field_value)):
1756                         if not obj.search(cr, uid, [('id', '=',
1757                             field_value[i])]):
1758                             continue
1759                         field_value2.append(field_value[i])
1760                     field_value = field_value2
1761                 if fld_def._type in ('one2many'):
1762                     obj = self.pool.get(fld_def._obj)
1763                     field_value2 = []
1764                     for i in range(len(field_value)):
1765                         field_value2.append({})
1766                         for field2 in field_value[i]:
1767                             if obj._columns[field2]._type in ('many2one', 'one2one'):
1768                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1769                                 if not obj2.search(cr, uid,
1770                                         [('id', '=', field_value[i][field2])]):
1771                                     continue
1772                             # TODO add test for many2many and one2many
1773                             field_value2[i][field2] = field_value[i][field2]
1774                     field_value = field_value2
1775                 value[field] = field_value
1776
1777         # get the default values from the context
1778         for key in context or {}:
1779             if key.startswith('default_') and (key[8:] in fields_list):
1780                 value[key[8:]] = context[key]
1781         return value
1782
1783     def _where_calc(self, cr, user, args, active_test=True, context=None):
1784         if not context:
1785             context = {}
1786         args = args[:]
1787         res=[]
1788         # if the object has a field named 'active', filter out all inactive
1789         # records unless they were explicitely asked for
1790         if 'active' in self._columns and (active_test and context.get('active_test', True)):
1791             if args:
1792                 active_in_args = False
1793                 for a in args:
1794                     if a[0] == 'active':
1795                         active_in_args = True
1796                 if not active_in_args:
1797                     args.insert(0, ('active', '=', 1))
1798             else:
1799                 args = [('active', '=', 1)]
1800         if args:
1801             import expression
1802             e = expression.expression(args)
1803             e.parse(cr, user, self, context)
1804             res=e.__dict__['_expression__exp']
1805         return res or []
1806
1807
1808     def search(self, cr, user, args, offset=0, limit=None, order=None,
1809             context=None, count=False):
1810         if not context:
1811             context = {}
1812         result = self._where_calc(cr, user, args, context=context)
1813         if result==[]:
1814             return self.datas.keys()
1815
1816         res=[]
1817         counter=0
1818         #Find the value of dict
1819         f=False
1820         if result:
1821             for id, data in self.datas.items():
1822                 counter=counter+1
1823                 data['id']  = id
1824                 if limit and (counter >int(limit)):
1825                     break
1826                 f = True
1827                 for arg in result:
1828                      if arg[1] =='=':
1829                          val =eval('data[arg[0]]'+'==' +' arg[2]')
1830                      elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
1831                          val =eval('data[arg[0]]'+arg[1] +' arg[2]')
1832                      elif arg[1] in ['ilike']:
1833                          if str(data[arg[0]]).find(str(arg[2]))!=-1:
1834                              val= True
1835                          else:
1836                              val=False
1837
1838                      if f and val:
1839                          f = True
1840                      else:
1841                          f = False
1842                 if f:
1843                     res.append(id)
1844         if count:
1845             return len(res)
1846         return res or []
1847
1848     def unlink(self, cr, uid, ids, context=None):
1849         for id in ids:
1850             if id in self.datas:
1851                 del self.datas[id]
1852         if len(ids):
1853             cr.execute('delete from wkf_instance where res_type=%s and res_id = ANY  (%s)', (self._name,ids))
1854         return True
1855
1856     def perm_read(self, cr, user, ids, context=None, details=True):
1857         result = []
1858         for id in ids:
1859             result.append({
1860                 'create_uid': (user, 'Root'),
1861                 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'),
1862                 'write_uid': False,
1863                 'write_date': False,
1864                 'id': id
1865             })
1866         return result
1867
1868     def _check_removed_columns(self, cr, log=False):
1869         # nothing to check in memory...
1870         pass
1871
1872     def exists(self, cr, uid, id, context=None):
1873         return id in self.datas
1874
1875 class orm(orm_template):
1876     _sql_constraints = []
1877     _table = None
1878     _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', 'exists']
1879
1880     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
1881         groupby_list = groupby
1882         groupby = groupby[0]
1883         context = context or {}
1884         self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
1885         if not fields:
1886             fields = self._columns.keys()
1887
1888         (where_clause, where_params, tables) = self._where_calc(cr, uid, domain, context=context)
1889         dom = self.pool.get('ir.rule').domain_get(cr, uid, self._name, 'read', context=context)
1890         where_clause = where_clause + dom[0]
1891         where_params = where_params + dom[1]
1892         for t in dom[2]:
1893             if t not in tables:
1894                 tables.append(t)
1895
1896         # Take care of adding join(s) if groupby is an '_inherits'ed field
1897         tables, where_clause = self._inherits_join_calc(groupby,tables,where_clause)
1898
1899         if len(where_clause):
1900             where_clause = ' where '+string.join(where_clause, ' and ')
1901         else:
1902             where_clause = ''
1903         limit_str = limit and ' limit %d' % limit or ''
1904         offset_str = offset and ' offset %d' % offset or ''
1905
1906         fget = self.fields_get(cr, uid, fields)
1907         float_int_fields = filter(lambda x: fget[x]['type'] in ('float','integer'), fields)
1908         sum = {}
1909
1910         group_by = groupby
1911         if fget.get(groupby,False) and fget[groupby]['type'] in ('date','datetime'):
1912             flist = "to_char(%s,'yyyy-mm') as %s "%(groupby,groupby)
1913             groupby = "to_char(%s,'yyyy-mm')"%(groupby)
1914         else:
1915             flist = groupby
1916
1917         fields_pre = [f for f in float_int_fields if
1918                    f == self.CONCURRENCY_CHECK_FIELD
1919                 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
1920         for f in fields_pre:
1921             if f not in ['id','sequence']:
1922                 operator = fget[f].get('group_operator','sum')
1923                 flist += ','+operator+'('+f+') as '+f
1924
1925         cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + ' group by '+ groupby + limit_str + offset_str, where_params)
1926         alldata = {}
1927         groupby = group_by
1928         for r in cr.dictfetchall():
1929             for fld,val in r.items():
1930                 if val == None:r[fld] = False
1931             alldata[r['id']] = r
1932             del r['id']
1933         data = self.read(cr, uid, alldata.keys(), [groupby], context=context)
1934         today = datetime.date.today()
1935
1936         for d in data:
1937             d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
1938             d['__context'] = {'group_by':groupby_list[1:]}
1939             if fget.has_key(groupby):
1940                 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
1941                    dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
1942                    days = calendar.monthrange(dt.year, dt.month)[1]
1943
1944                    d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
1945                    d['__domain'] = [(groupby,'>=',alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-01','%Y-%m-%d').strftime('%Y-%m-%d') or False),\
1946                                     (groupby,'<=',alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-' + str(days),'%Y-%m-%d').strftime('%Y-%m-%d') or False)] + domain
1947                 elif fget[groupby]['type'] == 'many2one':
1948                     d[groupby] = d[groupby] and ((type(d[groupby])==type(1)) and d[groupby] or d[groupby][1])  or ''
1949
1950             del alldata[d['id']][groupby]
1951             d.update(alldata[d['id']])
1952             del d['id']
1953         return data
1954
1955     def _inherits_join_calc(self, field, tables, where_clause):
1956         """ Adds missing table select and join clause(s) for reaching
1957             the field coming from an '_inherits' parent table.
1958             @param tables: list of table._table names enclosed in double quotes as returned
1959                            by _where_calc()
1960         """
1961         current_table = self
1962         while field in current_table._inherit_fields and not field in current_table._columns:
1963             parent_table = self.pool.get(current_table._inherit_fields[field][0])
1964             parent_table_name = parent_table._table
1965             if '"%s"'%parent_table_name not in tables:
1966                 tables.append('"%s"'%parent_table_name)
1967                 where_clause.append('(%s.%s = %s.id)' % (current_table._table, current_table._inherits[parent_table._name], parent_table_name))
1968             current_table = parent_table
1969         return (tables, where_clause)
1970
1971     def _parent_store_compute(self, cr):
1972         logger = netsvc.Logger()
1973         logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
1974         def browse_rec(root, pos=0):
1975 # TODO: set order
1976             where = self._parent_name+'='+str(root)
1977             if not root:
1978                 where = self._parent_name+' IS NULL'
1979             if self._parent_order:
1980                 where += ' order by '+self._parent_order
1981             cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
1982             pos2 = pos + 1
1983             childs = cr.fetchall()
1984             for id in childs:
1985                 pos2 = browse_rec(id[0], pos2)
1986             cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
1987             return pos2+1
1988         query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
1989         if self._parent_order:
1990             query += ' order by '+self._parent_order
1991         pos = 0
1992         cr.execute(query)
1993         for (root,) in cr.fetchall():
1994             pos = browse_rec(root, pos)
1995         return True
1996
1997     def _update_store(self, cr, f, k):
1998         logger = netsvc.Logger()
1999         logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2000         ss = self._columns[k]._symbol_set
2001         update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2002         cr.execute('select id from '+self._table)
2003         ids_lst = map(lambda x: x[0], cr.fetchall())
2004         while ids_lst:
2005             iids = ids_lst[:40]
2006             ids_lst = ids_lst[40:]
2007             res = f.get(cr, self, iids, k, 1, {})
2008             for key,val in res.items():
2009                 if f._multi:
2010                     val = val[k]
2011                 # if val is a many2one, just write the ID
2012                 if type(val)==tuple:
2013                     val = val[0]
2014                 if (val<>False) or (type(val)<>bool):
2015                     cr.execute(update_query, (ss[1](val), key))
2016
2017     def _check_removed_columns(self, cr, log=False):
2018         logger = netsvc.Logger()
2019         # iterate on the database columns to drop the NOT NULL constraints
2020         # of fields which were required but have been removed (or will be added by another module)
2021         columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2022         columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2023         cr.execute("SELECT a.attname, a.attnotnull"
2024                    "  FROM pg_class c, pg_attribute a"
2025                    " WHERE c.relname=%%s"
2026                    "   AND c.oid=a.attrelid"
2027                    "   AND a.attisdropped=%%s"
2028                    "   AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2029                    "   AND a.attname NOT IN (%s)" % ",".join(['%s']*len(columns)),
2030                        [self._table, False] + columns)
2031         for column in cr.dictfetchall():
2032             if log:
2033                 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))
2034             if column['attnotnull']:
2035                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2036
2037     def _auto_init(self, cr, context={}):
2038         store_compute =  False
2039         logger = netsvc.Logger()
2040         create = False
2041         todo_end = []
2042         self._field_create(cr, context=context)
2043         if getattr(self, '_auto', True):
2044             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
2045             if not cr.rowcount:
2046                 cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS" % self._table)
2047                 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2048                 create = True
2049             cr.commit()
2050             if self._parent_store:
2051                 cr.execute("""SELECT c.relname
2052                     FROM pg_class c, pg_attribute a
2053                     WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2054                     """, (self._table, 'parent_left'))
2055                 if not cr.rowcount:
2056                     if 'parent_left' not in self._columns:
2057                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2058                     if 'parent_right' not in self._columns:
2059                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2060                     if self._columns[self._parent_name].ondelete<>'cascade':
2061                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "the columns %s on object must be set as ondelete='cascasde'" % (self._name, self._parent_name))
2062                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2063                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2064                     cr.commit()
2065                     store_compute = True
2066
2067             if self._log_access:
2068                 logs = {
2069                     'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2070                     'create_date': 'TIMESTAMP',
2071                     'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2072                     'write_date': 'TIMESTAMP'
2073                 }
2074                 for k in logs:
2075                     cr.execute("""
2076                         SELECT c.relname
2077                           FROM pg_class c, pg_attribute a
2078                          WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2079                         """, (self._table, k))
2080                     if not cr.rowcount:
2081                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2082                         cr.commit()
2083
2084             self._check_removed_columns(cr, log=False)
2085
2086             # iterate on the "object columns"
2087             todo_update_store = []
2088             update_custom_fields = context.get('update_custom_fields', False)
2089             for k in self._columns:
2090                 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2091                     continue
2092                     #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2093                 #Not Updating Custom fields
2094                 if k.startswith('x_') and not update_custom_fields:
2095                     continue
2096                 f = self._columns[k]
2097
2098                 if isinstance(f, fields.one2many):
2099                     cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2100
2101                     if self.pool.get(f._obj):
2102                         if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2103                             if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2104                                 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2105
2106                     if cr.fetchone():
2107                         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))
2108                         res = cr.fetchone()[0]
2109                         if not res:
2110                             cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2111                 elif isinstance(f, fields.many2many):
2112                     cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
2113                     if not cr.dictfetchall():
2114                         if not self.pool.get(f._obj):
2115                             raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2116                         ref = self.pool.get(f._obj)._table
2117 #                        ref = f._obj.replace('.', '_')
2118                         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))
2119                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2120                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2121                         cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2122                         cr.commit()
2123                 else:
2124                     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 " \
2125                                "FROM pg_class c,pg_attribute a,pg_type t " \
2126                                "WHERE c.relname=%s " \
2127                                "AND a.attname=%s " \
2128                                "AND c.oid=a.attrelid " \
2129                                "AND a.atttypid=t.oid", (self._table, k))
2130                     res = cr.dictfetchall()
2131                     if not res and hasattr(f,'oldname'):
2132                         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 " \
2133                             "FROM pg_class c,pg_attribute a,pg_type t " \
2134                             "WHERE c.relname=%s " \
2135                             "AND a.attname=%s " \
2136                             "AND c.oid=a.attrelid " \
2137                             "AND a.atttypid=t.oid", (self._table, f.oldname))
2138                         res_old = cr.dictfetchall()
2139                         logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2140                         if res_old and len(res_old)==1:
2141                             cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2142                             res = res_old
2143                             res[0]['attname'] = k
2144
2145                     if not res:
2146                         if not isinstance(f, fields.function) or f.store:
2147
2148                             # add the missing field
2149                             cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2150                             cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2151
2152                             # initialize it
2153                             if not create and k in self._defaults:
2154                                 if callable(self._defaults[k]):
2155                                     default = self._defaults[k](self, cr, 1, context)
2156                                 else:
2157                                     default = self._defaults[k]
2158
2159                                 ss = self._columns[k]._symbol_set
2160                                 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2161                                 cr.execute(query, (ss[1](default),))
2162                                 cr.commit()
2163                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2164                             elif not create:
2165                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2166
2167                             if isinstance(f, fields.function):
2168                                 order = 10
2169                                 if f.store is not True:
2170                                     order = f.store[f.store.keys()[0]][2]
2171                                 todo_update_store.append((order, f,k))
2172
2173                             # and add constraints if needed
2174                             if isinstance(f, fields.many2one):
2175                                 if not self.pool.get(f._obj):
2176                                     raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2177                                 ref = self.pool.get(f._obj)._table
2178 #                                ref = f._obj.replace('.', '_')
2179                                 # ir_actions is inherited so foreign key doesn't work on it
2180                                 if ref != 'ir_actions':
2181                                     cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2182                             if f.select:
2183                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2184                             if f.required:
2185                                 try:
2186                                     cr.commit()
2187                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2188                                 except Exception, e:
2189                                     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))
2190                             cr.commit()
2191                     elif len(res)==1:
2192                         f_pg_def = res[0]
2193                         f_pg_type = f_pg_def['typname']
2194                         f_pg_size = f_pg_def['size']
2195                         f_pg_notnull = f_pg_def['attnotnull']
2196                         if isinstance(f, fields.function) and not f.store and\
2197                                 not getattr(f, 'nodrop', False):
2198                             logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2199                             cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2200                             cr.commit()
2201                             f_obj_type = None
2202                         else:
2203                             f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2204
2205                         if f_obj_type:
2206                             ok = False
2207                             casts = [
2208                                 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2209                                 ('varchar', 'text', 'TEXT', ''),
2210                                 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2211                                 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2212                                 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2213                                 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2214                             ]
2215                             # !!! Avoid reduction of varchar field !!!
2216                             if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2217                             # if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
2218                                 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2219                                 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2220                                 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2221                                 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2222                                 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2223                                 cr.commit()
2224                             for c in casts:
2225                                 if (f_pg_type==c[0]) and (f._type==c[1]):
2226                                     if f_pg_type != f_obj_type:
2227                                         if f_pg_type != f_obj_type:
2228                                             logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2229                                         ok = True
2230                                         cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2231                                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2232                                         cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2233                                         cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2234                                         cr.commit()
2235                                     break
2236
2237                             if f_pg_type != f_obj_type:
2238                                 if not ok:
2239                                     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))
2240
2241                             # if the field is required and hasn't got a NOT NULL constraint
2242                             if f.required and f_pg_notnull == 0:
2243                                 # set the field to the default value if any
2244                                 if k in self._defaults:
2245                                     if callable(self._defaults[k]):
2246                                         default = self._defaults[k](self, cr, 1, context)
2247                                     else:
2248                                         default = self._defaults[k]
2249
2250                                     if (default is not None):
2251                                         ss = self._columns[k]._symbol_set
2252                                         query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2253                                         cr.execute(query, (ss[1](default),))
2254                                 # add the NOT NULL constraint
2255                                 cr.commit()
2256                                 try:
2257                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2258                                     cr.commit()
2259                                 except Exception, e:
2260                                     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))
2261                                 cr.commit()
2262                             elif not f.required and f_pg_notnull == 1:
2263                                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2264                                 cr.commit()
2265                             indexname = '%s_%s_index' % (self._table, k)
2266                             cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2267                             res = cr.dictfetchall()
2268                             if not res and f.select:
2269                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2270                                 cr.commit()
2271                             if res and not f.select:
2272                                 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2273                                 cr.commit()
2274                             if isinstance(f, fields.many2one):
2275                                 ref = self.pool.get(f._obj)._table
2276                                 if ref != 'ir_actions':
2277                                     cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2278                                                 'pg_attribute as att1, pg_attribute as att2 '
2279                                             'WHERE con.conrelid = cl1.oid '
2280                                                 'AND cl1.relname = %s '
2281                                                 'AND con.confrelid = cl2.oid '
2282                                                 'AND cl2.relname = %s '
2283                                                 'AND array_lower(con.conkey, 1) = 1 '
2284                                                 'AND con.conkey[1] = att1.attnum '
2285                                                 'AND att1.attrelid = cl1.oid '
2286                                                 'AND att1.attname = %s '
2287                                                 'AND array_lower(con.confkey, 1) = 1 '
2288                                                 'AND con.confkey[1] = att2.attnum '
2289                                                 'AND att2.attrelid = cl2.oid '
2290                                                 'AND att2.attname = %s '
2291                                                 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2292                                     res = cr.dictfetchall()
2293                                     if res:
2294                                         if res[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2295                                             cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
2296                                             cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2297                                             cr.commit()
2298                     else:
2299                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2300             for order,f,k in todo_update_store:
2301                 todo_end.append((order, self._update_store, (f, k)))
2302
2303         else:
2304             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (self._table,))
2305             create = not bool(cr.fetchone())
2306
2307         for (key, con, _) in self._sql_constraints:
2308             conname = '%s_%s' % (self._table, key)
2309             cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2310             if not cr.dictfetchall():
2311                 try:
2312                     cr.execute('alter table "%s" add constraint "%s_%s" %s' % (self._table, self._table, key, con,))
2313                     cr.commit()
2314                 except:
2315                     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,))
2316
2317         if create:
2318             if hasattr(self, "_sql"):
2319                 for line in self._sql.split(';'):
2320                     line2 = line.replace('\n', '').strip()
2321                     if line2:
2322                         cr.execute(line2)
2323                         cr.commit()
2324         if store_compute:
2325             self._parent_store_compute(cr)
2326         return todo_end
2327
2328     def __init__(self, cr):
2329         super(orm, self).__init__(cr)
2330
2331         if not hasattr(self, '_log_access'):
2332             # if not access is not specify, it is the same value as _auto
2333             self._log_access = getattr(self, "_auto", True)
2334
2335         self._columns = self._columns.copy()
2336         for store_field in self._columns:
2337             f = self._columns[store_field]
2338             if hasattr(f, 'digits_change'):
2339                 f.digits_change(cr)
2340             if not isinstance(f, fields.function):
2341                 continue
2342             if not f.store:
2343                 continue
2344             if self._columns[store_field].store is True:
2345                 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2346             else:
2347                 sm = self._columns[store_field].store
2348             for object, aa in sm.items():
2349                 if len(aa)==4:
2350                     (fnct,fields2,order,length)=aa
2351                 elif len(aa)==3:
2352                     (fnct,fields2,order)=aa
2353                     length = None
2354                 else:
2355                     raise except_orm('Error',
2356                         ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2357                 self.pool._store_function.setdefault(object, [])
2358                 ok = True
2359                 for x,y,z,e,f,l in self.pool._store_function[object]:
2360                     if (x==self._name) and (y==store_field) and (e==fields2):
2361                         if f==order:
2362                             ok = False
2363                 if ok:
2364                     self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2365                     self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2366
2367         for (key, _, msg) in self._sql_constraints:
2368             self.pool._sql_error[self._table+'_'+key] = msg
2369
2370         # Load manual fields
2371
2372         cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2373         if cr.fetchone():
2374             cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2375             for field in cr.dictfetchall():
2376                 if field['name'] in self._columns:
2377                     continue
2378                 attrs = {
2379                     'string': field['field_description'],
2380                     'required': bool(field['required']),
2381                     'readonly': bool(field['readonly']),
2382                     'domain': field['domain'] or None,
2383                     'size': field['size'],
2384                     'ondelete': field['on_delete'],
2385                     'translate': (field['translate']),
2386                     #'select': int(field['select_level'])
2387                 }
2388
2389                 if field['ttype'] == 'selection':
2390                     self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2391                 elif field['ttype'] == 'reference':
2392                     self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2393                 elif field['ttype'] == 'many2one':
2394                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2395                 elif field['ttype'] == 'one2many':
2396                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2397                 elif field['ttype'] == 'many2many':
2398                     _rel1 = field['relation'].replace('.', '_')
2399                     _rel2 = field['model'].replace('.', '_')
2400                     _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2401                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2402                 else:
2403                     self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2404
2405         self._inherits_reload()
2406         if not self._sequence:
2407             self._sequence = self._table+'_id_seq'
2408         for k in self._defaults:
2409             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,)
2410         for f in self._columns:
2411             self._columns[f].restart()
2412
2413     def default_get(self, cr, uid, fields_list, context=None):
2414         if not context:
2415             context = {}
2416         value = {}
2417         # get the default values for the inherited fields
2418         for t in self._inherits.keys():
2419             value.update(self.pool.get(t).default_get(cr, uid, fields_list,
2420                 context))
2421
2422         # get the default values defined in the object
2423         for f in fields_list:
2424             if f in self._defaults:
2425                 if callable(self._defaults[f]):
2426                     value[f] = self._defaults[f](self, cr, uid, context)
2427                 else:
2428                     value[f] = self._defaults[f]
2429
2430             fld_def = ((f in self._columns) and self._columns[f]) \
2431                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
2432                     or False
2433             if isinstance(fld_def, fields.property):
2434                 property_obj = self.pool.get('ir.property')
2435                 definition_id = fld_def._field_get(cr, uid, self._name, f)
2436                 nid = property_obj.search(cr, uid, [('fields_id', '=',
2437                     definition_id), ('res_id', '=', False)])
2438                 if nid:
2439                     prop_value = property_obj.browse(cr, uid, nid[0],
2440                             context=context).value
2441                     value[f] = (prop_value and int(prop_value.split(',')[1])) \
2442                             or False
2443
2444         # get the default values set by the user and override the default
2445         # values defined in the object
2446         ir_values_obj = self.pool.get('ir.values')
2447         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
2448         for id, field, field_value in res:
2449             if field in fields_list:
2450                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
2451                 if fld_def._type in ('many2one', 'one2one'):
2452                     obj = self.pool.get(fld_def._obj)
2453                     if not obj.search(cr, uid, [('id', '=', field_value or False)]):
2454                         continue
2455                 if fld_def._type in ('many2many'):
2456                     obj = self.pool.get(fld_def._obj)
2457                     field_value2 = []
2458                     for i in range(len(field_value)):
2459                         if not obj.search(cr, uid, [('id', '=',
2460                             field_value[i])]):
2461                             continue
2462                         field_value2.append(field_value[i])
2463                     field_value = field_value2
2464                 if fld_def._type in ('one2many'):
2465                     obj = self.pool.get(fld_def._obj)
2466                     field_value2 = []
2467                     for i in range(len(field_value)):
2468                         field_value2.append({})
2469                         for field2 in field_value[i]:
2470                             if obj._columns[field2]._type in ('many2one', 'one2one'):
2471                                 obj2 = self.pool.get(obj._columns[field2]._obj)
2472                                 if not obj2.search(cr, uid,
2473                                         [('id', '=', field_value[i][field2])]):
2474                                     continue
2475                             # TODO add test for many2many and one2many
2476                             field_value2[i][field2] = field_value[i][field2]
2477                     field_value = field_value2
2478                 value[field] = field_value
2479         for key in context or {}:
2480             if key.startswith('default_') and (key[8:] in fields_list):
2481                 value[key[8:]] = context[key]
2482         return value
2483
2484     #
2485     # Update objects that uses this one to update their _inherits fields
2486     #
2487
2488     def _inherits_reload_src(self):
2489         for obj in self.pool.obj_pool.values():
2490             if self._name in obj._inherits:
2491                 obj._inherits_reload()
2492
2493     def _inherits_reload(self):
2494         res = {}
2495         for table in self._inherits:
2496             res.update(self.pool.get(table)._inherit_fields)
2497             for col in self.pool.get(table)._columns.keys():
2498                 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2499             for col in self.pool.get(table)._inherit_fields.keys():
2500                 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2501         self._inherit_fields = res
2502         self._inherits_reload_src()
2503
2504     def fields_get(self, cr, user, fields=None, context=None):
2505         ira = self.pool.get('ir.model.access')
2506         read_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2507                       ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2508         return super(orm, self).fields_get(cr, user, fields, context, read_access)
2509
2510     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2511         if not context:
2512             context = {}
2513         self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2514         if not fields:
2515             fields = self._columns.keys() + self._inherit_fields.keys()
2516         if isinstance(ids, (int, long)):
2517             select = [ids]
2518         else:
2519             select = ids
2520         select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2521         result = self._read_flat(cr, user, select, fields, context, load)
2522         for r in result:
2523             for key, v in r.items():
2524                 if v == None:
2525                     r[key] = False
2526                 if key in self._columns.keys():
2527                     type = self._columns[key]._type
2528                 elif key in self._inherit_fields.keys():
2529                     type = self._inherit_fields[key][2]._type
2530                 else:
2531                     continue
2532                 if type == 'reference' and v:
2533                     model,ref_id = v.split(',')
2534                     table = self.pool.get(model)._table
2535                     cr.execute('select id from "%s" where id=%s' % (table,ref_id))
2536                     id_exist = cr.fetchone()
2537                     if not id_exist:
2538                         cr.execute('update "'+self._table+'" set "'+key+'"=NULL where "%s"=%s' %(key,''.join("'"+str(v)+"'")))
2539                         r[key] = ''
2540         if isinstance(ids, (int, long, dict)):
2541             return result and result[0] or False
2542         return result
2543
2544     def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2545         if not context:
2546             context = {}
2547         #ids = map(lambda x:int(x), ids)
2548         if not ids:
2549             return []
2550         if fields_to_read == None:
2551             fields_to_read = self._columns.keys()
2552
2553         # construct a clause for the rules :
2554         d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2555         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2556         fields_pre = [f for f in fields_to_read if
2557                            f == self.CONCURRENCY_CHECK_FIELD
2558                         or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2559                      ] + self._inherits.values()
2560
2561         res = []
2562         if len(fields_pre):
2563             def convert_field(f):
2564                 if f in ('create_date', 'write_date'):
2565                     return "date_trunc('second', %s) as %s" % (f, f)
2566                 if f == self.CONCURRENCY_CHECK_FIELD:
2567                     if self._log_access:
2568                         return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2569                     return "now()::timestamp AS %s" % (f,)
2570                 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2571                     return 'length("%s") as "%s"' % (f, f)
2572                 return '"%s"' % (f,)
2573             fields_pre2 = map(convert_field, fields_pre)
2574             order_by = self._parent_order or self._order
2575             for sub_ids in cr.split_for_in_conditions(ids):
2576                 if d1:
2577                     cr.execute('SELECT %s FROM %s WHERE %s.id IN %%s AND %s ORDER BY %s' % \
2578                             (','.join(fields_pre2 + [self._table + '.id']), ','.join(tables), self._table, ' and '.join(d1),
2579                                 order_by),[sub_ids,]+d2)
2580                     if cr.rowcount != len(sub_ids):
2581                         raise except_orm(_('AccessError'),
2582                                 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2583                 else:
2584                     cr.execute('SELECT %s FROM \"%s\" WHERE id IN %%s ORDER BY %s' %
2585                                (','.join(fields_pre2 + ['id']), self._table,
2586                                 order_by), (sub_ids,))
2587                 res.extend(cr.dictfetchall())
2588         else:
2589             res = map(lambda x: {'id': x}, ids)
2590
2591         for f in fields_pre:
2592             if f == self.CONCURRENCY_CHECK_FIELD:
2593                 continue
2594             if self._columns[f].translate:
2595                 ids = map(lambda x: x['id'], res)
2596                 #TODO: optimize out of this loop
2597                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2598                 for r in res:
2599                     r[f] = res_trans.get(r['id'], False) or r[f]
2600
2601         for table in self._inherits:
2602             col = self._inherits[table]
2603             cols = intersect(self._inherit_fields.keys(), fields_to_read)
2604             if not cols:
2605                 continue
2606             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
2607
2608             res3 = {}
2609             for r in res2:
2610                 res3[r['id']] = r
2611                 del r['id']
2612
2613             for record in res:
2614                 if not record[col]:# if the record is deleted from _inherits table?
2615                     continue
2616                 record.update(res3[record[col]])
2617                 if col not in fields_to_read:
2618                     del record[col]
2619
2620         # all fields which need to be post-processed by a simple function (symbol_get)
2621         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
2622         if fields_post:
2623             for r in res:
2624                 for f in fields_post:
2625                     r[f] = self._columns[f]._symbol_get(r[f])
2626         ids = map(lambda x: x['id'], res)
2627
2628         # all non inherited fields for which the attribute whose name is in load is False
2629         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2630
2631         # Compute POST fields
2632         todo = {}
2633         for f in fields_post:
2634             todo.setdefault(self._columns[f]._multi, [])
2635             todo[self._columns[f]._multi].append(f)
2636         for key,val in todo.items():
2637             if key:
2638                 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2639                 for pos in val:
2640                     for record in res:
2641                         if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
2642                         record[pos] = res2[record['id']][pos]
2643             else:
2644                 for f in val:
2645                     res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2646                     for record in res:
2647                         if res2:
2648                             record[f] = res2[record['id']]
2649                         else:
2650                             record[f] = []
2651
2652 #for f in fields_post:
2653 #    # get the value of that field for all records/ids
2654 #    res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2655 #    for record in res:
2656 #        record[f] = res2[record['id']]
2657
2658         readonly = None
2659         for vals in res:
2660             for field in vals.copy():
2661                 fobj = None
2662                 if field in self._columns:
2663                     fobj = self._columns[field]
2664
2665                 if not fobj:
2666                     continue
2667                 groups = fobj.read
2668                 if groups:
2669                     edit = False
2670                     for group in groups:
2671                         module = group.split(".")[0]
2672                         grp = group.split(".")[1]
2673                         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" % \
2674                                    (grp, module, 'res.groups', user))
2675                         readonly = cr.fetchall()
2676                         if readonly[0][0] >= 1:
2677                             edit = True
2678                             break
2679                         elif readonly[0][0] == 0:
2680                             edit = False
2681                         else:
2682                             edit = False
2683
2684                     if not edit:
2685                         if type(vals[field]) == type([]):
2686                             vals[field] = []
2687                         elif type(vals[field]) == type(0.0):
2688                             vals[field] = 0
2689                         elif type(vals[field]) == type(''):
2690                             vals[field] = '=No Permission='
2691                         else:
2692                             vals[field] = False
2693         return res
2694
2695     def perm_read(self, cr, user, ids, context=None, details=True):
2696         if not context:
2697             context = {}
2698         if not ids:
2699             return []
2700         fields = ''
2701         if self._log_access:
2702             fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date'
2703         if isinstance(ids, (int, long)):
2704             ids_str = str(ids)
2705         else:
2706             ids_str = string.join(map(lambda x: str(x), ids), ',')
2707         cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
2708         res = cr.dictfetchall()
2709         for r in res:
2710             for key in r:
2711                 r[key] = r[key] or False
2712                 if key in ('write_uid', 'create_uid', 'uid') and details:
2713                     if r[key]:
2714                         r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
2715         if isinstance(ids, (int, long)):
2716             return res[ids]
2717         return res
2718
2719     def _check_concurrency(self, cr, ids, context):
2720         if not context:
2721             return
2722         if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
2723             def key(oid):
2724                 return "%s,%s" % (self._name, oid)
2725             santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
2726             for i in range(0, len(ids), cr.IN_MAX):
2727                 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
2728                                           for oid in ids[i:i+cr.IN_MAX]
2729                                           if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
2730                 if sub_ids:
2731                     cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
2732                     res = cr.fetchone()
2733                     if res and res[0]:
2734                         raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
2735
2736     def check_access_rule(self, cr, uid, ids, operation, context=None):
2737         """Verifies that the operation given by ``operation`` is allowed for the user
2738            according to ir.rules.
2739            @param ``operation``: one of ``'read'``, ``'write'``, ``'unlink'``
2740            @raise ``except_orm``: if current ir.rules do not permit this operation.
2741            @return: ``None`` if the operation is allowed
2742         """
2743         where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
2744         if where_clause:
2745             where_clause = ' and ' + ' and '.join(where_clause)
2746             for sub_ids in cr.split_for_in_conditions(ids):
2747                 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
2748                            ' WHERE ' + self._table + '.id IN %s' + where_clause,
2749                            [sub_ids] + where_params)
2750                 if cr.rowcount != len(sub_ids):
2751                     raise except_orm(_('AccessError'),
2752                                      _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
2753                                      % (operation, self._name))
2754
2755     def unlink(self, cr, uid, ids, context=None):
2756         if not ids:
2757             return True
2758         if isinstance(ids, (int, long)):
2759             ids = [ids]
2760
2761         result_store = self._store_get_values(cr, uid, ids, None, context)
2762
2763         self._check_concurrency(cr, ids, context)
2764
2765         self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
2766
2767         properties = self.pool.get('ir.property')
2768         domain = [('res_id', '=', False),
2769                   ('value', 'in', ['%s,%s' % (self._name, i) for i in ids]),
2770                  ]
2771         if properties.search(cr, uid, domain, context=context):
2772             raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
2773
2774         wf_service = netsvc.LocalService("workflow")
2775         for oid in ids:
2776             wf_service.trg_delete(uid, self._name, oid, cr)
2777
2778         #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
2779         #res = cr.dictfetchall()
2780         #for key in self._inherits:
2781         #   ids2 = [x[self._inherits[key]] for x in res]
2782         #   self.pool.get(key).unlink(cr, uid, ids2)
2783
2784         self.check_access_rule(cr, uid, ids, 'unlink', context=context)
2785         for sub_ids in cr.split_for_in_conditions(ids):
2786             cr.execute('delete from ' + self._table + ' ' \
2787                        'where id in %s', sub_ids)
2788
2789         for order, object, store_ids, fields in result_store:
2790             if object != self._name:
2791                 obj =  self.pool.get(object)
2792                 cr.execute('select id from '+obj._table+' where id in ('+','.join(map(str, store_ids))+')')
2793                 rids = map(lambda x: x[0], cr.fetchall())
2794                 if rids:
2795                     obj._store_set_values(cr, uid, rids, fields, context)
2796         return True
2797
2798     #
2799     # TODO: Validate
2800     #
2801     def write(self, cr, user, ids, vals, context=None):
2802         readonly = None
2803         for field in vals.copy():
2804             fobj = None
2805             if field in self._columns:
2806                 fobj = self._columns[field]
2807             else:
2808                 fobj = self._inherit_fields[field][2]
2809             if not fobj:
2810                 continue
2811             groups = fobj.write
2812
2813             if groups:
2814                 edit = False
2815                 for group in groups:
2816                     module = group.split(".")[0]
2817                     grp = group.split(".")[1]
2818                     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" % \
2819                                (grp, module, 'res.groups', user))
2820                     readonly = cr.fetchall()
2821                     if readonly[0][0] >= 1:
2822                         edit = True
2823                         break
2824                     elif readonly[0][0] == 0:
2825                         edit = False
2826                     else:
2827                         edit = False
2828
2829                 if not edit:
2830                     vals.pop(field)
2831
2832         if not context:
2833             context = {}
2834         if not ids:
2835             return True
2836         if isinstance(ids, (int, long)):
2837             ids = [ids]
2838
2839         self._check_concurrency(cr, ids, context)
2840
2841         self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
2842
2843
2844         upd0 = []
2845         upd1 = []
2846         upd_todo = []
2847         updend = []
2848         direct = []
2849         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
2850         for field in vals:
2851             if field in self._columns:
2852                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
2853                     if (not totranslate) or not self._columns[field].translate:
2854                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
2855                         upd1.append(self._columns[field]._symbol_set[1](vals[field]))
2856                     direct.append(field)
2857                 else:
2858                     upd_todo.append(field)
2859             else:
2860                 updend.append(field)
2861             if field in self._columns \
2862                     and hasattr(self._columns[field], 'selection') \
2863                     and vals[field]:
2864                 if self._columns[field]._type == 'reference':
2865                     val = vals[field].split(',')[0]
2866                 else:
2867                     val = vals[field]
2868                 if isinstance(self._columns[field].selection, (tuple, list)):
2869                     if val not in dict(self._columns[field].selection):
2870                         raise except_orm(_('ValidateError'),
2871                         _('The value "%s" for the field "%s" is not in the selection') \
2872                                 % (vals[field], field))
2873                 else:
2874                     if val not in dict(self._columns[field].selection(
2875                         self, cr, user, context=context)):
2876                         raise except_orm(_('ValidateError'),
2877                         _('The value "%s" for the field "%s" is not in the selection') \
2878                                 % (vals[field], field))
2879
2880         if self._log_access:
2881             upd0.append('write_uid=%s')
2882             upd0.append('write_date=now()')
2883             upd1.append(user)
2884
2885         if len(upd0):
2886             self.check_access_rule(cr, user, ids, 'write', context=context)
2887             for sub_ids in cr.split_for_in_conditions(ids):
2888                 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
2889                            'where id in %s', upd1 + [sub_ids])
2890
2891             if totranslate:
2892                 # TODO: optimize
2893                 for f in direct:
2894                     if self._columns[f].translate:
2895                         src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
2896                         if not src_trans:
2897                             src_trans = vals[f]
2898                             # Inserting value to DB
2899                             self.write(cr, user, ids, {f:vals[f]})
2900                         self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
2901
2902
2903         # call the 'set' method of fields which are not classic_write
2904         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
2905
2906         # default element in context must be removed when call a one2many or many2many
2907         rel_context = context.copy()
2908         for c in context.items():
2909             if c[0].startswith('default_'):
2910                 del rel_context[c[0]]
2911
2912         result = []
2913         for field in upd_todo:
2914             for id in ids:
2915                 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
2916
2917         for table in self._inherits:
2918             col = self._inherits[table]
2919             nids = []
2920             for sub_ids in cr.split_for_in_conditions(ids):
2921                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2922                            'where id in %s', (sub_ids,))
2923                 nids.extend([x[0] for x in cr.fetchall()])
2924
2925             v = {}
2926             for val in updend:
2927                 if self._inherit_fields[val][0] == table:
2928                     v[val] = vals[val]
2929             self.pool.get(table).write(cr, user, nids, v, context)
2930
2931         self._validate(cr, user, ids, context)
2932 # TODO: use _order to set dest at the right position and not first node of parent
2933         if self._parent_store and (self._parent_name in vals):
2934             if self.pool._init:
2935                 self.pool._init_parent[self._name]=True
2936             else:
2937                 for id in ids:
2938                     # Find Position of the element
2939                     if vals[self._parent_name]:
2940                         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],))
2941                     else:
2942                         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))
2943                     result_p = cr.fetchall()
2944                     position = None
2945                     for (pleft,pright,pid) in result_p:
2946                         if pid == id:
2947                             break
2948                         position = pright+1
2949
2950                     # It's the first node of the parent: position = parent_left+1
2951                     if not position:
2952                         if not vals[self._parent_name]:
2953                             position = 1
2954                         else:
2955                             cr.execute('select parent_left from '+self._table+' where id=%s', (vals[self._parent_name],))
2956                             position = cr.fetchone()[0]+1
2957
2958                     # We have the new position !
2959                     cr.execute('select parent_left,parent_right from '+self._table+' where id=%s', (id,))
2960                     pleft,pright = cr.fetchone()
2961                     distance = pright - pleft + 1
2962
2963                     if position>pleft and position<=pright:
2964                         raise except_orm(_('UserError'), _('Recursivity Detected.'))
2965
2966                     if pleft<position:
2967                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2968                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2969                         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))
2970                     else:
2971                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2972                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2973                         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))
2974
2975         result += self._store_get_values(cr, user, ids, vals.keys(), context)
2976         for order, object, ids, fields in result:
2977             self.pool.get(object)._store_set_values(cr, user, ids, fields, context)
2978
2979         wf_service = netsvc.LocalService("workflow")
2980         for id in ids:
2981             wf_service.trg_write(user, self._name, id, cr)
2982         return True
2983
2984     #
2985     # TODO: Should set perm to user.xxx
2986     #
2987     def create(self, cr, user, vals, context=None):
2988         """ create(cr, user, vals, context) -> int
2989         cr = database cursor
2990         user = user id
2991         vals = dictionary of the form {'field_name':field_value, ...}
2992         """
2993         if not context:
2994             context = {}
2995         self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
2996
2997         default = []
2998
2999         avoid_table = []
3000         for (t, c) in self._inherits.items():
3001             if c in vals:
3002                 avoid_table.append(t)
3003         for f in self._columns.keys(): # + self._inherit_fields.keys():
3004             if not f in vals:
3005                 default.append(f)
3006
3007         for f in self._inherit_fields.keys():
3008             if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
3009                 default.append(f)
3010
3011         if len(default):
3012             default_values = self.default_get(cr, user, default, context)
3013             for dv in default_values:
3014                 if dv in self._columns and self._columns[dv]._type == 'many2many':
3015                     if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
3016                         default_values[dv] = [(6, 0, default_values[dv])]
3017
3018             vals.update(default_values)
3019
3020         tocreate = {}
3021         for v in self._inherits:
3022             if self._inherits[v] not in vals:
3023                 tocreate[v] = {}
3024             else:
3025                 tocreate[v] = {'id' : vals[self._inherits[v]]}
3026         (upd0, upd1, upd2) = ('', '', [])
3027         upd_todo = []
3028         for v in vals.keys():
3029             if v in self._inherit_fields:
3030                 (table, col, col_detail) = self._inherit_fields[v]
3031                 tocreate[table][v] = vals[v]
3032                 del vals[v]
3033             else:
3034                 if (v not in self._inherit_fields) and (v not in self._columns):
3035                     del vals[v]
3036
3037         # Try-except added to filter the creation of those records whose filds are readonly.
3038         # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3039         try:
3040             cr.execute("SELECT nextval('"+self._sequence+"')")
3041         except:
3042             raise except_orm(_('UserError'),
3043                         _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3044
3045         id_new = cr.fetchone()[0]
3046         for table in tocreate:
3047             if self._inherits[table] in vals:
3048                 del vals[self._inherits[table]]
3049
3050             record_id = tocreate[table].pop('id', None)
3051
3052             if record_id is None:
3053                 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3054             else:
3055                 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3056
3057             upd0 += ','+self._inherits[table]
3058             upd1 += ',%s'
3059             upd2.append(record_id)
3060
3061         #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3062         bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3063
3064         for bool_field in bool_fields:
3065             if bool_field not in vals:
3066                 vals[bool_field] = False
3067         #End
3068         for field in vals.copy():
3069             fobj = None
3070             if field in self._columns:
3071                 fobj = self._columns[field]
3072             else:
3073                 fobj = self._inherit_fields[field][2]
3074             if not fobj:
3075                 continue
3076             groups = fobj.write
3077             if groups:
3078                 edit = False
3079                 for group in groups:
3080                     module = group.split(".")[0]
3081                     grp = group.split(".")[1]
3082                     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" % \
3083                                (grp, module, 'res.groups', user))
3084                     readonly = cr.fetchall()
3085                     if readonly[0][0] >= 1:
3086                         edit = True
3087                         break
3088                     elif readonly[0][0] == 0:
3089                         edit = False
3090                     else:
3091                         edit = False
3092
3093                 if not edit:
3094                     vals.pop(field)
3095         for field in vals:
3096             if self._columns[field]._classic_write:
3097                 upd0 = upd0 + ',"' + field + '"'
3098                 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3099                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3100             else:
3101                 if not isinstance(self._columns[field], fields.related):
3102                     upd_todo.append(field)
3103             if field in self._columns \
3104                     and hasattr(self._columns[field], 'selection') \
3105                     and vals[field]:
3106                 if self._columns[field]._type == 'reference':
3107                     val = vals[field].split(',')[0]
3108                 else:
3109                     val = vals[field]
3110                 if isinstance(self._columns[field].selection, (tuple, list)):
3111                     if val not in dict(self._columns[field].selection):
3112                         raise except_orm(_('ValidateError'),
3113                         _('The value "%s" for the field "%s" is not in the selection') \
3114                                 % (vals[field], field))
3115                 else:
3116                     if val not in dict(self._columns[field].selection(
3117                         self, cr, user, context=context)):
3118                         raise except_orm(_('ValidateError'),
3119                         _('The value "%s" for the field "%s" is not in the selection') \
3120                                 % (vals[field], field))
3121         if self._log_access:
3122             upd0 += ',create_uid,create_date'
3123             upd1 += ',%s,now()'
3124             upd2.append(user)
3125         cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3126         d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'create', context=context)
3127         if d1:
3128             d1 = ' AND '+' AND '.join(d1)
3129             cr.execute('SELECT '+self._table+'.id FROM '+','.join(tables)+' ' \
3130                     'WHERE '+self._table+'.id = ' +str(id_new)+d1,d2)
3131             if not cr.rowcount:
3132                 raise except_orm(_('AccessError'),
3133                                  _('You try to bypass an access rule to create (Document type: %s).') \
3134                                   % self._name)
3135         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3136
3137         if self._parent_store:
3138             if self.pool._init:
3139                 self.pool._init_parent[self._name]=True
3140             else:
3141                 parent = vals.get(self._parent_name, False)
3142                 if parent:
3143                     cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3144                     pleft_old = None
3145                     result_p = cr.fetchall()
3146                     for (pleft,) in result_p:
3147                         if not pleft:
3148                             break
3149                         pleft_old = pleft
3150                     if not pleft_old:
3151                         cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3152                         pleft_old = cr.fetchone()[0]
3153                     pleft = pleft_old
3154                 else:
3155                     cr.execute('select max(parent_right) from '+self._table)
3156                     pleft = cr.fetchone()[0] or 0
3157                 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3158                 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3159                 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3160
3161         # default element in context must be remove when call a one2many or many2many
3162         rel_context = context.copy()
3163         for c in context.items():
3164             if c[0].startswith('default_'):
3165                 del rel_context[c[0]]
3166
3167         result = []
3168         for field in upd_todo:
3169             result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3170         self._validate(cr, user, [id_new], context)
3171
3172         if not context.get('no_store_function', False):
3173             result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3174             result.sort()
3175             done = []
3176             for order, object, ids, fields2 in result:
3177                 if not (object, ids, fields2) in done:
3178                     self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3179                     done.append((object, ids, fields2))
3180
3181         wf_service = netsvc.LocalService("workflow")
3182         wf_service.trg_create(user, self._name, id_new, cr)
3183         return id_new
3184
3185     def _store_get_values(self, cr, uid, ids, fields, context):
3186         result = {}
3187         fncts = self.pool._store_function.get(self._name, [])
3188         for fnct in range(len(fncts)):
3189             if fncts[fnct][3]:
3190                 ok = False
3191                 if not fields:
3192                     ok = True
3193                 for f in (fields or []):
3194                     if f in fncts[fnct][3]:
3195                         ok = True
3196                         break
3197                 if not ok:
3198                     continue
3199
3200             result.setdefault(fncts[fnct][0], {})
3201
3202             # uid == 1 for accessing objects having rules defined on store fields
3203             ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3204             for id in filter(None, ids2):
3205                 result[fncts[fnct][0]].setdefault(id, [])
3206                 result[fncts[fnct][0]][id].append(fnct)
3207         dict = {}
3208         for object in result:
3209             k2 = {}
3210             for id,fnct in result[object].items():
3211                 k2.setdefault(tuple(fnct), [])
3212                 k2[tuple(fnct)].append(id)
3213             for fnct,id in k2.items():
3214                 dict.setdefault(fncts[fnct[0]][4],[])
3215                 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3216         result2 = []
3217         tmp = dict.keys()
3218         tmp.sort()
3219         for k in tmp:
3220             result2+=dict[k]
3221         return result2
3222
3223     def _store_set_values(self, cr, uid, ids, fields, context):
3224         field_flag = False
3225         field_dict = {}
3226         if self._log_access:
3227             cr.execute('select id,write_date from '+self._table+' where id in ('+','.join(map(str, ids))+')')
3228             res = cr.fetchall()
3229             for r in res:
3230                 if r[1]:
3231                     field_dict.setdefault(r[0], [])
3232                     res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3233                     write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3234                     for i in self.pool._store_function.get(self._name, []):
3235                         if i[5]:
3236                             up_write_date = write_date + datetime.timedelta(hours=i[5])
3237                             if datetime.datetime.now() < up_write_date:
3238                                 if i[1] in fields:
3239                                     field_dict[r[0]].append(i[1])
3240                                     if not field_flag:
3241                                         field_flag = True
3242         todo = {}
3243         keys = []
3244         for f in fields:
3245             if self._columns[f]._multi not in keys:
3246                 keys.append(self._columns[f]._multi)
3247             todo.setdefault(self._columns[f]._multi, [])
3248             todo[self._columns[f]._multi].append(f)
3249         for key in keys:
3250             val = todo[key]
3251             if key:
3252                 # uid == 1 for accessing objects having rules defined on store fields
3253                 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3254                 for id,value in result.items():
3255                     if field_flag:
3256                         for f in value.keys():
3257                             if f in field_dict[id]:
3258                                 value.pop(f)
3259                     upd0 = []
3260                     upd1 = []
3261                     for v in value:
3262                         if v not in val:
3263                             continue
3264                         if self._columns[v]._type in ('many2one', 'one2one'):
3265                             try:
3266                                 value[v] = value[v][0]
3267                             except:
3268                                 pass
3269                         upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3270                         upd1.append(self._columns[v]._symbol_set[1](value[v]))
3271                     upd1.append(id)
3272                     if upd0 and upd1:
3273                         cr.execute('update "' + self._table + '" set ' + \
3274                             string.join(upd0, ',') + ' where id = %s', upd1)
3275
3276             else:
3277                 for f in val:
3278                     # uid == 1 for accessing objects having rules defined on store fields
3279                     result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3280                     for r in result.keys():
3281                         if field_flag:
3282                             if r in field_dict.keys():
3283                                 if f in field_dict[r]:
3284                                     result.pop(r)
3285                     for id,value in result.items():
3286                         if self._columns[f]._type in ('many2one', 'one2one'):
3287                             try:
3288                                 value = value[0]
3289                             except:
3290                                 pass
3291                         cr.execute('update "' + self._table + '" set ' + \
3292                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3293         return True
3294
3295     #
3296     # TODO: Validate
3297     #
3298     def perm_write(self, cr, user, ids, fields, context=None):
3299         raise _('This method does not exist anymore')
3300
3301     # TODO: ameliorer avec NULL
3302     def _where_calc(self, cr, user, args, active_test=True, context=None):
3303         if not context:
3304             context = {}
3305         args = args[:]
3306         # if the object has a field named 'active', filter out all inactive
3307         # records unless they were explicitely asked for
3308         if 'active' in self._columns and (active_test and context.get('active_test', True)):
3309             if args:
3310                 active_in_args = False
3311                 for a in args:
3312                     if a[0] == 'active':
3313                         active_in_args = True
3314                 if not active_in_args:
3315                     args.insert(0, ('active', '=', 1))
3316             else:
3317                 args = [('active', '=', 1)]
3318
3319         if args:
3320             import expression
3321             e = expression.expression(args)
3322             e.parse(cr, user, self, context)
3323             tables = e.get_tables()
3324             qu1, qu2 = e.to_sql()
3325             qu1 = qu1 and [qu1] or []
3326         else:
3327             qu1, qu2, tables = [], [], ['"%s"' % self._table]
3328
3329         return (qu1, qu2, tables)
3330
3331     def _check_qorder(self, word):
3332         if not regex_order.match(word):
3333             raise except_orm(_('AccessError'), _('Bad query.'))
3334         return True
3335
3336     def search(self, cr, user, args, offset=0, limit=None, order=None,
3337             context=None, count=False):
3338         if not context:
3339             context = {}
3340         # compute the where, order by, limit and offset clauses
3341         (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
3342         dom = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3343         qu1 = qu1 + dom[0]
3344         qu2 = qu2 + dom[1]
3345         for t in dom[2]:
3346             if t not in tables:
3347                 tables.append(t)
3348
3349         if len(qu1):
3350             qu1 = ' where '+string.join(qu1, ' and ')
3351         else:
3352             qu1 = ''
3353
3354
3355         order_by = self._order
3356         if order:
3357             self._check_qorder(order)
3358             o = order.split(' ')[0]
3359             if (o in self._columns) and getattr(self._columns[o], '_classic_write'):
3360                 order_by = order
3361
3362         limit_str = limit and ' limit %d' % limit or ''
3363         offset_str = offset and ' offset %d' % offset or ''
3364
3365
3366         if count:
3367             cr.execute('select count(%s.id) from ' % self._table +
3368                     ','.join(tables) +qu1 + limit_str + offset_str, qu2)
3369             res = cr.fetchall()
3370             return res[0][0]
3371         cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
3372         res = cr.fetchall()
3373         return [x[0] for x in res]
3374
3375     # returns the different values ever entered for one field
3376     # this is used, for example, in the client when the user hits enter on
3377     # a char field
3378     def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3379         if not args:
3380             args = []
3381         if field in self._inherit_fields:
3382             return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3383         else:
3384             return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3385
3386     def name_get(self, cr, user, ids, context=None):
3387         if not context:
3388             context = {}
3389         if not ids:
3390             return []
3391         if isinstance(ids, (int, long)):
3392             ids = [ids]
3393         return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
3394             [self._rec_name], context, load='_classic_write')]
3395
3396     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
3397         if not args:
3398             args = []
3399         if not context:
3400             context = {}
3401         args = args[:]
3402         if name:
3403             args += [(self._rec_name, operator, name)]
3404         ids = self.search(cr, user, args, limit=limit, context=context)
3405         res = self.name_get(cr, user, ids, context)
3406         return res
3407
3408     def copy_data(self, cr, uid, id, default=None, context=None):
3409         if not context:
3410             context = {}
3411         if not default:
3412             default = {}
3413         if 'state' not in default:
3414             if 'state' in self._defaults:
3415                 if callable(self._defaults['state']):
3416                     default['state'] = self._defaults['state'](self, cr, uid, context)
3417                 else:
3418                     default['state'] = self._defaults['state']
3419
3420         data = self.read(cr, uid, [id], context=context)[0]
3421         fields = self.fields_get(cr, uid, context=context)
3422         trans_data=[]
3423         for f in fields:
3424             ftype = fields[f]['type']
3425
3426             if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3427                 del data[f]
3428
3429             if f in default:
3430                 data[f] = default[f]
3431             elif ftype == 'function':
3432                 del data[f]
3433             elif ftype == 'many2one':
3434                 try:
3435                     data[f] = data[f] and data[f][0]
3436                 except:
3437                     pass
3438             elif ftype in ('one2many', 'one2one'):
3439                 res = []
3440                 rel = self.pool.get(fields[f]['relation'])
3441                 if data[f] != False:
3442                     for rel_id in data[f]:
3443                         # the lines are first duplicated using the wrong (old)
3444                         # parent but then are reassigned to the correct one thanks
3445                         # to the (4, ...)
3446                         d,t = rel.copy_data(cr, uid, rel_id, context=context)
3447                         res.append((0, 0, d))
3448                         trans_data += t
3449                 data[f] = res
3450             elif ftype == 'many2many':
3451                 data[f] = [(6, 0, data[f])]
3452
3453         trans_obj = self.pool.get('ir.translation')
3454         #TODO: optimize translations
3455         trans_name=''
3456         for f in fields:
3457             trans_flag=True
3458             if f in self._columns and self._columns[f].translate:
3459                 trans_name=self._name+","+f
3460             elif f in self._inherit_fields and self._inherit_fields[f][2].translate:
3461                 trans_name=self._inherit_fields[f][0]+","+f
3462             else:
3463                 trans_flag=False
3464
3465             if trans_flag:
3466                 trans_ids = trans_obj.search(cr, uid, [
3467                         ('name', '=', trans_name),
3468                         ('res_id','=',data['id'])
3469                     ])
3470
3471                 trans_data.extend(trans_obj.read(cr,uid,trans_ids,context=context))
3472
3473         del data['id']
3474
3475         for v in self._inherits:
3476             del data[self._inherits[v]]
3477         return data, trans_data
3478
3479     def copy(self, cr, uid, id, default=None, context=None):
3480         trans_obj = self.pool.get('ir.translation')
3481         data, trans_data = self.copy_data(cr, uid, id, default, context)
3482         new_id = self.create(cr, uid, data, context)
3483         for record in trans_data:
3484             del record['id']
3485             record['res_id'] = new_id
3486             trans_obj.create(cr, uid, record, context)
3487         return new_id
3488
3489     def exists(self, cr, uid, id, context=None):
3490         cr.execute('SELECT count(1) FROM "%s" where id=%%s' % (self._table,), (id,))
3491         return bool(cr.fetchone()[0])
3492
3493     def check_recursion(self, cr, uid, ids, parent=None):
3494         if not parent:
3495             parent = self._parent_name
3496         ids_parent = ids[:]
3497         while len(ids_parent):
3498             ids_parent2 = []
3499             for i in range(0, len(ids), cr.IN_MAX):
3500                 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
3501                 cr.execute('SELECT distinct "'+parent+'"'+
3502                     ' FROM "'+self._table+'" ' \
3503                     'WHERE id = ANY(%s)',(sub_ids_parent,))
3504                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
3505             ids_parent = ids_parent2
3506             for i in ids_parent:
3507                 if i in ids:
3508                     return False
3509         return True
3510
3511 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: