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