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