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