[FIX]:read_group for None Values
[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             for fld,val in r.items():
1921                 if val == None:r[fld] = False
1922             alldata[r['id']] = r
1923             del r['id']
1924         data = self.read(cr, uid, alldata.keys(), [groupby], context=context)
1925         today = datetime.date.today()
1926
1927         for d in data:
1928             d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
1929             d['__context'] = {'group_by':groupby_list[1:]}
1930             if fget.has_key(groupby):
1931                 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
1932                    dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
1933                    days = calendar.monthrange(dt.year, dt.month)[1]
1934
1935                    d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
1936                    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),\
1937                                     (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
1938                 elif fget[groupby]['type'] == 'many2one':
1939                     d[groupby] = d[groupby] and ((type(d[groupby])==type(1)) and d[groupby] or d[groupby][1])  or ''
1940
1941             del alldata[d['id']][groupby]
1942             d.update(alldata[d['id']])
1943             del d['id']
1944         return data
1945
1946     def _inherits_join_calc(self, field, tables, where_clause):
1947         """ Adds missing table select and join clause(s) for reaching
1948             the field coming from an '_inherits' parent table.
1949             @param tables: list of table._table names enclosed in double quotes as returned
1950                            by _where_calc()
1951         """
1952         current_table = self
1953         while field in current_table._inherit_fields and not field in current_table._columns:
1954             parent_table = self.pool.get(current_table._inherit_fields[field][0])
1955             parent_table_name = parent_table._table
1956             if '"%s"'%parent_table_name not in tables:
1957                 tables.append('"%s"'%parent_table_name)
1958                 where_clause.append('(%s.%s = %s.id)' % (current_table._table, current_table._inherits[parent_table._name], parent_table_name))
1959             current_table = parent_table
1960         return (tables, where_clause)
1961
1962     def _parent_store_compute(self, cr):
1963         logger = netsvc.Logger()
1964         logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
1965         def browse_rec(root, pos=0):
1966 # TODO: set order
1967             where = self._parent_name+'='+str(root)
1968             if not root:
1969                 where = self._parent_name+' IS NULL'
1970             if self._parent_order:
1971                 where += ' order by '+self._parent_order
1972             cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
1973             pos2 = pos + 1
1974             childs = cr.fetchall()
1975             for id in childs:
1976                 pos2 = browse_rec(id[0], pos2)
1977             cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
1978             return pos2+1
1979         query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
1980         if self._parent_order:
1981             query += ' order by '+self._parent_order
1982         pos = 0
1983         cr.execute(query)
1984         for (root,) in cr.fetchall():
1985             pos = browse_rec(root, pos)
1986         return True
1987
1988     def _update_store(self, cr, f, k):
1989         logger = netsvc.Logger()
1990         logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
1991         ss = self._columns[k]._symbol_set
1992         update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
1993         cr.execute('select id from '+self._table)
1994         ids_lst = map(lambda x: x[0], cr.fetchall())
1995         while ids_lst:
1996             iids = ids_lst[:40]
1997             ids_lst = ids_lst[40:]
1998             res = f.get(cr, self, iids, k, 1, {})
1999             for key,val in res.items():
2000                 if f._multi:
2001                     val = val[k]
2002                 # if val is a many2one, just write the ID
2003                 if type(val)==tuple:
2004                     val = val[0]
2005                 if (val<>False) or (type(val)<>bool):
2006                     cr.execute(update_query, (ss[1](val), key))
2007
2008     def _check_removed_columns(self, cr, log=False):
2009         logger = netsvc.Logger()
2010         # iterate on the database columns to drop the NOT NULL constraints
2011         # of fields which were required but have been removed (or will be added by another module)
2012         columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2013         columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2014         cr.execute("SELECT a.attname, a.attnotnull"
2015                    "  FROM pg_class c, pg_attribute a"
2016                    " WHERE c.relname=%%s"
2017                    "   AND c.oid=a.attrelid"
2018                    "   AND a.attisdropped=%%s"
2019                    "   AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2020                    "   AND a.attname NOT IN (%s)" % ",".join(['%s']*len(columns)),
2021                        [self._table, False] + columns)
2022         for column in cr.dictfetchall():
2023             if log:
2024                 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))
2025             if column['attnotnull']:
2026                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2027
2028     def _auto_init(self, cr, context={}):
2029         store_compute =  False
2030         logger = netsvc.Logger()
2031         create = False
2032         todo_end = []
2033         self._field_create(cr, context=context)
2034         if getattr(self, '_auto', True):
2035             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
2036             if not cr.rowcount:
2037                 cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS" % self._table)
2038                 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2039                 create = True
2040             cr.commit()
2041             if self._parent_store:
2042                 cr.execute("""SELECT c.relname
2043                     FROM pg_class c, pg_attribute a
2044                     WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2045                     """, (self._table, 'parent_left'))
2046                 if not cr.rowcount:
2047                     if 'parent_left' not in self._columns:
2048                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2049                     if 'parent_right' not in self._columns:
2050                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2051                     if self._columns[self._parent_name].ondelete<>'cascade':
2052                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "the columns %s on object must be set as ondelete='cascasde'" % (self._name, self._parent_name))
2053                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2054                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2055                     cr.commit()
2056                     store_compute = True
2057
2058             if self._log_access:
2059                 logs = {
2060                     'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2061                     'create_date': 'TIMESTAMP',
2062                     'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2063                     'write_date': 'TIMESTAMP'
2064                 }
2065                 for k in logs:
2066                     cr.execute("""
2067                         SELECT c.relname
2068                           FROM pg_class c, pg_attribute a
2069                          WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2070                         """, (self._table, k))
2071                     if not cr.rowcount:
2072                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2073                         cr.commit()
2074
2075             self._check_removed_columns(cr, log=False)
2076
2077             # iterate on the "object columns"
2078             todo_update_store = []
2079             update_custom_fields = context.get('update_custom_fields', False)
2080             for k in self._columns:
2081                 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2082                     continue
2083                     #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2084                 #Not Updating Custom fields
2085                 if k.startswith('x_') and not update_custom_fields:
2086                     continue
2087                 f = self._columns[k]
2088
2089                 if isinstance(f, fields.one2many):
2090                     cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2091
2092                     if self.pool.get(f._obj):
2093                         if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2094                             if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2095                                 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2096
2097                     if cr.fetchone():
2098                         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))
2099                         res = cr.fetchone()[0]
2100                         if not res:
2101                             cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2102                 elif isinstance(f, fields.many2many):
2103                     cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
2104                     if not cr.dictfetchall():
2105                         if not self.pool.get(f._obj):
2106                             raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2107                         ref = self.pool.get(f._obj)._table
2108 #                        ref = f._obj.replace('.', '_')
2109                         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))
2110                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2111                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2112                         cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2113                         cr.commit()
2114                 else:
2115                     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 " \
2116                                "FROM pg_class c,pg_attribute a,pg_type t " \
2117                                "WHERE c.relname=%s " \
2118                                "AND a.attname=%s " \
2119                                "AND c.oid=a.attrelid " \
2120                                "AND a.atttypid=t.oid", (self._table, k))
2121                     res = cr.dictfetchall()
2122                     if not res and hasattr(f,'oldname'):
2123                         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 " \
2124                             "FROM pg_class c,pg_attribute a,pg_type t " \
2125                             "WHERE c.relname=%s " \
2126                             "AND a.attname=%s " \
2127                             "AND c.oid=a.attrelid " \
2128                             "AND a.atttypid=t.oid", (self._table, f.oldname))
2129                         res_old = cr.dictfetchall()
2130                         logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2131                         if res_old and len(res_old)==1:
2132                             cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2133                             res = res_old
2134                             res[0]['attname'] = k
2135
2136                     if not res:
2137                         if not isinstance(f, fields.function) or f.store:
2138
2139                             # add the missing field
2140                             cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2141                             cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2142
2143                             # initialize it
2144                             if not create and k in self._defaults:
2145                                 if callable(self._defaults[k]):
2146                                     default = self._defaults[k](self, cr, 1, context)
2147                                 else:
2148                                     default = self._defaults[k]
2149
2150                                 ss = self._columns[k]._symbol_set
2151                                 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2152                                 cr.execute(query, (ss[1](default),))
2153                                 cr.commit()
2154                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2155                             elif not create:
2156                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2157
2158                             if isinstance(f, fields.function):
2159                                 order = 10
2160                                 if f.store is not True:
2161                                     order = f.store[f.store.keys()[0]][2]
2162                                 todo_update_store.append((order, f,k))
2163
2164                             # and add constraints if needed
2165                             if isinstance(f, fields.many2one):
2166                                 if not self.pool.get(f._obj):
2167                                     raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2168                                 ref = self.pool.get(f._obj)._table
2169 #                                ref = f._obj.replace('.', '_')
2170                                 # ir_actions is inherited so foreign key doesn't work on it
2171                                 if ref != 'ir_actions':
2172                                     cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2173                             if f.select:
2174                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2175                             if f.required:
2176                                 try:
2177                                     cr.commit()
2178                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2179                                 except Exception, e:
2180                                     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))
2181                             cr.commit()
2182                     elif len(res)==1:
2183                         f_pg_def = res[0]
2184                         f_pg_type = f_pg_def['typname']
2185                         f_pg_size = f_pg_def['size']
2186                         f_pg_notnull = f_pg_def['attnotnull']
2187                         if isinstance(f, fields.function) and not f.store and\
2188                                 not getattr(f, 'nodrop', False):
2189                             logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2190                             cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2191                             cr.commit()
2192                             f_obj_type = None
2193                         else:
2194                             f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2195
2196                         if f_obj_type:
2197                             ok = False
2198                             casts = [
2199                                 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2200                                 ('varchar', 'text', 'TEXT', ''),
2201                                 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2202                                 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2203                                 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2204                                 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2205                             ]
2206                             # !!! Avoid reduction of varchar field !!!
2207                             if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2208                             # if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
2209                                 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2210                                 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2211                                 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2212                                 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2213                                 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2214                                 cr.commit()
2215                             for c in casts:
2216                                 if (f_pg_type==c[0]) and (f._type==c[1]):
2217                                     if f_pg_type != f_obj_type:
2218                                         if f_pg_type != f_obj_type:
2219                                             logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2220                                         ok = True
2221                                         cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2222                                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2223                                         cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2224                                         cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2225                                         cr.commit()
2226                                     break
2227
2228                             if f_pg_type != f_obj_type:
2229                                 if not ok:
2230                                     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))
2231
2232                             # if the field is required and hasn't got a NOT NULL constraint
2233                             if f.required and f_pg_notnull == 0:
2234                                 # set the field to the default value if any
2235                                 if k in self._defaults:
2236                                     if callable(self._defaults[k]):
2237                                         default = self._defaults[k](self, cr, 1, context)
2238                                     else:
2239                                         default = self._defaults[k]
2240
2241                                     if (default is not None):
2242                                         ss = self._columns[k]._symbol_set
2243                                         query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2244                                         cr.execute(query, (ss[1](default),))
2245                                 # add the NOT NULL constraint
2246                                 cr.commit()
2247                                 try:
2248                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2249                                     cr.commit()
2250                                 except Exception, e:
2251                                     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))
2252                                 cr.commit()
2253                             elif not f.required and f_pg_notnull == 1:
2254                                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2255                                 cr.commit()
2256                             indexname = '%s_%s_index' % (self._table, k)
2257                             cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2258                             res = cr.dictfetchall()
2259                             if not res and f.select:
2260                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2261                                 cr.commit()
2262                             if res and not f.select:
2263                                 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2264                                 cr.commit()
2265                             if isinstance(f, fields.many2one):
2266                                 ref = self.pool.get(f._obj)._table
2267                                 if ref != 'ir_actions':
2268                                     cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2269                                                 'pg_attribute as att1, pg_attribute as att2 '
2270                                             'WHERE con.conrelid = cl1.oid '
2271                                                 'AND cl1.relname = %s '
2272                                                 'AND con.confrelid = cl2.oid '
2273                                                 'AND cl2.relname = %s '
2274                                                 'AND array_lower(con.conkey, 1) = 1 '
2275                                                 'AND con.conkey[1] = att1.attnum '
2276                                                 'AND att1.attrelid = cl1.oid '
2277                                                 'AND att1.attname = %s '
2278                                                 'AND array_lower(con.confkey, 1) = 1 '
2279                                                 'AND con.confkey[1] = att2.attnum '
2280                                                 'AND att2.attrelid = cl2.oid '
2281                                                 'AND att2.attname = %s '
2282                                                 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2283                                     res = cr.dictfetchall()
2284                                     if res:
2285                                         confdeltype = {
2286                                             'RESTRICT': 'r',
2287                                             'NO ACTION': 'a',
2288                                             'CASCADE': 'c',
2289                                             'SET NULL': 'n',
2290                                             'SET DEFAULT': 'd',
2291                                         }
2292                                         if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'):
2293                                             cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
2294                                             cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2295                                             cr.commit()
2296                     else:
2297                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2298             for order,f,k in todo_update_store:
2299                 todo_end.append((order, self._update_store, (f, k)))
2300
2301         else:
2302             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (self._table,))
2303             create = not bool(cr.fetchone())
2304
2305         for (key, con, _) in self._sql_constraints:
2306             conname = '%s_%s' % (self._table, key)
2307             cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2308             if not cr.dictfetchall():
2309                 try:
2310                     cr.execute('alter table "%s" add constraint "%s_%s" %s' % (self._table, self._table, key, con,))
2311                     cr.commit()
2312                 except:
2313                     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,))
2314
2315         if create:
2316             if hasattr(self, "_sql"):
2317                 for line in self._sql.split(';'):
2318                     line2 = line.replace('\n', '').strip()
2319                     if line2:
2320                         cr.execute(line2)
2321                         cr.commit()
2322         if store_compute:
2323             self._parent_store_compute(cr)
2324         return todo_end
2325
2326     def __init__(self, cr):
2327         super(orm, self).__init__(cr)
2328
2329         if not hasattr(self, '_log_access'):
2330             # if not access is not specify, it is the same value as _auto
2331             self._log_access = getattr(self, "_auto", True)
2332
2333         self._columns = self._columns.copy()
2334         for store_field in self._columns:
2335             f = self._columns[store_field]
2336             if hasattr(f, 'digits_change'):
2337                 f.digits_change(cr)
2338             if not isinstance(f, fields.function):
2339                 continue
2340             if not f.store:
2341                 continue
2342             if self._columns[store_field].store is True:
2343                 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2344             else:
2345                 sm = self._columns[store_field].store
2346             for object, aa in sm.items():
2347                 if len(aa)==4:
2348                     (fnct,fields2,order,length)=aa
2349                 elif len(aa)==3:
2350                     (fnct,fields2,order)=aa
2351                     length = None
2352                 else:
2353                     raise except_orm('Error',
2354                         ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2355                 self.pool._store_function.setdefault(object, [])
2356                 ok = True
2357                 for x,y,z,e,f,l in self.pool._store_function[object]:
2358                     if (x==self._name) and (y==store_field) and (e==fields2):
2359                         if f==order:
2360                             ok = False
2361                 if ok:
2362                     self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2363                     self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2364
2365         for (key, _, msg) in self._sql_constraints:
2366             self.pool._sql_error[self._table+'_'+key] = msg
2367
2368         # Load manual fields
2369
2370         cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2371         if cr.fetchone():
2372             cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2373             for field in cr.dictfetchall():
2374                 if field['name'] in self._columns:
2375                     continue
2376                 attrs = {
2377                     'string': field['field_description'],
2378                     'required': bool(field['required']),
2379                     'readonly': bool(field['readonly']),
2380                     'domain': field['domain'] or None,
2381                     'size': field['size'],
2382                     'ondelete': field['on_delete'],
2383                     'translate': (field['translate']),
2384                     #'select': int(field['select_level'])
2385                 }
2386
2387                 if field['ttype'] == 'selection':
2388                     self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2389                 elif field['ttype'] == 'reference':
2390                     self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2391                 elif field['ttype'] == 'many2one':
2392                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2393                 elif field['ttype'] == 'one2many':
2394                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2395                 elif field['ttype'] == 'many2many':
2396                     _rel1 = field['relation'].replace('.', '_')
2397                     _rel2 = field['model'].replace('.', '_')
2398                     _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2399                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2400                 else:
2401                     self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2402
2403         self._inherits_reload()
2404         if not self._sequence:
2405             self._sequence = self._table+'_id_seq'
2406         for k in self._defaults:
2407             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,)
2408         for f in self._columns:
2409             self._columns[f].restart()
2410
2411     def default_get(self, cr, uid, fields_list, context=None):
2412         if not context:
2413             context = {}
2414         value = {}
2415         # get the default values for the inherited fields
2416         for t in self._inherits.keys():
2417             value.update(self.pool.get(t).default_get(cr, uid, fields_list,
2418                 context))
2419
2420         # get the default values defined in the object
2421         for f in fields_list:
2422             if f in self._defaults:
2423                 if callable(self._defaults[f]):
2424                     value[f] = self._defaults[f](self, cr, uid, context)
2425                 else:
2426                     value[f] = self._defaults[f]
2427
2428             fld_def = ((f in self._columns) and self._columns[f]) \
2429                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
2430                     or False
2431             if isinstance(fld_def, fields.property):
2432                 property_obj = self.pool.get('ir.property')
2433                 definition_id = fld_def._field_get(cr, uid, self._name, f)
2434                 nid = property_obj.search(cr, uid, [('fields_id', '=',
2435                     definition_id), ('res_id', '=', False)])
2436                 if nid:
2437                     prop_value = property_obj.browse(cr, uid, nid[0],
2438                             context=context).value
2439                     value[f] = (prop_value and int(prop_value.split(',')[1])) \
2440                             or False
2441
2442         # get the default values set by the user and override the default
2443         # values defined in the object
2444         ir_values_obj = self.pool.get('ir.values')
2445         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
2446         for id, field, field_value in res:
2447             if field in fields_list:
2448                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
2449                 if fld_def._type in ('many2one', 'one2one'):
2450                     obj = self.pool.get(fld_def._obj)
2451                     if not obj.search(cr, uid, [('id', '=', field_value or False)]):
2452                         continue
2453                 if fld_def._type in ('many2many'):
2454                     obj = self.pool.get(fld_def._obj)
2455                     field_value2 = []
2456                     for i in range(len(field_value)):
2457                         if not obj.search(cr, uid, [('id', '=',
2458                             field_value[i])]):
2459                             continue
2460                         field_value2.append(field_value[i])
2461                     field_value = field_value2
2462                 if fld_def._type in ('one2many'):
2463                     obj = self.pool.get(fld_def._obj)
2464                     field_value2 = []
2465                     for i in range(len(field_value)):
2466                         field_value2.append({})
2467                         for field2 in field_value[i]:
2468                             if obj._columns[field2]._type in ('many2one', 'one2one'):
2469                                 obj2 = self.pool.get(obj._columns[field2]._obj)
2470                                 if not obj2.search(cr, uid,
2471                                         [('id', '=', field_value[i][field2])]):
2472                                     continue
2473                             # TODO add test for many2many and one2many
2474                             field_value2[i][field2] = field_value[i][field2]
2475                     field_value = field_value2
2476                 value[field] = field_value
2477         for key in context or {}:
2478             if key.startswith('default_') and (key[8:] in fields_list):
2479                 value[key[8:]] = context[key]
2480         return value
2481
2482     #
2483     # Update objects that uses this one to update their _inherits fields
2484     #
2485
2486     def _inherits_reload_src(self):
2487         for obj in self.pool.obj_pool.values():
2488             if self._name in obj._inherits:
2489                 obj._inherits_reload()
2490
2491     def _inherits_reload(self):
2492         res = {}
2493         for table in self._inherits:
2494             res.update(self.pool.get(table)._inherit_fields)
2495             for col in self.pool.get(table)._columns.keys():
2496                 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2497             for col in self.pool.get(table)._inherit_fields.keys():
2498                 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2499         self._inherit_fields = res
2500         self._inherits_reload_src()
2501
2502     def fields_get(self, cr, user, fields=None, context=None):
2503         ira = self.pool.get('ir.model.access')
2504         read_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2505                       ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2506         return super(orm, self).fields_get(cr, user, fields, context, read_access)
2507
2508     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2509         if not context:
2510             context = {}
2511         self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2512         if not fields:
2513             fields = self._columns.keys() + self._inherit_fields.keys()
2514         if isinstance(ids, (int, long)):
2515             select = [ids]
2516         else:
2517             select = ids
2518         select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2519         result = self._read_flat(cr, user, select, fields, context, load)
2520         for r in result:
2521             for key, v in r.items():
2522                 if v == None:
2523                     r[key] = False
2524                 if key in self._columns.keys():
2525                     type = self._columns[key]._type
2526                 elif key in self._inherit_fields.keys():
2527                     type = self._inherit_fields[key][2]._type
2528                 else:
2529                     continue
2530                 if type == 'reference' and v:
2531                     model,ref_id = v.split(',')
2532                     table = self.pool.get(model)._table
2533                     cr.execute('select id from "%s" where id=%s' % (table,ref_id))
2534                     id_exist = cr.fetchone()
2535                     if not id_exist:
2536                         cr.execute('update "'+self._table+'" set "'+key+'"=NULL where "%s"=%s' %(key,''.join("'"+str(v)+"'")))
2537                         r[key] = ''
2538         if isinstance(ids, (int, long, dict)):
2539             return result and result[0] or False
2540         return result
2541
2542     def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2543         if not context:
2544             context = {}
2545         #ids = map(lambda x:int(x), ids)
2546         if not ids:
2547             return []
2548         if fields_to_read == None:
2549             fields_to_read = self._columns.keys()
2550
2551         # construct a clause for the rules :
2552         d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2553         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2554         fields_pre = [f for f in fields_to_read if
2555                            f == self.CONCURRENCY_CHECK_FIELD
2556                         or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2557                      ] + self._inherits.values()
2558
2559         res = []
2560         if len(fields_pre):
2561             def convert_field(f):
2562                 if f in ('create_date', 'write_date'):
2563                     return "date_trunc('second', %s) as %s" % (f, f)
2564                 if f == self.CONCURRENCY_CHECK_FIELD:
2565                     if self._log_access:
2566                         return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2567                     return "now()::timestamp AS %s" % (f,)
2568                 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2569                     return 'length("%s") as "%s"' % (f, f)
2570                 return '"%s"' % (f,)
2571             fields_pre2 = map(convert_field, fields_pre)
2572             order_by = self._parent_order or self._order
2573             for i in range(0, len(ids), cr.IN_MAX):
2574                 sub_ids = ids[i:i+cr.IN_MAX]
2575                 if d1:
2576                     cr.execute('SELECT %s FROM %s WHERE %s.id = ANY (%%s) AND %s ORDER BY %s' % \
2577                             (','.join(fields_pre2 + [self._table + '.id']), ','.join(tables), self._table, ' and '.join(d1),
2578                                 order_by),[sub_ids,]+d2)
2579                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
2580                         raise except_orm(_('AccessError'),
2581                                 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2582                 else:
2583                     cr.execute('SELECT %s FROM \"%s\" WHERE id = ANY (%%s) ORDER BY %s' %
2584                                (','.join(fields_pre2 + ['id']), self._table,
2585                                 order_by), (sub_ids,))
2586                 res.extend(cr.dictfetchall())
2587         else:
2588             res = map(lambda x: {'id': x}, ids)
2589
2590         for f in fields_pre:
2591             if f == self.CONCURRENCY_CHECK_FIELD:
2592                 continue
2593             if self._columns[f].translate:
2594                 ids = map(lambda x: x['id'], res)
2595                 #TODO: optimize out of this loop
2596                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2597                 for r in res:
2598                     r[f] = res_trans.get(r['id'], False) or r[f]
2599
2600         for table in self._inherits:
2601             col = self._inherits[table]
2602             cols = intersect(self._inherit_fields.keys(), fields_to_read)
2603             if not cols:
2604                 continue
2605             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
2606
2607             res3 = {}
2608             for r in res2:
2609                 res3[r['id']] = r
2610                 del r['id']
2611
2612             for record in res:
2613                 if not record[col]:# if the record is deleted from _inherits table?
2614                     continue
2615                 record.update(res3[record[col]])
2616                 if col not in fields_to_read:
2617                     del record[col]
2618
2619         # all fields which need to be post-processed by a simple function (symbol_get)
2620         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
2621         if fields_post:
2622             for r in res:
2623                 for f in fields_post:
2624                     r[f] = self._columns[f]._symbol_get(r[f])
2625         ids = map(lambda x: x['id'], res)
2626
2627         # all non inherited fields for which the attribute whose name is in load is False
2628         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2629
2630         # Compute POST fields
2631         todo = {}
2632         for f in fields_post:
2633             todo.setdefault(self._columns[f]._multi, [])
2634             todo[self._columns[f]._multi].append(f)
2635         for key,val in todo.items():
2636             if key:
2637                 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2638                 for pos in val:
2639                     for record in res:
2640                         if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
2641                         record[pos] = res2[record['id']][pos]
2642             else:
2643                 for f in val:
2644                     res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2645                     for record in res:
2646                         if res2:
2647                             record[f] = res2[record['id']]
2648                         else:
2649                             record[f] = []
2650
2651 #for f in fields_post:
2652 #    # get the value of that field for all records/ids
2653 #    res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2654 #    for record in res:
2655 #        record[f] = res2[record['id']]
2656
2657         readonly = None
2658         for vals in res:
2659             for field in vals.copy():
2660                 fobj = None
2661                 if field in self._columns:
2662                     fobj = self._columns[field]
2663
2664                 if not fobj:
2665                     continue
2666                 groups = fobj.read
2667                 if groups:
2668                     edit = False
2669                     for group in groups:
2670                         module = group.split(".")[0]
2671                         grp = group.split(".")[1]
2672                         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" % \
2673                                    (grp, module, 'res.groups', user))
2674                         readonly = cr.fetchall()
2675                         if readonly[0][0] >= 1:
2676                             edit = True
2677                             break
2678                         elif readonly[0][0] == 0:
2679                             edit = False
2680                         else:
2681                             edit = False
2682
2683                     if not edit:
2684                         if type(vals[field]) == type([]):
2685                             vals[field] = []
2686                         elif type(vals[field]) == type(0.0):
2687                             vals[field] = 0
2688                         elif type(vals[field]) == type(''):
2689                             vals[field] = '=No Permission='
2690                         else:
2691                             vals[field] = False
2692         return res
2693
2694     def perm_read(self, cr, user, ids, context=None, details=True):
2695         if not context:
2696             context = {}
2697         if not ids:
2698             return []
2699         fields = ''
2700         if self._log_access:
2701             fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date'
2702         if isinstance(ids, (int, long)):
2703             ids_str = str(ids)
2704         else:
2705             ids_str = string.join(map(lambda x: str(x), ids), ',')
2706         cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
2707         res = cr.dictfetchall()
2708         for r in res:
2709             for key in r:
2710                 r[key] = r[key] or False
2711                 if key in ('write_uid', 'create_uid', 'uid') and details:
2712                     if r[key]:
2713                         r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
2714         if isinstance(ids, (int, long)):
2715             return res[ids]
2716         return res
2717
2718     def _check_concurrency(self, cr, ids, context):
2719         if not context:
2720             return
2721         if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
2722             def key(oid):
2723                 return "%s,%s" % (self._name, oid)
2724             santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
2725             for i in range(0, len(ids), cr.IN_MAX):
2726                 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
2727                                           for oid in ids[i:i+cr.IN_MAX]
2728                                           if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
2729                 if sub_ids:
2730                     cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
2731                     res = cr.fetchone()
2732                     if res and res[0]:
2733                         raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
2734
2735     def check_access_rule(self, cr, uid, ids, mode, context={}):
2736         d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, mode, context=context)
2737         if d1:
2738             d1 = ' and '+' and '.join(d1)
2739
2740         for i in range(0, len(ids), cr.IN_MAX):
2741             sub_ids = ids[i:i+cr.IN_MAX]
2742             ids_str = string.join(map(str, sub_ids), ',')
2743             if d1:
2744                 cr.execute('SELECT '+self._table+'.id FROM '+','.join(tables)+' ' \
2745                         'WHERE '+self._table+'.id IN ('+ids_str+')'+d1, d2)
2746                 if not cr.rowcount == len(sub_ids):
2747                     raise except_orm(_('AccessError'),
2748                                      _('You try to bypass an access rule to '+mode+
2749                                     ' (Document type: %s).') % self._name)
2750             else:
2751                 cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')')
2752                 if not cr.rowcount == len(sub_ids):
2753                     raise except_orm(_('AccessError'),
2754                                      _('You try to ' +mode+ ' a record that doesn\'t exist (Document type: %s).')
2755                                       % self._name)
2756         return ids_str
2757
2758     def unlink(self, cr, uid, ids, context=None):
2759         if not ids:
2760             return True
2761         if isinstance(ids, (int, long)):
2762             ids = [ids]
2763
2764         result_store = self._store_get_values(cr, uid, ids, None, context)
2765
2766         self._check_concurrency(cr, ids, context)
2767
2768         self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
2769
2770         properties = self.pool.get('ir.property')
2771         domain = [('res_id', '=', False),
2772                   ('value', 'in', ['%s,%s' % (self._name, i) for i in ids]),
2773                  ]
2774         if properties.search(cr, uid, domain, context=context):
2775             raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
2776
2777         wf_service = netsvc.LocalService("workflow")
2778         for oid in ids:
2779             wf_service.trg_delete(uid, self._name, oid, cr)
2780
2781         #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
2782         #res = cr.dictfetchall()
2783         #for key in self._inherits:
2784         #   ids2 = [x[self._inherits[key]] for x in res]
2785         #   self.pool.get(key).unlink(cr, uid, ids2)
2786
2787         ids_str = self.check_access_rule(cr, uid, ids, 'unlink', context=context)
2788         cr.execute('delete from '+self._table+' ' \
2789                     'where id in ('+ids_str+')', ids)
2790
2791         for order, object, store_ids, fields in result_store:
2792             if object<>self._name:
2793                 obj =  self.pool.get(object)
2794                 cr.execute('select id from '+obj._table+' where id in ('+','.join(map(str, store_ids))+')')
2795                 rids = map(lambda x: x[0], cr.fetchall())
2796                 if rids:
2797                     obj._store_set_values(cr, uid, rids, fields, context)
2798         return True
2799
2800     #
2801     # TODO: Validate
2802     #
2803     def write(self, cr, user, ids, vals, context=None):
2804         readonly = None
2805         for field in vals.copy():
2806             fobj = None
2807             if field in self._columns:
2808                 fobj = self._columns[field]
2809             else:
2810                 fobj = self._inherit_fields[field][2]
2811             if not fobj:
2812                 continue
2813             groups = fobj.write
2814
2815             if groups:
2816                 edit = False
2817                 for group in groups:
2818                     module = group.split(".")[0]
2819                     grp = group.split(".")[1]
2820                     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" % \
2821                                (grp, module, 'res.groups', user))
2822                     readonly = cr.fetchall()
2823                     if readonly[0][0] >= 1:
2824                         edit = True
2825                         break
2826                     elif readonly[0][0] == 0:
2827                         edit = False
2828                     else:
2829                         edit = False
2830
2831                 if not edit:
2832                     vals.pop(field)
2833
2834         if not context:
2835             context = {}
2836         if not ids:
2837             return True
2838         if isinstance(ids, (int, long)):
2839             ids = [ids]
2840
2841         self._check_concurrency(cr, ids, context)
2842
2843         self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
2844
2845
2846         upd0 = []
2847         upd1 = []
2848         upd_todo = []
2849         updend = []
2850         direct = []
2851         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
2852         for field in vals:
2853             if field in self._columns:
2854                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
2855                     if (not totranslate) or not self._columns[field].translate:
2856                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
2857                         upd1.append(self._columns[field]._symbol_set[1](vals[field]))
2858                     direct.append(field)
2859                 else:
2860                     upd_todo.append(field)
2861             else:
2862                 updend.append(field)
2863             if field in self._columns \
2864                     and hasattr(self._columns[field], 'selection') \
2865                     and vals[field]:
2866                 if self._columns[field]._type == 'reference':
2867                     val = vals[field].split(',')[0]
2868                 else:
2869                     val = vals[field]
2870                 if isinstance(self._columns[field].selection, (tuple, list)):
2871                     if val not in dict(self._columns[field].selection):
2872                         raise except_orm(_('ValidateError'),
2873                         _('The value "%s" for the field "%s" is not in the selection') \
2874                                 % (vals[field], field))
2875                 else:
2876                     if val not in dict(self._columns[field].selection(
2877                         self, cr, user, context=context)):
2878                         raise except_orm(_('ValidateError'),
2879                         _('The value "%s" for the field "%s" is not in the selection') \
2880                                 % (vals[field], field))
2881
2882         if self._log_access:
2883             upd0.append('write_uid=%s')
2884             upd0.append('write_date=now()')
2885             upd1.append(user)
2886
2887         if len(upd0):
2888             ids_str = self.check_access_rule(cr, user, ids, 'write', context=context)
2889             cr.execute('update '+self._table+' set '+string.join(upd0, ',')+' ' \
2890                     'where id in ('+ids_str+')', upd1)
2891
2892             if totranslate:
2893                 # TODO: optimize
2894                 for f in direct:
2895                     if self._columns[f].translate:
2896                         src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
2897                         if not src_trans:
2898                             src_trans = vals[f]
2899                             # Inserting value to DB
2900                             self.write(cr, user, ids, {f:vals[f]})
2901                         self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
2902
2903
2904         # call the 'set' method of fields which are not classic_write
2905         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
2906
2907         # default element in context must be removed when call a one2many or many2many
2908         rel_context = context.copy()
2909         for c in context.items():
2910             if c[0].startswith('default_'):
2911                 del rel_context[c[0]]
2912
2913         result = []
2914         for field in upd_todo:
2915             for id in ids:
2916                 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
2917
2918         for table in self._inherits:
2919             col = self._inherits[table]
2920             nids = []
2921             for i in range(0, len(ids), cr.IN_MAX):
2922                 sub_ids = ids[i:i+cr.IN_MAX]
2923                 ids_str = string.join(map(str, sub_ids), ',')
2924                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2925                         'where id in ('+ids_str+')', upd1)
2926                 nids.extend([x[0] for x in cr.fetchall()])
2927
2928             v = {}
2929             for val in updend:
2930                 if self._inherit_fields[val][0] == table:
2931                     v[val] = vals[val]
2932             self.pool.get(table).write(cr, user, nids, v, context)
2933
2934         self._validate(cr, user, ids, context)
2935 # TODO: use _order to set dest at the right position and not first node of parent
2936         if self._parent_store and (self._parent_name in vals):
2937             if self.pool._init:
2938                 self.pool._init_parent[self._name]=True
2939             else:
2940                 for id in ids:
2941                     # Find Position of the element
2942                     if vals[self._parent_name]:
2943                         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],))
2944                     else:
2945                         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))
2946                     result_p = cr.fetchall()
2947                     position = None
2948                     for (pleft,pright,pid) in result_p:
2949                         if pid == id:
2950                             break
2951                         position = pright+1
2952
2953                     # It's the first node of the parent: position = parent_left+1
2954                     if not position:
2955                         if not vals[self._parent_name]:
2956                             position = 1
2957                         else:
2958                             cr.execute('select parent_left from '+self._table+' where id=%s', (vals[self._parent_name],))
2959                             position = cr.fetchone()[0]+1
2960
2961                     # We have the new position !
2962                     cr.execute('select parent_left,parent_right from '+self._table+' where id=%s', (id,))
2963                     pleft,pright = cr.fetchone()
2964                     distance = pright - pleft + 1
2965
2966                     if position>pleft and position<=pright:
2967                         raise except_orm(_('UserError'), _('Recursivity Detected.'))
2968
2969                     if pleft<position:
2970                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2971                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2972                         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))
2973                     else:
2974                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2975                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2976                         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))
2977
2978         result += self._store_get_values(cr, user, ids, vals.keys(), context)
2979         for order, object, ids, fields in result:
2980             self.pool.get(object)._store_set_values(cr, user, ids, fields, context)
2981
2982         wf_service = netsvc.LocalService("workflow")
2983         for id in ids:
2984             wf_service.trg_write(user, self._name, id, cr)
2985         return True
2986
2987     #
2988     # TODO: Should set perm to user.xxx
2989     #
2990     def create(self, cr, user, vals, context=None):
2991         """ create(cr, user, vals, context) -> int
2992         cr = database cursor
2993         user = user id
2994         vals = dictionary of the form {'field_name':field_value, ...}
2995         """
2996         if not context:
2997             context = {}
2998         self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
2999
3000         default = []
3001
3002         avoid_table = []
3003         for (t, c) in self._inherits.items():
3004             if c in vals:
3005                 avoid_table.append(t)
3006         for f in self._columns.keys(): # + self._inherit_fields.keys():
3007             if not f in vals:
3008                 default.append(f)
3009
3010         for f in self._inherit_fields.keys():
3011             if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
3012                 default.append(f)
3013
3014         if len(default):
3015             default_values = self.default_get(cr, user, default, context)
3016             for dv in default_values:
3017                 if dv in self._columns and self._columns[dv]._type == 'many2many':
3018                     if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
3019                         default_values[dv] = [(6, 0, default_values[dv])]
3020
3021             vals.update(default_values)
3022
3023         tocreate = {}
3024         for v in self._inherits:
3025             if self._inherits[v] not in vals:
3026                 tocreate[v] = {}
3027             else:
3028                 tocreate[v] = {'id' : vals[self._inherits[v]]}
3029         (upd0, upd1, upd2) = ('', '', [])
3030         upd_todo = []
3031         for v in vals.keys():
3032             if v in self._inherit_fields:
3033                 (table, col, col_detail) = self._inherit_fields[v]
3034                 tocreate[table][v] = vals[v]
3035                 del vals[v]
3036             else:
3037                 if (v not in self._inherit_fields) and (v not in self._columns):
3038                     del vals[v]
3039
3040         # Try-except added to filter the creation of those records whose filds are readonly.
3041         # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3042         try:
3043             cr.execute("SELECT nextval('"+self._sequence+"')")
3044         except:
3045             raise except_orm(_('UserError'),
3046                         _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3047
3048         id_new = cr.fetchone()[0]
3049         for table in tocreate:
3050             if self._inherits[table] in vals:
3051                 del vals[self._inherits[table]]
3052
3053             record_id = tocreate[table].pop('id', None)
3054
3055             if record_id is None:
3056                 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3057             else:
3058                 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3059
3060             upd0 += ','+self._inherits[table]
3061             upd1 += ',%s'
3062             upd2.append(record_id)
3063
3064         #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3065         bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3066
3067         for bool_field in bool_fields:
3068             if bool_field not in vals:
3069                 vals[bool_field] = False
3070         #End
3071         for field in vals.copy():
3072             fobj = None
3073             if field in self._columns:
3074                 fobj = self._columns[field]
3075             else:
3076                 fobj = self._inherit_fields[field][2]
3077             if not fobj:
3078                 continue
3079             groups = fobj.write
3080             if groups:
3081                 edit = False
3082                 for group in groups:
3083                     module = group.split(".")[0]
3084                     grp = group.split(".")[1]
3085                     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" % \
3086                                (grp, module, 'res.groups', user))
3087                     readonly = cr.fetchall()
3088                     if readonly[0][0] >= 1:
3089                         edit = True
3090                         break
3091                     elif readonly[0][0] == 0:
3092                         edit = False
3093                     else:
3094                         edit = False
3095
3096                 if not edit:
3097                     vals.pop(field)
3098         for field in vals:
3099             if self._columns[field]._classic_write:
3100                 upd0 = upd0 + ',"' + field + '"'
3101                 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3102                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3103             else:
3104                 upd_todo.append(field)
3105             if field in self._columns \
3106                     and hasattr(self._columns[field], 'selection') \
3107                     and vals[field]:
3108                 if self._columns[field]._type == 'reference':
3109                     val = vals[field].split(',')[0]
3110                 else:
3111                     val = vals[field]
3112                 if isinstance(self._columns[field].selection, (tuple, list)):
3113                     if val not in dict(self._columns[field].selection):
3114                         raise except_orm(_('ValidateError'),
3115                         _('The value "%s" for the field "%s" is not in the selection') \
3116                                 % (vals[field], field))
3117                 else:
3118                     if val not in dict(self._columns[field].selection(
3119                         self, cr, user, context=context)):
3120                         raise except_orm(_('ValidateError'),
3121                         _('The value "%s" for the field "%s" is not in the selection') \
3122                                 % (vals[field], field))
3123         if self._log_access:
3124             upd0 += ',create_uid,create_date'
3125             upd1 += ',%s,now()'
3126             upd2.append(user)
3127         cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3128         d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'create', context=context)
3129         if d1:
3130             d1 = ' AND '+' AND '.join(d1)
3131             cr.execute('SELECT '+self._table+'.id FROM '+','.join(tables)+' ' \
3132                     'WHERE '+self._table+'.id = ' +str(id_new)+d1,d2)
3133             if not cr.rowcount:
3134                 raise except_orm(_('AccessError'),
3135                                  _('You try to bypass an access rule to create (Document type: %s).') \
3136                                   % self._name)
3137         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3138
3139         if self._parent_store:
3140             if self.pool._init:
3141                 self.pool._init_parent[self._name]=True
3142             else:
3143                 parent = vals.get(self._parent_name, False)
3144                 if parent:
3145                     cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3146                     pleft_old = None
3147                     result_p = cr.fetchall()
3148                     for (pleft,) in result_p:
3149                         if not pleft:
3150                             break
3151                         pleft_old = pleft
3152                     if not pleft_old:
3153                         cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3154                         pleft_old = cr.fetchone()[0]
3155                     pleft = pleft_old
3156                 else:
3157                     cr.execute('select max(parent_right) from '+self._table)
3158                     pleft = cr.fetchone()[0] or 0
3159                 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3160                 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3161                 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3162
3163         # default element in context must be remove when call a one2many or many2many
3164         rel_context = context.copy()
3165         for c in context.items():
3166             if c[0].startswith('default_'):
3167                 del rel_context[c[0]]
3168
3169         result = []
3170         for field in upd_todo:
3171             result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3172         self._validate(cr, user, [id_new], context)
3173
3174         if not context.get('no_store_function', False):
3175             result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3176             result.sort()
3177             done = []
3178             for order, object, ids, fields2 in result:
3179                 if not (object, ids, fields2) in done:
3180                     self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3181                     done.append((object, ids, fields2))
3182
3183         wf_service = netsvc.LocalService("workflow")
3184         wf_service.trg_create(user, self._name, id_new, cr)
3185         return id_new
3186
3187     def _store_get_values(self, cr, uid, ids, fields, context):
3188         result = {}
3189         fncts = self.pool._store_function.get(self._name, [])
3190         for fnct in range(len(fncts)):
3191             if fncts[fnct][3]:
3192                 ok = False
3193                 if not fields:
3194                     ok = True
3195                 for f in (fields or []):
3196                     if f in fncts[fnct][3]:
3197                         ok = True
3198                         break
3199                 if not ok:
3200                     continue
3201
3202             result.setdefault(fncts[fnct][0], {})
3203
3204             # uid == 1 for accessing objects having rules defined on store fields
3205             ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3206             for id in filter(None, ids2):
3207                 result[fncts[fnct][0]].setdefault(id, [])
3208                 result[fncts[fnct][0]][id].append(fnct)
3209         dict = {}
3210         for object in result:
3211             k2 = {}
3212             for id,fnct in result[object].items():
3213                 k2.setdefault(tuple(fnct), [])
3214                 k2[tuple(fnct)].append(id)
3215             for fnct,id in k2.items():
3216                 dict.setdefault(fncts[fnct[0]][4],[])
3217                 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3218         result2 = []
3219         tmp = dict.keys()
3220         tmp.sort()
3221         for k in tmp:
3222             result2+=dict[k]
3223         return result2
3224
3225     def _store_set_values(self, cr, uid, ids, fields, context):
3226         field_flag = False
3227         field_dict = {}
3228         if self._log_access:
3229             cr.execute('select id,write_date from '+self._table+' where id in ('+','.join(map(str, ids))+')')
3230             res = cr.fetchall()
3231             for r in res:
3232                 if r[1]:
3233                     field_dict.setdefault(r[0], [])
3234                     res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3235                     write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3236                     for i in self.pool._store_function.get(self._name, []):
3237                         if i[5]:
3238                             up_write_date = write_date + datetime.timedelta(hours=i[5])
3239                             if datetime.datetime.now() < up_write_date:
3240                                 if i[1] in fields:
3241                                     field_dict[r[0]].append(i[1])
3242                                     if not field_flag:
3243                                         field_flag = True
3244         todo = {}
3245         keys = []
3246         for f in fields:
3247             if self._columns[f]._multi not in keys:
3248                 keys.append(self._columns[f]._multi)
3249             todo.setdefault(self._columns[f]._multi, [])
3250             todo[self._columns[f]._multi].append(f)
3251         for key in keys:
3252             val = todo[key]
3253             if key:
3254                 # uid == 1 for accessing objects having rules defined on store fields
3255                 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3256                 for id,value in result.items():
3257                     if field_flag:
3258                         for f in value.keys():
3259                             if f in field_dict[id]:
3260                                 value.pop(f)
3261                     upd0 = []
3262                     upd1 = []
3263                     for v in value:
3264                         if v not in val:
3265                             continue
3266                         if self._columns[v]._type in ('many2one', 'one2one'):
3267                             try:
3268                                 value[v] = value[v][0]
3269                             except:
3270                                 pass
3271                         upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3272                         upd1.append(self._columns[v]._symbol_set[1](value[v]))
3273                     upd1.append(id)
3274                     cr.execute('update "' + self._table + '" set ' + \
3275                         string.join(upd0, ',') + ' where id = %s', upd1)
3276
3277             else:
3278                 for f in val:
3279                     # uid == 1 for accessing objects having rules defined on store fields
3280                     result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3281                     for r in result.keys():
3282                         if field_flag:
3283                             if r in field_dict.keys():
3284                                 if f in field_dict[r]:
3285                                     result.pop(r)
3286                     for id,value in result.items():
3287                         if self._columns[f]._type in ('many2one', 'one2one'):
3288                             try:
3289                                 value = value[0]
3290                             except:
3291                                 pass
3292                         cr.execute('update "' + self._table + '" set ' + \
3293                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3294         return True
3295
3296     #
3297     # TODO: Validate
3298     #
3299     def perm_write(self, cr, user, ids, fields, context=None):
3300         raise _('This method does not exist anymore')
3301
3302     # TODO: ameliorer avec NULL
3303     def _where_calc(self, cr, user, args, active_test=True, context=None):
3304         if not context:
3305             context = {}
3306         args = args[:]
3307         # if the object has a field named 'active', filter out all inactive
3308         # records unless they were explicitely asked for
3309         if 'active' in self._columns and (active_test and context.get('active_test', True)):
3310             if args:
3311                 active_in_args = False
3312                 for a in args:
3313                     if a[0] == 'active':
3314                         active_in_args = True
3315                 if not active_in_args:
3316                     args.insert(0, ('active', '=', 1))
3317             else:
3318                 args = [('active', '=', 1)]
3319
3320         if args:
3321             import expression
3322             e = expression.expression(args)
3323             e.parse(cr, user, self, context)
3324             tables = e.get_tables()
3325             qu1, qu2 = e.to_sql()
3326             qu1 = qu1 and [qu1] or []
3327         else:
3328             qu1, qu2, tables = [], [], ['"%s"' % self._table]
3329
3330         return (qu1, qu2, tables)
3331
3332     def _check_qorder(self, word):
3333         if not regex_order.match(word):
3334             raise except_orm(_('AccessError'), _('Bad query.'))
3335         return True
3336
3337     def search(self, cr, user, args, offset=0, limit=None, order=None,
3338             context=None, count=False):
3339         if not context:
3340             context = {}
3341         # compute the where, order by, limit and offset clauses
3342         (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
3343         dom = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3344         qu1 = qu1 + dom[0]
3345         qu2 = qu2 + dom[1]
3346         for t in dom[2]:
3347             if t not in tables:
3348                 tables.append(t)
3349
3350         if len(qu1):
3351             qu1 = ' where '+string.join(qu1, ' and ')
3352         else:
3353             qu1 = ''
3354
3355
3356         order_by = self._order
3357         if order:
3358             self._check_qorder(order)
3359             o = order.split(' ')[0]
3360             if (o in self._columns) and getattr(self._columns[o], '_classic_write'):
3361                 order_by = order
3362
3363         limit_str = limit and ' limit %d' % limit or ''
3364         offset_str = offset and ' offset %d' % offset or ''
3365
3366
3367         if count:
3368             cr.execute('select count(%s.id) from ' % self._table +
3369                     ','.join(tables) +qu1 + limit_str + offset_str, qu2)
3370             res = cr.fetchall()
3371             return res[0][0]
3372         cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
3373         res = cr.fetchall()
3374         return [x[0] for x in res]
3375
3376     # returns the different values ever entered for one field
3377     # this is used, for example, in the client when the user hits enter on
3378     # a char field
3379     def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3380         if not args:
3381             args = []
3382         if field in self._inherit_fields:
3383             return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3384         else:
3385             return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3386
3387     def name_get(self, cr, user, ids, context=None):
3388         if not context:
3389             context = {}
3390         if not ids:
3391             return []
3392         if isinstance(ids, (int, long)):
3393             ids = [ids]
3394         return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
3395             [self._rec_name], context, load='_classic_write')]
3396
3397     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
3398         if not args:
3399             args = []
3400         if not context:
3401             context = {}
3402         args = args[:]
3403         if name:
3404             args += [(self._rec_name, operator, name)]
3405         ids = self.search(cr, user, args, limit=limit, context=context)
3406         res = self.name_get(cr, user, ids, context)
3407         return res
3408
3409     def copy_data(self, cr, uid, id, default=None, context=None):
3410         if not context:
3411             context = {}
3412         if not default:
3413             default = {}
3414         if 'state' not in default:
3415             if 'state' in self._defaults:
3416                 if callable(self._defaults['state']):
3417                     default['state'] = self._defaults['state'](self, cr, uid, context)
3418                 else:
3419                     default['state'] = self._defaults['state']
3420
3421         data = self.read(cr, uid, [id], context=context)[0]
3422         fields = self.fields_get(cr, uid, context=context)
3423         trans_data=[]
3424         for f in fields:
3425             ftype = fields[f]['type']
3426
3427             if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3428                 del data[f]
3429
3430             if f in default:
3431                 data[f] = default[f]
3432             elif ftype == 'function':
3433                 del data[f]
3434             elif ftype == 'many2one':
3435                 try:
3436                     data[f] = data[f] and data[f][0]
3437                 except:
3438                     pass
3439             elif ftype in ('one2many', 'one2one'):
3440                 res = []
3441                 rel = self.pool.get(fields[f]['relation'])
3442                 if data[f] != False:
3443                     for rel_id in data[f]:
3444                         # the lines are first duplicated using the wrong (old)
3445                         # parent but then are reassigned to the correct one thanks
3446                         # to the (4, ...)
3447                         d,t = rel.copy_data(cr, uid, rel_id, context=context)
3448                         res.append((0, 0, d))
3449                         trans_data += t
3450                 data[f] = res
3451             elif ftype == 'many2many':
3452                 data[f] = [(6, 0, data[f])]
3453
3454         trans_obj = self.pool.get('ir.translation')
3455         #TODO: optimize translations
3456         trans_name=''
3457         for f in fields:
3458             trans_flag=True
3459             if f in self._columns and self._columns[f].translate:
3460                 trans_name=self._name+","+f
3461             elif f in self._inherit_fields and self._inherit_fields[f][2].translate:
3462                 trans_name=self._inherit_fields[f][0]+","+f
3463             else:
3464                 trans_flag=False
3465
3466             if trans_flag:
3467                 trans_ids = trans_obj.search(cr, uid, [
3468                         ('name', '=', trans_name),
3469                         ('res_id','=',data['id'])
3470                     ])
3471
3472                 trans_data.extend(trans_obj.read(cr,uid,trans_ids,context=context))
3473
3474         del data['id']
3475
3476         for v in self._inherits:
3477             del data[self._inherits[v]]
3478         return data, trans_data
3479
3480     def copy(self, cr, uid, id, default=None, context=None):
3481         trans_obj = self.pool.get('ir.translation')
3482         data, trans_data = self.copy_data(cr, uid, id, default, context)
3483         new_id = self.create(cr, uid, data, context)
3484         for record in trans_data:
3485             del record['id']
3486             record['res_id'] = new_id
3487             trans_obj.create(cr, uid, record, context)
3488         return new_id
3489
3490     def exists(self, cr, uid, id, context=None):
3491         cr.execute('SELECT count(1) FROM "%s" where id=%%s' % (self._table,), (id,))
3492         return bool(cr.fetchone()[0])
3493
3494     def check_recursion(self, cr, uid, ids, parent=None):
3495         if not parent:
3496             parent = self._parent_name
3497         ids_parent = ids[:]
3498         while len(ids_parent):
3499             ids_parent2 = []
3500             for i in range(0, len(ids), cr.IN_MAX):
3501                 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
3502                 cr.execute('SELECT distinct "'+parent+'"'+
3503                     ' FROM "'+self._table+'" ' \
3504                     'WHERE id = ANY(%s)',(sub_ids_parent,))
3505                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
3506             ids_parent = ids_parent2
3507             for i in ids_parent:
3508                 if i in ids:
3509                     return False
3510         return True
3511
3512 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: