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