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