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