~openerp-dev/openobject-server/trunk-bug-712254-ysa

« back to all changes in this revision

Viewing changes to bin/osv/orm.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: latin1 -*-
 
2
##############################################################################
 
3
#
 
4
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
 
5
#
 
6
# $Id: orm.py 1008 2005-07-25 14:03:55Z pinky $
 
7
#
 
8
# WARNING: This program as such is intended to be used by professional
 
9
# programmers who take the whole responsability of assessing all potential
 
10
# consequences resulting from its eventual inadequacies and bugs
 
11
# End users who are looking for a ready-to-use solution with commercial
 
12
# garantees and support are strongly adviced to contract a Free Software
 
13
# Service Company
 
14
#
 
15
# This program is Free Software; you can redistribute it and/or
 
16
# modify it under the terms of the GNU General Public License
 
17
# as published by the Free Software Foundation; either version 2
 
18
# of the License, or (at your option) any later version.
 
19
#
 
20
# This program is distributed in the hope that it will be useful,
 
21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
23
# GNU General Public License for more details.
 
24
#
 
25
# You should have received a copy of the GNU General Public License
 
26
# along with this program; if not, write to the Free Software
 
27
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
28
#
 
29
##############################################################################
 
30
 
 
31
#
 
32
# Object relationnal mapping to postgresql module
 
33
#    . Hierarchical structure
 
34
#    . Constraints consistency, validations
 
35
#    . Object meta Data depends on its status
 
36
#    . Optimised processing by complex query (multiple actions at once)
 
37
#    . Default fields value
 
38
#    . Permissions optimisation
 
39
#    . Multi-company features
 
40
#    . Persistant object: DB postgresql
 
41
#    . Datas conversions
 
42
#    . Multi-level caching system
 
43
#    . 2 different inheritancies
 
44
#    . Fields:
 
45
#         - classicals (varchar, integer, boolean, ...)
 
46
#         - relations (one2many, many2one, many2many)
 
47
#         - functions
 
48
#
 
49
#
 
50
 
 
51
from xml import dom
 
52
from xml.parsers import expat
 
53
import string
 
54
import pooler
 
55
import psycopg
 
56
import netsvc
 
57
import re
 
58
 
 
59
import fields
 
60
import ir
 
61
import tools
 
62
 
 
63
prof = 0
 
64
 
 
65
def intersect(la, lb):
 
66
        return filter(lambda x: x in lb, la)
 
67
 
 
68
class except_orm(Exception):
 
69
        def __init__(self, name, value):
 
70
                self.name = name
 
71
                self.value = value
 
72
                self.args='no error args'
 
73
 
 
74
#class find_fields(object):
 
75
#       def _start_el(self,name, attrs):
 
76
#               if name == 'field' and attrs.get('name', False):
 
77
#                       self.datas[str(attrs['name'])] = attrs.get('preload','')
 
78
#       def __init__(self):
 
79
#               self.datas = {}
 
80
#       def parse(self, datas):
 
81
#               p = expat.ParserCreate()
 
82
#               p.StartElementHandler = self._start_el
 
83
#               p.Parse(datas, 1)
 
84
#               return self.datas
 
85
 
 
86
#
 
87
# TODO: trigger pour chaque action
 
88
#
 
89
# Readonly python database object browser
 
90
class browse_null(object):
 
91
        def __init__(self):
 
92
                self.id=False
 
93
        def __getitem__(self, name):
 
94
                return False
 
95
        def __int__(self):
 
96
                return False
 
97
        def __str__(self):
 
98
                return ''
 
99
        def __nonzero__(self):
 
100
                return False
 
101
 
 
102
#
 
103
# TODO: execute an object method on browse_record_list
 
104
#
 
105
class browse_record_list(list):
 
106
        def __init__(self, lst, context={}):
 
107
                super(browse_record_list, self).__init__(lst)
 
108
                self.context = context
 
109
 
 
110
#
 
111
# table : the object (inherited from orm)
 
112
# context : a dictionnary with an optionnal context
 
113
#    default to : browse_record_list
 
114
#
 
115
class browse_record(object):
 
116
        def __init__(self, cr, uid, id, table, cache, context={}, list_class = None):
 
117
                self._list_class = list_class or browse_record_list
 
118
                self._cr = cr
 
119
                self._uid = uid
 
120
                self._id = id
 
121
                self._table = table
 
122
                self._table_name = self._table._name
 
123
                self._context = context
 
124
 
 
125
                cache.setdefault(table._name, {})
 
126
                self._data = cache[table._name]
 
127
                if not id in self._data:
 
128
                        self._data[id] = {'id':id}
 
129
                self._cache = cache
 
130
 
 
131
        def __getitem__(self, name):
 
132
                if name == 'id':
 
133
                        return self._id
 
134
                if not self._data[self._id].has_key(name):
 
135
                        # build the list of fields we will fetch
 
136
 
 
137
                        # fetch the definition of the field which was asked for
 
138
                        if name in self._table._columns:
 
139
                                col = self._table._columns[name]
 
140
                        elif name in self._table._inherit_fields:
 
141
                                col = self._table._inherit_fields[name][2]
 
142
                        elif hasattr(self._table, name):
 
143
                                return getattr(self._table, name)
 
144
                        else:
 
145
                                print "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name)
 
146
                                return False
 
147
                        
 
148
                        # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
 
149
                        if col._classic_write:
 
150
                                # gen the list of "local" (ie not inherited) fields which are classic or many2one
 
151
                                ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
 
152
                                # gen the list of inherited fields
 
153
                                inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
 
154
                                # complete the field list with the inherited fields which are classic or many2one
 
155
                                ffields += filter(lambda x: x[1]._classic_write, inherits)
 
156
                        # otherwise we fetch only that field
 
157
                        else:
 
158
                                ffields = [(name,col)]
 
159
                                
 
160
                        if isinstance(col, fields.function):
 
161
                                ids = [self._id]
 
162
                        else:
 
163
                                # filter out all ids which were already fetched (and are already in _data)
 
164
                                ids = filter(lambda id: not self._data[id].has_key(name), self._data.keys())
 
165
                        
 
166
                        # read the data
 
167
                        datas = self._table.read(self._cr, self._uid, ids, map(lambda x: x[0], ffields), context=self._context, load="_classic_write")
 
168
                        
 
169
                        # create browse records for 'remote' objects
 
170
                        for data in datas:
 
171
                                for n,f in ffields:
 
172
                                        if isinstance(f, fields.many2one) or isinstance(f, fields.one2one):
 
173
                                                if data[n]:
 
174
                                                        obj = self._table.pool.get(f._obj)
 
175
                                                        data[n] = browse_record(self._cr, self._uid, data[n], obj, self._cache, context=self._context, list_class=self._list_class)
 
176
                                                else:
 
177
                                                        data[n] = browse_null()
 
178
                                        elif isinstance(f, (fields.one2many, fields.many2many)) and len(data[n]):
 
179
                                                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) for id in data[n]], self._context)
 
180
                                self._data[data['id']].update(data)
 
181
                return self._data[self._id][name]
 
182
 
 
183
        def __getattr__(self, name):
 
184
#               raise an AttributeError exception.
 
185
                return self[name]
 
186
 
 
187
        def __int__(self):
 
188
                return self._id
 
189
 
 
190
        def __str__(self):
 
191
                return "browse_record(%s, %d)" % (self._table_name, self._id)
 
192
 
 
193
        def __eq__(self, other):
 
194
                return (self._table_name, self._id) == (other._table_name, other._id)
 
195
 
 
196
        def __ne__(self, other):
 
197
                return (self._table_name, self._id) != (other._table_name, other._id)
 
198
 
 
199
        # we need to define __unicode__ even though we've already defined __str__ 
 
200
        # because we have overridden __getattr__
 
201
        def __unicode__(self):
 
202
                return unicode(str(self))
 
203
 
 
204
        def __hash__(self):
 
205
                return hash((self._table_name, self._id))
 
206
 
 
207
        __repr__ = __str__
 
208
 
 
209
 
 
210
# returns a tuple (type returned by postgres when the column was created, type expression to create the column)
 
211
def get_pg_type(f):
 
212
        type_dict = {fields.boolean:'bool', fields.integer:'int4', fields.text:'text', fields.date:'date', fields.time:'time', fields.datetime:'timestamp', fields.binary:'bytea', fields.many2one:'int4'}
 
213
 
 
214
        if type_dict.has_key(type(f)):
 
215
                f_type = (type_dict[type(f)], type_dict[type(f)])
 
216
        elif isinstance(f, fields.float):
 
217
                if f.digits:
 
218
                        f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0],f.digits[1]))
 
219
                else:
 
220
                        f_type = ('float8', 'DOUBLE PRECISION')
 
221
        elif isinstance(f, (fields.char, fields.reference)):
 
222
                f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
 
223
        elif isinstance(f, fields.selection):
 
224
                if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
 
225
                        f_size = reduce(lambda x,y: max(x,len(y[0])), f.selection, 16)
 
226
                elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
 
227
                        f_size = -1
 
228
                else:
 
229
                        f_size = (hasattr(f,'size') and f.size) or 16
 
230
                        
 
231
                if f_size == -1:
 
232
                        f_type = ('int4', 'INTEGER')
 
233
                else:
 
234
                        f_type = ('varchar', 'VARCHAR(%d)' % f_size)
 
235
        else:
 
236
                print "WARNING: type not supported!"
 
237
                f_type = None
 
238
        return f_type
 
239
 
 
240
                
 
241
        
 
242
class orm(object):
 
243
        _columns = {}
 
244
        _sql_constraints = []
 
245
        _constraints = []
 
246
        _defaults = {}
 
247
        _log_access = True
 
248
        _table = None
 
249
        _name = None
 
250
        _rec_name = 'name'
 
251
        _order = 'id'
 
252
        _inherits = {}
 
253
        _sequence = None
 
254
        _description = None
 
255
        _protected = ['read','write','create','default_get','perm_read','perm_write','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data']
 
256
        def _field_create(self, cr):
 
257
                cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name)
 
258
                if not cr.rowcount:
 
259
                        # reference model in order to have a description of its fonctionnality in custom_report
 
260
                        cr.execute("INSERT INTO ir_model (model, name, info) VALUES (%s, %s, %s)", (self._name, self._description, self.__doc__)) 
 
261
                cr.commit()
 
262
 
 
263
                for k in self._columns:
 
264
                        f = self._columns[k]
 
265
                        cr.execute("select id from ir_model_fields where model=%s and name=%s", (self._name,k))
 
266
                        if not cr.rowcount:
 
267
                                cr.execute("select id from ir_model where model='%s'" % self._name)
 
268
                                model_id = cr.fetchone()[0]
 
269
                                cr.execute("INSERT INTO ir_model_fields (model_id, model, name, field_description, ttype, relate,relation,group_name,view_load) VALUES (%d,%s,%s,%s,%s,%s,%s,%s,%s)", (model_id, self._name, k, f.string.replace("'", " "), f._type, (f.relate and 'True') or 'False', f._obj or 'NULL', f.group_name or '', (f.view_load and 'True') or 'False'))
 
270
                cr.commit()
 
271
 
 
272
        def _auto_init(self, cr):
 
273
#               print '*' * 60
 
274
#               print "auto_init", self._name, self._columns
 
275
                create = False
 
276
                self._field_create(cr)
 
277
                if not hasattr(self, "_auto") or self._auto:
 
278
                        cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
 
279
                        if not cr.rowcount:
 
280
                                cr.execute("CREATE TABLE %s(id SERIAL NOT NULL, perm_id INTEGER, PRIMARY KEY(id)) WITH OIDS" % self._table)
 
281
                                create = True
 
282
                        cr.commit()
 
283
                        if self._log_access:
 
284
                                logs = {
 
285
                                        'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
 
286
                                        'create_date': 'TIMESTAMP',
 
287
                                        'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
 
288
                                        'write_date': 'TIMESTAMP'
 
289
                                }
 
290
                                for k in logs:
 
291
                                        cr.execute(
 
292
                                                """
 
293
                                                SELECT c.relname 
 
294
                                                FROM pg_class c, pg_attribute a
 
295
                                                WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid
 
296
                                                """ % (self._table, k))
 
297
                                        if not cr.rowcount:
 
298
                                                cr.execute("ALTER TABLE %s ADD COLUMN %s %s" % 
 
299
                                                        (self._table, k, logs[k]))
 
300
                                                cr.commit()
 
301
 
 
302
                        # iterate on the database columns to drop the NOT NULL constraints
 
303
                        # of fields which were required but have been removed
 
304
                        cr.execute(
 
305
                                "SELECT a.attname, a.attnotnull "\
 
306
                                "FROM pg_class c, pg_attribute a "\
 
307
                                "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table)
 
308
                        db_columns = cr.dictfetchall()
 
309
                        for column in db_columns:
 
310
                                if column['attname'] not in ('id', 'oid', 'tableoid', 'ctid', 'xmin', 'xmax', 'cmin', 'cmax'):
 
311
                                        if column['attnotnull'] and column['attname'] not in self._columns:
 
312
                                                cr.execute("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL" % (self._table, column['attname']))
 
313
 
 
314
                        # iterate on the "object columns"
 
315
                        for k in self._columns:
 
316
                                if k in ('id','perm_id','write_uid','write_date','create_uid','create_date'):
 
317
                                        continue
 
318
                                        #raise 'Can not define a column %s. Reserved keyword !' % (k,)
 
319
                                f = self._columns[k]
 
320
 
 
321
                                if isinstance(f, fields.one2many):
 
322
                                        cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'"%f._obj)
 
323
                                        if cr.fetchone():
 
324
                                                q = """SELECT count(*) 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)
 
325
                                                cr.execute(q)
 
326
                                                res = cr.fetchone()[0]
 
327
                                                if not res:
 
328
                                                        cr.execute("ALTER TABLE %s ADD FOREIGN KEY (%s) REFERENCES %s ON DELETE SET NULL" % (self._obj, f._fields_id, f._table))
 
329
                                elif isinstance(f, fields.many2many):
 
330
                                        cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'"%f._rel)
 
331
                                        if not cr.dictfetchall():
 
332
                                                #FIXME: Remove this try/except
 
333
                                                try:
 
334
                                                        ref = self.pool.get(f._obj)._table
 
335
                                                except AttributeError:
 
336
                                                        ref = f._obj.replace('.','_')
 
337
                                                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))
 
338
                                                cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (f._rel,f._id1,f._rel,f._id1))
 
339
                                                cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (f._rel,f._id2,f._rel,f._id2))
 
340
                                                cr.commit()
 
341
                                else:
 
342
                                        q = """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 FROM pg_class c,pg_attribute a,pg_type t WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid AND a.atttypid=t.oid""" % (self._table, k)
 
343
                                        cr.execute(q)
 
344
                                        res = cr.dictfetchall() 
 
345
                                        if not res:
 
346
                                                if not isinstance(f,fields.function):
 
347
 
 
348
                                                        # add the missing field
 
349
                                                        cr.execute("ALTER TABLE %s ADD COLUMN %s %s" % (self._table, k, get_pg_type(f)[1]))
 
350
                                                        
 
351
                                                        # initialize it
 
352
                                                        if not create and k in self._defaults:
 
353
                                                                default = self._defaults[k](self, cr, 1, {})
 
354
                                                                if not default:
 
355
                                                                        cr.execute("UPDATE %s SET %s=NULL" % (self._table, k))
 
356
                                                                else:
 
357
                                                                        cr.execute("UPDATE %s SET %s='%s'" % (self._table, k, default))
 
358
 
 
359
                                                        # and add constraints if needed
 
360
                                                        if isinstance(f, fields.many2one):
 
361
                                                                #FIXME: Remove this try/except
 
362
                                                                try:
 
363
                                                                        ref = self.pool.get(f._obj)._table
 
364
                                                                except AttributeError:
 
365
                                                                        ref = f._obj.replace('.','_')
 
366
                                                                cr.execute("ALTER TABLE %s ADD FOREIGN KEY (%s) REFERENCES %s ON DELETE %s" % (self._table, k, ref, f.ondelete))
 
367
                                                        if f.select:
 
368
                                                                cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (self._table, k, self._table, k))
 
369
                                                        if f.required:
 
370
                                                                cr.commit()
 
371
                                                                try:
 
372
                                                                        cr.execute("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k))
 
373
                                                                except:
 
374
                                                                        print '-'*50
 
375
                                                                        print 'WARNING: unable to set column %s of table %s not null !' % (k,self._table)
 
376
                                                                        print '\tTry to re-run: tinyerp-server.py --update=module'
 
377
                                                                        print '\tIf it doesn\'t work, update records and execute manually:'
 
378
                                                                        print "\tALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k)
 
379
                                                        cr.commit()
 
380
                                        elif len(res)==1:
 
381
                                                f_pg_def = res[0]
 
382
                                                f_pg_type = f_pg_def['typname']
 
383
                                                f_pg_size = f_pg_def['size']
 
384
                                                f_pg_notnull = f_pg_def['attnotnull']
 
385
                                                if isinstance(f, fields.function):
 
386
                                                        print '-'*60
 
387
                                                        print "WARNING: column %s (%s) in table %s was converted to a function !" % (k, f.string, self._table)
 
388
                                                        print "\tYou should remove this column from your database."
 
389
                                                        f_obj_type = None
 
390
                                                else:
 
391
                                                        f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
 
392
                                                
 
393
                                                if f_obj_type:
 
394
                                                        if f_pg_type != f_obj_type:
 
395
                                                                print "WARNING: column '%s' in table '%s' has changed type (DB = %s, def = %s) !" % (k, self._table, f_pg_type, f._type)
 
396
                                                        if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
 
397
                                                                # columns with the name 'type' cannot be changed for an unknown reason?!
 
398
                                                                if k != 'type':
 
399
                                                                        if f_pg_size > f.size:
 
400
                                                                                print "WARNING: column '%s' in table '%s' has changed size (DB = %d, def = %d), strings will be truncated !" % (k, self._table, f_pg_size, f.size)
 
401
#TODO: check si y a des donnees qui vont poser probleme (select char_length(...))
 
402
#TODO: issue a log message even if f_pg_size < f.size
 
403
                                                                        cr.execute("ALTER TABLE %s RENAME COLUMN %s TO temp_change_size" % (self._table,k))
 
404
                                                                        cr.execute("ALTER TABLE %s ADD COLUMN %s VARCHAR(%d)" % (self._table,k,f.size))
 
405
                                                                        cr.execute("UPDATE %s SET %s=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size))
 
406
                                                                        cr.execute("ALTER TABLE %s DROP COLUMN temp_change_size" % (self._table,))
 
407
                                                                        cr.commit()
 
408
                                                        # if the field is required and hasn't got a NOT NULL constraint
 
409
                                                        if f.required and f_pg_notnull == 0:
 
410
                                                                # set the field to the default value if any
 
411
                                                                if self._defaults.has_key(k):
 
412
                                                                        default = self._defaults[k](self, cr, 1, {})
 
413
                                                                        cr.execute("UPDATE %s SET %s='%s' WHERE %s is NULL" % (self._table, k, default, k))
 
414
                                                                        cr.commit()
 
415
                                                                # add the NOT NULL constraint
 
416
                                                                try:
 
417
                                                                        cr.execute("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k))
 
418
                                                                        cr.commit()
 
419
                                                                except:
 
420
#TODO: use the logger                                                           
 
421
                                                                        print '-'*50
 
422
                                                                        print 'WARNING: unable to set a NOT NULL constraint on column %s of the %s table !' % (k,self._table)
 
423
                                                                        print "\tIf you want to have it, you should update the records and execute manually:"
 
424
                                                                        print "\tALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k)
 
425
                                                                cr.commit()
 
426
                                                        elif not f.required and f_pg_notnull == 1:
 
427
                                                                cr.execute("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL" % (self._table,k))
 
428
                                                                cr.commit()
 
429
                                                        cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table))
 
430
                                                        res = cr.dictfetchall()
 
431
                                                        if not res and f.select:
 
432
                                                                cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (self._table, k, self._table, k))
 
433
                                                                cr.commit()
 
434
                                                        if res and not f.select:
 
435
                                                                cr.execute("DROP INDEX %s_%s_index" % (self._table, k))
 
436
                                                                cr.commit()
 
437
                                        else:
 
438
                                                print "ERROR"
 
439
                else:
 
440
                        cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
 
441
                        create = not bool(cr.fetchone())
 
442
 
 
443
                if create:
 
444
                        for (key,con,_) in self._sql_constraints:
 
445
                                cr.execute('alter table %s add constraint %s_%s %s' % (self._table,self._table,key, con,))
 
446
                                cr.commit()
 
447
 
 
448
                        if hasattr(self,"_sql"):
 
449
                                for line in self._sql.split(';'):
 
450
                                        line2 = line.replace('\n','').strip()
 
451
                                        if line2:
 
452
                                                cr.execute(line2)
 
453
                                                cr.commit()
 
454
 
 
455
        def __init__(self):
 
456
                if not self._table:
 
457
                        self._table=self._name.replace('.','_')
 
458
                if not self._description:
 
459
                        self._description = self._name
 
460
                for (key,_,msg) in self._sql_constraints:
 
461
                        self.pool._sql_error[self._table+'_'+key] = msg
 
462
                
 
463
#               if self.__class__.__name__ != 'fake_class':
 
464
                self._inherits_reload()
 
465
                if not self._sequence:
 
466
                        self._sequence = self._table+'_id_seq'
 
467
                for k in self._defaults:
 
468
                        assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined but field %s does not exist !' % (k,)
 
469
 
 
470
        #
 
471
        # Update objects that uses this one to update their _inherits fields
 
472
        #
 
473
        def _inherits_reload_src(self):
 
474
                for obj in self.pool.obj_pool.values():
 
475
                        if self._name in obj._inherits:
 
476
                                obj._inherits_reload()
 
477
 
 
478
        def _inherits_reload(self):
 
479
                res = {}
 
480
                for table in self._inherits:
 
481
                        res.update(self.pool.get(table)._inherit_fields)
 
482
                        for col in self.pool.get(table)._columns.keys():
 
483
                                res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col])
 
484
                        for col in self.pool.get(table)._inherit_fields.keys():
 
485
                                res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
 
486
                self._inherit_fields=res
 
487
                self._inherits_reload_src()
 
488
 
 
489
        def browse(self, cr, uid, select, context={}, list_class=None):
 
490
                self._list_class = list_class or browse_record_list
 
491
                cache = {}
 
492
                # need to accepts ints and longs because ids coming from a method 
 
493
                # launched by button in the interface have a type long...
 
494
                if isinstance(select, (int, long)):
 
495
                        return browse_record(cr,uid,select,self,cache, context=context, list_class=self._list_class)
 
496
                elif isinstance(select,list):
 
497
                        return self._list_class([browse_record(cr,uid,id,self,cache, context=context, list_class=self._list_class) for id in select], context)
 
498
                else:
 
499
                        return []
 
500
 
 
501
        # TODO: implement this
 
502
        def __export_row(self, cr, uid, row, fields, prefix, context={}):
 
503
                lines = []
 
504
                data = map(lambda x: '', range(len(fields)))
 
505
                done = []
 
506
                for fpos in range(len(fields)):
 
507
                        f = fields[fpos]
 
508
                        if f and f[:len(prefix)] == prefix:
 
509
                                r = row
 
510
                                i = 0
 
511
                                while i<len(f):
 
512
                                        r = r[f[i]]
 
513
                                        if not r:
 
514
                                                break
 
515
                                        if isinstance(r, (browse_record_list, list)):
 
516
                                                first = True
 
517
                                                fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) or [], fields)
 
518
                                                if fields2 in done:
 
519
                                                        break
 
520
                                                done.append(fields2)
 
521
                                                for row2 in r:
 
522
                                                        lines2 = self.__export_row(cr, uid, row2, fields2, f[i+2:], context)
 
523
                                                        if first:
 
524
                                                                for fpos2 in range(len(fields)):
 
525
                                                                        if lines2 and lines2[0][fpos2]:
 
526
                                                                                data[fpos2] = lines2[0][fpos2]
 
527
                                                                lines+= lines2[1:]
 
528
                                                                first = False
 
529
                                                        else:
 
530
                                                                lines+= lines2
 
531
                                                break
 
532
                                        i+=1
 
533
                                if i==len(f):
 
534
                                        data[fpos] = str(r or '')
 
535
                return [data] + lines
 
536
 
 
537
        def export_data(self, cr, uid, ids, fields, context={}):
 
538
                fields = map(lambda x: x.split('/'), fields)
 
539
                datas = []
 
540
                for row in self.browse(cr, uid, ids, context):
 
541
                        datas += self.__export_row(cr, uid, row, fields, [], context)
 
542
                return datas
 
543
 
 
544
        def import_data(self, cr, uid, fields, datas, context={}):
 
545
                fields = map(lambda x: x.split('/'), fields)
 
546
 
 
547
                def process_liness(self, datas, prefix, fields_def, position=0):
 
548
                        line = datas[position]
 
549
                        row = {}
 
550
                        todo = []
 
551
                        warning = ''
 
552
                        #
 
553
                        # Import normal fields
 
554
                        #
 
555
                        for i in range(len(fields)):
 
556
                                if i>=len(line):
 
557
                                        raise 'Please check that all your lines have %d cols.' % (len(fields),)
 
558
                                field = fields[i]
 
559
                                if (len(field)==len(prefix)+1) and (prefix==field[0:len(prefix)]):
 
560
                                        if fields_def[field[len(prefix)]]['type']=='integer':
 
561
                                                res =line[i] and int(line[i])
 
562
                                        elif fields_def[field[len(prefix)]]['type']=='float':
 
563
                                                res =line[i] and float(line[i])
 
564
                                        elif fields_def[field[len(prefix)]]['type']=='selection':
 
565
                                                res = False
 
566
                                                for key,val in fields_def[field[len(prefix)]]['selection']:
 
567
                                                        if val==line[i]:
 
568
                                                                res = key
 
569
                                                if line[i] and not res:
 
570
                                                        print 'WARNING, key',line[i],'not found in selection field', field[len(prefix)]
 
571
                                        elif fields_def[field[len(prefix)]]['type']=='many2one':
 
572
                                                res = False
 
573
                                                if line[i]:
 
574
                                                        relation = fields_def[field[len(prefix)]]['relation']
 
575
                                                        res2 = self.pool.get(relation).name_search(cr, uid, line[i], [],operator='=')
 
576
                                                        res = (res2 and res2[0][0]) or False
 
577
                                                        if not res and line[i]:
 
578
                                                                warning += ('Relation not found: '+line[i]+' on '+relation + ' !\n')
 
579
                                                        if not res:
 
580
                                                                print 'Relation not found: '+line[i]+' on '+relation + ' !\n'
 
581
                                        elif fields_def[field[len(prefix)]]['type']=='many2many':
 
582
                                                res = []
 
583
                                                if line[i]:
 
584
                                                        relation = fields_def[field[len(prefix)]]['relation']
 
585
                                                        for word in line[i].split(','):
 
586
                                                                res2 = self.pool.get(relation).name_search(cr, uid, word, [],operator='=')
 
587
                                                                res3 = (res2 and res2[0][0]) or False
 
588
                                                                if res3:
 
589
                                                                        res.append(res3)
 
590
                                                        if len(res):
 
591
                                                                res= [(6,0,res)]
 
592
                                        else:
 
593
                                                res = line[i] or False
 
594
                                        row[field[len(prefix)]] = res
 
595
                                elif (prefix==field[0:len(prefix)]):
 
596
                                        if field[0] not in todo:
 
597
                                                todo.append(field[len(prefix)])
 
598
 
 
599
 
 
600
                        #
 
601
                        # Import one2many fields
 
602
                        #
 
603
                        nbrmax = 0
 
604
                        for field in todo:
 
605
                                newfd = self.pool.get(fields_def[field]['relation']).fields_get(cr, uid, context=context)
 
606
                                (newrow,max2,w2) = process_liness(self, datas, prefix+[field], newfd, position)
 
607
                                nbrmax = max(nbrmax, max2)
 
608
                                warning = warning+w2
 
609
                                reduce(lambda x,y: x and y, newrow)
 
610
                                row[field] = (reduce(lambda x,y: x or y, newrow.values()) and [(0,0,newrow)]) or []
 
611
                                i = max2
 
612
                                while (position+i)<len(datas):
 
613
                                        ok = True
 
614
                                        for j in range(len(fields)):
 
615
                                                field2 = fields[j]
 
616
                                                if (len(field2)<=(len(prefix)+1)) and datas[position+i][j]:
 
617
                                                        ok = False
 
618
                                        if not ok:
 
619
                                                break
 
620
 
 
621
                                        (newrow,max2,w2) = process_liness(self, datas, prefix+[field], newfd, position+i)
 
622
                                        warning = warning+w2
 
623
                                        if reduce(lambda x,y: x or y, newrow.values()):
 
624
                                                row[field].append((0,0,newrow))
 
625
                                        i+=max2
 
626
                                        nbrmax = max(nbrmax, i)
 
627
 
 
628
                        if len(prefix)==0:
 
629
                                for i in range(max(nbrmax,1)):
 
630
                                        #if datas:
 
631
                                        datas.pop(0)
 
632
                        result = [row, nbrmax+1, warning]
 
633
                        return result
 
634
 
 
635
                fields_def = self.fields_get(cr, uid, context=context)
 
636
                done = 0
 
637
                while len(datas):
 
638
                        (res,other,warning) = process_liness(self, datas, [], fields_def)
 
639
                        try:
 
640
                                self.create(cr, uid, res, context)
 
641
                        except Exception, e:
 
642
                                cr.rollback()
 
643
                                return (-1, res, e[0], warning)
 
644
                        done += 1
 
645
                #
 
646
                # TODO: Send a request with the result and multi-thread !
 
647
                #
 
648
                return (done, 0, 0, 0)
 
649
 
 
650
        def read(self, cr, user, ids, fields=None, context={}, load='_classic_read'):
 
651
                self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
 
652
                if not fields:
 
653
                        fields = self._columns.keys() + self._inherit_fields.keys()
 
654
                result =  self._read_flat(cr, user, ids, fields, context, load)
 
655
                for r in result:
 
656
                        for key,v in r.items():
 
657
                                if v == None:
 
658
                                        r[key]=False
 
659
                return result
 
660
 
 
661
        def _read_flat(self, cr, user, ids, fields, context={}, load='_classic_read'):
 
662
                if not ids:
 
663
                        return []
 
664
 
 
665
                if fields==None:
 
666
                        fields = self._columns.keys()
 
667
 
 
668
                # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
 
669
                fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
 
670
 
 
671
                if len(fields_pre):
 
672
                        cr.execute('select %s from %s where id = any(array[%s]) order by %s' % (','.join(fields_pre + ['id']), self._table, ','.join([str(x) for x in ids]), self._order))
 
673
                
 
674
                        if not cr.rowcount:
 
675
                                return []
 
676
                        res = cr.dictfetchall()
 
677
                else:
 
678
                        res = map(lambda x: {'id':x}, ids)
 
679
 
 
680
                if context.get('lang', False):
 
681
                        for f in fields_pre:
 
682
                                if self._columns[f].translate:
 
683
                                        ids = map(lambda x: x['id'], res)
 
684
                                        res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context['lang'], ids)
 
685
                                        for r in res:
 
686
                                                r[f] = res_trans.get(r['id'], r[f])
 
687
 
 
688
                for table in self._inherits:
 
689
                        col = self._inherits[table]
 
690
                        cols = intersect(self._inherit_fields.keys(), fields)
 
691
                        res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
 
692
 
 
693
                        res3 = {}
 
694
                        for r in res2:
 
695
                                res3[r['id']] = r
 
696
                                del r['id']
 
697
 
 
698
                        for record in res:
 
699
                                record.update(res3[record[col]])
 
700
                                if col not in fields:
 
701
                                        del record[col]
 
702
 
 
703
                # all fields which need to be post-processed by a simple function (symbol_get)
 
704
                fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
 
705
                if fields_post:
 
706
                        # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
 
707
                        # to get the _symbol_get in each occurence
 
708
                        for r in res:
 
709
                                for f in fields_post:
 
710
                                        r[f] = self.columns[f]._symbol_get(r[f])
 
711
                ids = map(lambda x: x['id'], res)
 
712
 
 
713
                # all non inherited fields for which the attribute whose name is in load is False
 
714
                fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
 
715
                for f in fields_post:
 
716
                        # get the value of that field for all records/ids
 
717
                        res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
 
718
                        for record in res:
 
719
                                record[f] = res2[record['id']]
 
720
 
 
721
                return res
 
722
 
 
723
        def _validate(self, cr, uid, ids):
 
724
                field_error = []
 
725
                field_err_str = []
 
726
                for field in self._constraints:
 
727
                        if not field[0](self, cr, uid, ids):
 
728
                                if len(field)>1:
 
729
                                        field_error+=field[2]
 
730
                                field_err_str.append(field[1])
 
731
                if len(field_err_str):
 
732
                        cr.rollback()
 
733
                        raise except_orm('ValidateError', ('\n'.join(field_err_str), ','.join(field_error)))
 
734
 
 
735
        def default_get(self, cr, uid, fields, context={}):
 
736
                value = {}
 
737
                # get the default values for the inherited fields
 
738
                for t in self._inherits.keys():
 
739
                        value.update(self.pool.get(t).default_get(cr, uid, fields, context))
 
740
 
 
741
                # get the default values defined in the object
 
742
                for f in fields:
 
743
                        if f in self._defaults:
 
744
                                value[f] = self._defaults[f](self, cr, uid, context)
 
745
 
 
746
                # get the default values set by the user and override the default
 
747
                # values defined in the object
 
748
                res = ir.ir_get(cr, uid, 'default', False, [self._name])
 
749
                for id, field, field_value in res:
 
750
                        if field in fields:
 
751
                                value[field] = field_value 
 
752
                return value
 
753
 
 
754
        def perm_read(self, cr, user, ids, context={}):
 
755
                fields = ', p.level, p.uid, p.gid'
 
756
                if self._log_access:
 
757
                        fields +=', u.create_uid, u.create_date, u.write_uid, u.write_date'
 
758
                ids_str = string.join(map(lambda x:str(x), ids),',')
 
759
                cr.execute('select u.id'+fields+' from perm p right join '+self._table+' u on u.perm_id=p.id where u.id=any(array['+ids_str+'])')
 
760
                res = cr.dictfetchall()
 
761
#               for record in res:
 
762
#                       for f in ('ox','ux','gx','uid','gid'):
 
763
#                               if record[f]==None:
 
764
#                                       record[f]=False
 
765
                for r in res:
 
766
                        for key in r:
 
767
                                r[key] = r[key] or False
 
768
                                if key in ('write_uid','create_uid','uid'):
 
769
                                        if r[key]:
 
770
                                                r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
 
771
                return res
 
772
 
 
773
        def unlink(self, cr, uid, ids, context={}):
 
774
#CHECKME: wouldn't it be better to check for the write access instead of create?
 
775
#or alternatively, create a new 'delete' permission
 
776
                self.pool.get('ir.model.access').check(cr, uid, self._name, 'create')
 
777
                if not len(ids):
 
778
                        return True
 
779
                wf_service = netsvc.LocalService("workflow")
 
780
                for id in ids:
 
781
                        wf_service.trg_delete(uid, self._name, id, cr)
 
782
                str_d = string.join(('%d',)*len(ids),',')
 
783
 
 
784
                cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
 
785
                res = cr.dictfetchall()
 
786
                #for key in self._inherits:
 
787
                #       ids2 = [x[self._inherits[key]] for x in res]
 
788
                #       self.pool.get(key).unlink(cr, uid, ids2)
 
789
                cr.execute('delete from inherit where (obj_type=%s and obj_id in ('+str_d+')) or (inst_type=%s and inst_id in ('+str_d+'))', (self._name,)+tuple(ids)+(self._name,)+tuple(ids))
 
790
                cr.execute('delete from '+self._table+' where id in ('+str_d+')', ids)
 
791
                return True
 
792
 
 
793
        #
 
794
        # TODO: Validate
 
795
        #
 
796
        def write(self, cr, user, ids, vals, context={}):
 
797
                self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
 
798
                #for v in self._inherits.values():
 
799
                #       assert v not in vals, (v, vals)
 
800
                if not ids:
 
801
                        return
 
802
                ids_str = string.join(map(lambda x:str(x), ids),',')
 
803
                upd0=[]
 
804
                upd1=[]
 
805
                upd_todo=[]
 
806
                updend=[]
 
807
                direct = []
 
808
                totranslate = context.get('lang', False) and (context['lang'] != 'en')
 
809
                for field in vals:
 
810
                        if field in self._columns:
 
811
                                if self._columns[field]._classic_write:
 
812
                                        if (not totranslate) or not self._columns[field].translate:
 
813
                                                upd0.append(field+'='+self._columns[field]._symbol_set[0])
 
814
                                                upd1.append(self._columns[field]._symbol_set[1](vals[field]))
 
815
                                        direct.append(field)
 
816
                                else:
 
817
                                        upd_todo.append(field)
 
818
                        else:
 
819
                                updend.append(field)
 
820
                if self._log_access:
 
821
                        upd0.append('write_uid=%d')
 
822
                        upd0.append('write_date=current_timestamp(0)')
 
823
                        upd1.append(user)
 
824
 
 
825
                if len(upd0):
 
826
                        cr.execute('update '+self._table+' set '+string.join(upd0,',')+' where id in ('+ids_str+')', upd1)
 
827
 
 
828
                        if totranslate:
 
829
                                for f in direct:
 
830
                                        if self._columns[f].translate:
 
831
                                                self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model',  context['lang'], ids,vals[f])
 
832
 
 
833
                # call the 'set' method of fields which are not classic_write
 
834
                upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
 
835
                for field in upd_todo:
 
836
                        for id in ids:
 
837
                                self._columns[field].set(cr, self, id, field, vals[field], user)
 
838
 
 
839
                for table in self._inherits:
 
840
                        col = self._inherits[table]
 
841
                        cr.execute('select distinct '+col+' from '+self._table+' where id in ('+ids_str+')', upd1)
 
842
                        nids = [x[0] for x in cr.fetchall()]
 
843
 
 
844
                        v = {}
 
845
                        for val in updend:
 
846
                                if self._inherit_fields[val][0]==table:
 
847
                                        v[val]=vals[val]
 
848
                        self.pool.get(table).write(cr, user, nids, v, context)
 
849
 
 
850
                self._validate(cr, user, ids)
 
851
 
 
852
                wf_service = netsvc.LocalService("workflow")
 
853
                for id in ids:
 
854
                        wf_service.trg_write(user, self._name, id, cr)
 
855
                return True
 
856
 
 
857
        #
 
858
        # TODO: Should set perm to user.xxx
 
859
        #
 
860
        def create(self, cr, user, vals, context={}):
 
861
                """ create(cr, user, vals, context) -> int
 
862
                cr = database cursor
 
863
                user = user id
 
864
                vals = dictionary of the form {'field_name':field_value, ...}
 
865
                """
 
866
                self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
 
867
 
 
868
                default = []
 
869
 
 
870
                avoid_table = []
 
871
                for (t,c) in self._inherits.items():
 
872
                        if c in vals:
 
873
                                avoid_table.append(t)
 
874
                for f in self._columns.keys(): # + self._inherit_fields.keys():
 
875
                        if not f in vals:
 
876
                                default.append(f)
 
877
                for f in self._inherit_fields.keys():
 
878
                        if (not f in vals) and (not self._inherit_fields[f][0] in avoid_table):
 
879
                                default.append(f)
 
880
 
 
881
                if len(default):
 
882
                        vals.update(self.default_get(cr, user, default, context))
 
883
 
 
884
                tocreate = {}
 
885
                for v in self._inherits:
 
886
                        if self._inherits[v] not in vals:
 
887
                                tocreate[v] = {}
 
888
 
 
889
                #cr.execute('select perm_id from res_users where id=%d', (user,))
 
890
                perm = False #cr.fetchone()[0]
 
891
                (upd0, upd1, upd2) = ('', '', [])
 
892
                upd_todo = []
 
893
 
 
894
                for v in vals.keys():
 
895
                        if v in self._inherit_fields:
 
896
                                (table,col,col_detail) = self._inherit_fields[v]
 
897
                                tocreate[table][v] = vals[v]
 
898
                                del vals[v]
 
899
 
 
900
                cr.execute("select nextval('"+self._sequence+"')")
 
901
                id_new = cr.fetchone()[0]
 
902
                for table in tocreate:
 
903
                        id = self.pool.get(table).create(cr, user, tocreate[table])
 
904
                        upd0 += ','+self._inherits[table]
 
905
                        upd1 += ',%d'
 
906
                        upd2.append(id)
 
907
                        cr.execute('insert into inherit (obj_type,obj_id,inst_type,inst_id) values (%s,%d,%s,%d)', (table,id,self._name,id_new))
 
908
 
 
909
                for field in vals:
 
910
                        if self._columns[field]._classic_write:
 
911
                                upd0=upd0+','+field
 
912
                                upd1=upd1+','+self._columns[field]._symbol_set[0]
 
913
                                upd2.append(self._columns[field]._symbol_set[1](vals[field]))
 
914
                        else:
 
915
                                upd_todo.append(field)
 
916
                if self._log_access:
 
917
                        upd0 += ',create_uid,create_date'
 
918
                        upd1 += ',%d,current_timestamp(0)'
 
919
                        upd2.append(user)
 
920
                cr.execute('insert into '+self._table+' (id,perm_id'+upd0+") values ("+str(id_new)+',NULL'+upd1+')', tuple(upd2))
 
921
                upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
 
922
                for field in upd_todo:
 
923
                        self._columns[field].set(cr, self, id_new, field, vals[field], user, context)
 
924
 
 
925
                self._validate(cr, user, [id_new])
 
926
 
 
927
                wf_service = netsvc.LocalService("workflow")
 
928
                wf_service.trg_create(user, self._name, id_new, cr)
 
929
                return id_new
 
930
 
 
931
        #
 
932
        # TODO: Validate
 
933
        #
 
934
        def perm_write(self, cr, user, ids, fields, context={}):
 
935
                query = []
 
936
                vals = []
 
937
                keys = fields.keys()
 
938
                for x in keys:
 
939
                        query.append(x+'=%d')
 
940
                        vals.append(fields[x])
 
941
                cr.execute('select id from perm where ' + ' and '.join(query) + ' limit 1', vals)
 
942
                res = cr.fetchone()
 
943
                if res:
 
944
                        id = res[0]
 
945
                else:
 
946
                        cr.execute("select nextval('perm_id_seq')")
 
947
                        id = cr.fetchone()[0]
 
948
                        cr.execute('insert into perm (id,' + ','.join(keys) + ') values (' + str(id) + ',' + ('%d,'*(len(keys)-1)+'%d')+')', vals)
 
949
                ids_str = ','.join(map(str, ids))
 
950
                cr.execute('update '+self._table+' set perm_id=%d where id in ('+ids_str+')', (id,))
 
951
                return True
 
952
 
 
953
        # returns the definition of each field in the object
 
954
        # the optional fields parameter can limit the result to some fields
 
955
        def fields_get(self, cr, user, fields=None, context={}):
 
956
                res = {}
 
957
                for parent in self._inherits:
 
958
                        res.update(self.pool.get(parent).fields_get(cr, user, fields, context))
 
959
                for f in self._columns.keys():
 
960
                        res[f] = {'type': self._columns[f]._type}
 
961
                        for arg in ('string','readonly','states','size','required','change_default','translate', 'help', 'select'):
 
962
                                if getattr(self._columns[f], arg):
 
963
                                        res[f][arg] = getattr(self._columns[f], arg)
 
964
                        for arg in ('digits', 'invisible'):
 
965
                                if hasattr(self._columns[f], arg) and getattr(self._columns[f], arg):
 
966
                                        res[f][arg] = getattr(self._columns[f], arg)
 
967
 
 
968
                        # translate the field label
 
969
                        if context.get('lang', False):
 
970
                                res_trans = self.pool.get('ir.translation')._get_source(cr, user, self._name+','+f, 'field', context['lang'])
 
971
                                if res_trans:
 
972
                                        res[f]['string'] = res_trans
 
973
 
 
974
                        if hasattr(self._columns[f], 'selection'):
 
975
                                if isinstance(self._columns[f].selection, (tuple, list)):
 
976
                                        sel = self._columns[f].selection
 
977
                                        
 
978
                                        # translate each selection option
 
979
                                        if context.get('lang', False):
 
980
                                                sel2 = []
 
981
                                                for (key,val) in sel:
 
982
                                                        val2 = self.pool.get('ir.translation')._get_source(cr, user, self._name+','+f, 'selection', context['lang'], val)
 
983
                                                        sel2.append((key, val2 or val))
 
984
                                                sel = sel2
 
985
                                        res[f]['selection'] = sel
 
986
                                else:
 
987
                                        # call the 'dynamic selection' function
 
988
                                        res[f]['selection'] = self._columns[f].selection(self, cr, user, context)
 
989
                        if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
 
990
                                res[f]['relation'] = self._columns[f]._obj
 
991
                                res[f]['domain'] = self._columns[f]._domain
 
992
                                res[f]['context'] = self._columns[f]._context
 
993
 
 
994
                if fields:
 
995
                        # filter out fields which aren't in the fields list
 
996
                        for r in res.keys():
 
997
                                if r not in fields:
 
998
                                        del res[r]
 
999
                return res
 
1000
 
 
1001
        #
 
1002
        # Overload this method if you need a window title which depends on the context
 
1003
        #
 
1004
        def view_header_get(self, cr, user, view_id=None, view_type='form', context={}):
 
1005
                return False
 
1006
 
 
1007
        def __view_look_dom(self, cr, user, node, context={}):
 
1008
                result = False
 
1009
                fields = {}
 
1010
                childs = True
 
1011
                if node.nodeType==node.ELEMENT_NODE and node.localName=='field':
 
1012
                        if node.hasAttribute('name'):
 
1013
                                attrs = {}
 
1014
                                try:
 
1015
                                        if node.getAttribute('name') in self._columns:
 
1016
                                                relation = self._columns[node.getAttribute('name')]._obj
 
1017
                                        else:
 
1018
                                                relation = self._inherit_fields[node.getAttribute('name')][2]._obj
 
1019
                                except:
 
1020
                                        relation = False
 
1021
                                if relation:
 
1022
                                        childs = False
 
1023
                                        views = {}
 
1024
                                        for f in node.childNodes:
 
1025
                                                if f.nodeType==f.ELEMENT_NODE and f.localName in ('form','tree'):
 
1026
                                                        node.removeChild(f)
 
1027
                                                        xarch,xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, context)
 
1028
                                                        views[str(f.localName)] = {
 
1029
                                                                'arch': xarch,
 
1030
                                                                'fields': xfields
 
1031
                                                        }
 
1032
                                        attrs = {'views': views}
 
1033
                                fields[node.getAttribute('name')] = attrs
 
1034
 
 
1035
                elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form','tree'):
 
1036
                        result = self.view_header_get(cr, user, False, node.localName, context)
 
1037
                        if result:
 
1038
                                node.setAttribute('string', result)
 
1039
 
 
1040
                if node.nodeType == node.ELEMENT_NODE:
 
1041
                        # translate view
 
1042
                        if ('lang' in context) and node.hasAttribute('string') and node.getAttribute('string') and not result:
 
1043
                                trans = tools.translate(cr, user, self._name, 'view', context['lang'], node.getAttribute('string').encode('utf8'))
 
1044
                                if trans:
 
1045
                                        node.setAttribute('string', trans.decode('utf8'))
 
1046
                        #
 
1047
                        # Add view for properties !
 
1048
                        #
 
1049
                        if node.localName=='properties':
 
1050
                                parent = node.parentNode
 
1051
 
 
1052
                                doc = node.ownerDocument
 
1053
 
 
1054
                                models = map(lambda x: "'"+x+"'", [self._name] + self._inherits.keys())
 
1055
                                cr.execute('select id,name,group_name from ir_model_fields where model in ('+','.join(models)+') and view_load order by group_name, id')
 
1056
                                oldgroup = None
 
1057
                                res = cr.fetchall()
 
1058
                                for id, fname, gname in res:
 
1059
                                        if oldgroup != gname:
 
1060
                                                child = doc.createElement('separator')
 
1061
                                                child.setAttribute('string', gname)
 
1062
                                                child.setAttribute('colspan', "4")
 
1063
                                                oldgroup = gname
 
1064
                                                parent.insertBefore(child, node)
 
1065
 
 
1066
                                        child = doc.createElement('field')
 
1067
                                        child.setAttribute('name', fname)
 
1068
                                        parent.insertBefore(child, node)
 
1069
                                parent.removeChild(node)
 
1070
 
 
1071
                if childs:
 
1072
                        for f in node.childNodes:
 
1073
                                fields.update(self.__view_look_dom(cr, user, f,context))
 
1074
                return fields
 
1075
 
 
1076
        def __view_look_dom_arch(self, cr, user, node, context={}):
 
1077
                fields_def = self.__view_look_dom(cr, user, node, context=context)
 
1078
                arch = node.toxml()
 
1079
                fields = self.fields_get(cr, user, fields_def.keys(), context)
 
1080
                for field in fields_def:
 
1081
                        fields[field].update(fields_def[field])
 
1082
                return arch,fields
 
1083
 
 
1084
 
 
1085
        #
 
1086
        # if view_id, view_type is not required
 
1087
        #
 
1088
        def fields_view_get(self, cr, user, view_id=None, view_type='form', context={}, toolbar=False):
 
1089
                def _inherit_apply(src, inherit):
 
1090
                        def _find(node, node2):
 
1091
                                if node.nodeType==node.ELEMENT_NODE and node.localName==node2.localName:
 
1092
                                        res = True
 
1093
                                        for attr in node2.attributes.keys():
 
1094
                                                if attr=='position':
 
1095
                                                        continue
 
1096
                                                if node.hasAttribute(attr):
 
1097
                                                        if node.getAttribute(attr)==node2.getAttribute(attr):
 
1098
                                                                continue
 
1099
                                                res = False
 
1100
                                        if res:
 
1101
                                                return node
 
1102
                                for child in node.childNodes:
 
1103
                                        res = _find(child, node2)
 
1104
                                        if res: return res
 
1105
                                return None
 
1106
 
 
1107
                        doc_src = dom.minidom.parseString(src)
 
1108
                        doc_dest = dom.minidom.parseString(inherit)
 
1109
                        for node2 in doc_dest.childNodes:
 
1110
                                if not node2.nodeType==node2.ELEMENT_NODE:
 
1111
                                        continue
 
1112
                                node = _find(doc_src, node2)
 
1113
                                if node:
 
1114
                                        pos = 'inside'
 
1115
                                        if node2.hasAttribute('position'):
 
1116
                                                pos = node2.getAttribute('position')
 
1117
                                        if pos=='replace':
 
1118
                                                parent = node.parentNode
 
1119
                                                for child in node2.childNodes:
 
1120
                                                        if child.nodeType==child.ELEMENT_NODE:
 
1121
                                                                parent.insertBefore(child, node)
 
1122
                                                parent.removeChild(node)
 
1123
                                        else:
 
1124
                                                for child in node2.childNodes:
 
1125
                                                        if child.nodeType==child.ELEMENT_NODE:
 
1126
                                                                if pos=='inside':
 
1127
                                                                        node.appendChild(child)
 
1128
                                                                elif pos=='after':
 
1129
                                                                        sib = node.nextSibling
 
1130
                                                                        if sib:
 
1131
                                                                                node.parentNode.insertBefore(child, sib)
 
1132
                                                                        else:
 
1133
                                                                                node.parentNode.appendChild(child)
 
1134
                                                                elif pos=='before':
 
1135
                                                                        node.parentNode.insertBefore(child, node)
 
1136
                                                                else:
 
1137
                                                                        raise AttributeError, 'Unknown position in inherited view %s !' % pos
 
1138
                                else:
 
1139
                                        attrs = ''.join([
 
1140
                                                ' %s="%s"' % (attr, node2.getAttribute(attr)) 
 
1141
                                                for attr in node2.attributes.keys() 
 
1142
                                                if attr != 'position'
 
1143
                                        ])
 
1144
                                        tag = "<%s%s>" % (node2.localName, attrs)
 
1145
                                        raise AttributeError, "Couldn't find tag '%s' in parent view !" % tag
 
1146
                        return doc_src.toxml()
 
1147
 
 
1148
                result = {'type':view_type, 'model':self._name}
 
1149
 
 
1150
                ok = True
 
1151
                model = True
 
1152
                while ok:
 
1153
                        if view_id:
 
1154
                                where = (model and (" and model='%s'" % (self._name,))) or ''
 
1155
                                cr.execute('select arch,name,field_parent,id,type,inherit_id from ir_ui_view where id=%d'+where, (view_id,))
 
1156
                        else:
 
1157
                                cr.execute('select arch,name,field_parent,id,type,inherit_id from ir_ui_view where model=%s and type=%s order by priority', (self._name,view_type))
 
1158
                        sql_res = cr.fetchone()
 
1159
                        if not sql_res:
 
1160
                                break
 
1161
                        ok = sql_res[5]
 
1162
                        view_id = ok or sql_res[3]
 
1163
                        model = False
 
1164
 
 
1165
                # if a view was found
 
1166
                if sql_res:
 
1167
                        result['type'] = sql_res[4]
 
1168
                        result['view_id'] = sql_res[3]
 
1169
                        result['arch'] = sql_res[0]
 
1170
 
 
1171
                        # get all views which inherit from (ie modify) this view
 
1172
                        cr.execute('select arch from ir_ui_view where inherit_id=%d and model=%s order by priority', (sql_res[3], self._name))
 
1173
                        sql_inherit = cr.fetchall()
 
1174
                        for (inherit,) in sql_inherit:
 
1175
                                result['arch'] = _inherit_apply(result['arch'], inherit)
 
1176
 
 
1177
                        result['name'] = sql_res[1]
 
1178
                        result['field_parent'] = sql_res[2] or False
 
1179
                else:
 
1180
                        # otherwise, build some kind of default view
 
1181
                        if view_type == 'form':
 
1182
                                res = self.fields_get(cr, user, context=context)
 
1183
                                xml = '''<?xml version="1.0"?>\n<form string="%s">\n''' % (self._description,)
 
1184
                                for x in res:
 
1185
                                        if res[x]['type'] not in ('one2many', 'many2many'):
 
1186
                                                xml += '\t<field name="%s"/>\n' % (x,)
 
1187
                                                if res[x]['type'] == 'text':
 
1188
                                                        xml += "<newline/>"
 
1189
                                xml += "</form>"
 
1190
                        elif view_type == 'tree':
 
1191
                                xml = '''<?xml version="1.0"?>\n<tree string="%s">\n\t<field name="%s"/>\n</tree>''' % (self._description,self._rec_name)
 
1192
                        else:
 
1193
                                xml = ''
 
1194
                        result['arch'] = xml
 
1195
                        result['name'] = 'default'
 
1196
                        result['field_parent'] = False
 
1197
                        result['view_id'] = 0
 
1198
 
 
1199
                doc = dom.minidom.parseString(result['arch'])
 
1200
                xarch, xfields = self.__view_look_dom_arch(cr, user, doc, context=context)
 
1201
                result['arch'] = xarch
 
1202
                result['fields'] = xfields
 
1203
                if toolbar:
 
1204
                        resprint = self.pool.get('ir.values').get(cr, user, 'action', 'client_print_multi', [(self._name, False)], False, context)
 
1205
                        resaction = self.pool.get('ir.values').get(cr, user, 'action', 'client_action_multi', [(self._name, False)], False, context)
 
1206
                        resprint = map(lambda x:x[2], resprint)
 
1207
                        resaction = map(lambda x:x[2], resaction)
 
1208
                        resaction = filter(lambda x: not x.get('multi',False), resaction)
 
1209
                        for x in resprint+resaction:
 
1210
                                x['string'] = x['name']
 
1211
                        ids = self.pool.get('ir.model.fields').search(cr, user, [('relation','=',self._name),('relate','=',1)])
 
1212
                        resrelate = self.pool.get('ir.model.fields').read(cr, user, ids, ['name','model_id'], context)
 
1213
                        models = self.pool.get('ir.model').read(cr, user, map(lambda x: x['model_id'][0], resrelate), ['name'], context)
 
1214
                        dmodels = {}
 
1215
                        for m in models:
 
1216
                                dmodels[m['id']] = m['name']
 
1217
                        for x in resrelate:
 
1218
                                x['string'] = dmodels[x['model_id'][0]]
 
1219
                        
 
1220
                        result['toolbar'] = {
 
1221
                                'print': resprint,
 
1222
                                'action': resaction,
 
1223
                                'relate': resrelate
 
1224
                        }
 
1225
 
 
1226
                return result
 
1227
 
 
1228
        # TODO: ameliorer avec NULL
 
1229
        def _where_calc(self, args):
 
1230
                qu1, qu2 = [], []
 
1231
                for x in args:
 
1232
                        table=self
 
1233
                        if len(x) > 3:
 
1234
                                table=x[3]
 
1235
                        if x[1] != 'in':
 
1236
#FIXME: this replace all (..., '=', False) values with 'is null' and this is 
 
1237
# not what we want for real boolean fields. The problem is, we can't change it
 
1238
# easily because we use False everywhere instead of None
 
1239
# NOTE FAB: we can't use None because it is not accepted by XML-RPC, that's why
 
1240
# boolean (0-1), None -> False
 
1241
# Ged> boolean fields are not always = 0 or 1
 
1242
                                if (x[2] is False) and (x[1]=='='):
 
1243
                                        qu1.append(x[0]+' is null')
 
1244
                                elif (x[2] is False) and (x[1]=='<>' or x[1]=='!='):
 
1245
                                        qu1.append(x[0]+' is not null')
 
1246
                                else:
 
1247
                                        if x[0]=='id':
 
1248
                                                if x[1]=='join':
 
1249
                                                        qu1.append('(%s.%s = %s)' % (table._table, x[0], x[2]))
 
1250
                                                else:
 
1251
                                                        qu1.append('(%s.%s %s %%s)' % (table._table, x[0], x[1]))
 
1252
                                                        qu2.append(x[2])
 
1253
                                        else:
 
1254
                                                if x[1] in ('like', 'ilike'):
 
1255
                                                        if isinstance(x[2], str):
 
1256
                                                                str_utf8 = x[2]
 
1257
                                                        elif isinstance(x[2], unicode):
 
1258
                                                                str_utf8 = x[2].encode('utf-8')
 
1259
                                                        else:
 
1260
                                                                str_utf8 = str(x[2])
 
1261
                                                        qu2.append('%%%s%%' % str_utf8)
 
1262
                                                else:
 
1263
                                                        qu2.append(table._columns[x[0]]._symbol_set[1](x[2]))
 
1264
                                                if x[1]=='=like':
 
1265
                                                        x1 = 'like'
 
1266
                                                else:
 
1267
                                                        x1 = x[1]
 
1268
                                                qu1.append('(%s.%s %s %s)' % (table._table, x[0], x1, table._columns[x[0]]._symbol_set[0]))
 
1269
                        elif x[1]=='in':
 
1270
                                if len(x[2])>0:
 
1271
                                        todel = False
 
1272
                                        for xitem in range(len(x[2])):
 
1273
                                                if x[2][xitem]==False and isinstance(x[2][xitem],bool):
 
1274
                                                        todel = xitem
 
1275
                                                        del x[2][xitem]
 
1276
                                        if x[0]=='id':
 
1277
                                                qu1.append('(id=any(array[%s]))' % (','.join(['%d'] * len(x[2])),))
 
1278
                                        else:
 
1279
                                                qu1.append('(%s.%s in (%s))' % (table._table, x[0], ','.join([table._columns[x[0]]._symbol_set[0]]*len(x[2]))))
 
1280
                                        if todel:
 
1281
                                                qu1[-1] = '('+qu1[-1]+' or '+x[0]+' is null)'
 
1282
                                        qu2+=x[2]
 
1283
                                else:
 
1284
                                        qu1.append(' (1=0)')
 
1285
                return (qu1,qu2)
 
1286
 
 
1287
        def search(self, cr, user, args, offset=0, limit=None, order=None):
 
1288
                # if the object has a field named 'active', filter out all inactive 
 
1289
                # records unless they were explicitely asked for
 
1290
                if 'active' in self._columns:
 
1291
                        ok = False
 
1292
                        for a in args:
 
1293
                                if a[0]=='active':
 
1294
                                        ok = True
 
1295
                        if not ok:
 
1296
                                args.append(('active', '=', 1))
 
1297
 
 
1298
                # if the object has a field named 'company_id', filter out all
 
1299
                # records which do not concern the current company (the company
 
1300
                # of the current user) or its "childs"
 
1301
                if 'company_id' in self._columns:
 
1302
                        compids = self.pool.get('res.company')._get_child_ids(cr, user, user)
 
1303
                        if compids:
 
1304
                                compids.append(False)
 
1305
                                args.append(('company_id','in',compids))
 
1306
 
 
1307
                i = 0
 
1308
                tables=[self._table]
 
1309
                joins=[]
 
1310
                while i<len(args):
 
1311
                        table=self
 
1312
                        if args[i][0] in self._inherit_fields:
 
1313
                                table=self.pool.get(self._inherit_fields[args[i][0]][0])
 
1314
                                if (table._table not in tables):
 
1315
                                        tables.append(table._table)
 
1316
                                        joins.append(('id', 'join', '%s.%s' % (self._table, self._inherits[table._name]), table))
 
1317
                        field = table._columns.get(args[i][0],False)
 
1318
                        if not field:
 
1319
                                i+=1
 
1320
                                continue
 
1321
                        if field._type=='one2many':
 
1322
                                field_obj = self.pool.get(field._obj)
 
1323
 
 
1324
                                # get the ids of the records of the "distant" resource
 
1325
                                ids2 = [x[0] for x in field_obj.name_search(cr, user, args[i][2], [], args[i][1])]
 
1326
                                if not ids2:
 
1327
                                        args[i] = ('id','=','0')
 
1328
                                else:
 
1329
                                        cr.execute('select '+field._fields_id+' from '+field_obj._table+' where id = any(array['+','.join(map(str,ids2))+'])')
 
1330
                                        ids3 = [x[0] for x in cr.fetchall()]
 
1331
 
 
1332
                                        args[i] = ('id', 'in', ids3)
 
1333
                                i+=1
 
1334
 
 
1335
                        elif field._type=='many2many':
 
1336
                                if args[i][1]=='child_of':
 
1337
                                        if isinstance(args[i][2], basestring):
 
1338
                                                ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
 
1339
                                        else:
 
1340
                                                ids2 = args[i][2]
 
1341
                                        def _rec_get(ids):
 
1342
                                                if not len(ids): return []
 
1343
                                                cr.execute('select '+field._id1+' from '+field._rel+' where '+field._id2+' in ('+','.join(map(str,ids))+')')
 
1344
                                                ids = [x[0] for x in cr.fetchall()]
 
1345
                                                return ids + _rec_get(ids)
 
1346
                                        args[i] = ('id','in',ids2+_rec_get(ids2))
 
1347
                                else:
 
1348
                                        if isinstance(args[i][2], basestring):
 
1349
                                                res_ids = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])]
 
1350
                                        else:
 
1351
                                                res_ids = args[i][2]
 
1352
                                        if not len(res_ids):
 
1353
                                                return []
 
1354
                                        cr.execute('select '+field._id1+' from '+field._rel+' where '+field._id2+' in ('+','.join(map(str, res_ids))+')')
 
1355
                                        args[i] = ('id', 'in', map(lambda x: x[0], cr.fetchall()))
 
1356
                                i+=1
 
1357
 
 
1358
                        elif field._type=='many2one':
 
1359
                                if args[i][1]=='child_of':
 
1360
                                        if isinstance(args[i][2], basestring):
 
1361
                                                ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')]
 
1362
                                        else:
 
1363
                                                ids2 = args[i][2]
 
1364
                                        def _rec_get(ids, table):
 
1365
                                                if not len(ids):
 
1366
                                                        return []
 
1367
                                                if 'active' in table._columns:
 
1368
                                                        ids2 = table.search(cr, user, [(args[i][0],'in',ids),('active','=',0)])
 
1369
                                                        ids2 += table.search(cr, user, [(args[i][0],'in',ids),('active','=',1)])
 
1370
                                                else:
 
1371
                                                        ids2 = table.search(cr, user, [(args[i][0], 'in', ids)])
 
1372
                                                return ids + _rec_get(ids2, table)
 
1373
                                        args[i] = ('id','in',ids2+_rec_get(ids2, table), table)
 
1374
                                else:
 
1375
                                        if isinstance(args[i][2], basestring):
 
1376
                                                res_ids = self.pool.get(field._obj).name_search(cr, user, args[i][2], [], args[i][1])
 
1377
                                                args[i] = (args[i][0],'in',map(lambda x: x[0], res_ids), table)
 
1378
                                        else:
 
1379
                                                args[i] += (table,)
 
1380
                                i+=1
 
1381
                        elif field._properties:
 
1382
                                arg = [args.pop(i)]
 
1383
                                j = i
 
1384
                                while j<len(args):
 
1385
                                        if args[j][0]==arg[0][0]:
 
1386
                                                arg.append(args.pop(j))
 
1387
                                        else:
 
1388
                                                j+=1
 
1389
                                if field._fnct_search:
 
1390
                                        args.extend(field.search(cr, user, self, arg[0][0], arg))
 
1391
                        else:
 
1392
                                args[i] += (table,)
 
1393
                                i+=1
 
1394
                args.extend(joins)
 
1395
 
 
1396
                # compute the where, order by, limit and offset clauses
 
1397
                (qu1,qu2) = self._where_calc(args)
 
1398
                if len(qu1):
 
1399
                        qu1 = ' where '+string.join(qu1,' and ')
 
1400
                else:
 
1401
                        qu1 = ''
 
1402
                order_by = order or self._order
 
1403
 
 
1404
                limit_str = limit and ' limit %d' % limit or ''
 
1405
                offset_str = offset and ' offset %d' % offset or ''
 
1406
                
 
1407
                # execute the "main" query to fetch the ids we were searching for
 
1408
                cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
 
1409
                res = cr.fetchall()
 
1410
                return [x[0] for x in res]
 
1411
 
 
1412
        # returns the different values ever entered for one field
 
1413
        # this is used, for example, in the client when the user hits enter on 
 
1414
        # a char field
 
1415
        def distinct_field_get(self, cr, uid, field, value, args=[], offset=0, limit=None):
 
1416
                if field in self._inherit_fields:
 
1417
                        return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid,field,value,args,offset,limit)
 
1418
                else:
 
1419
                        return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
 
1420
 
 
1421
        def name_get(self, cr, user, ids, context={}):
 
1422
                if not len(ids):
 
1423
                        return []
 
1424
                return [(r['id'], r[self._rec_name]) for r in self.read(cr, user, ids, [self._rec_name], context, load='_classic_write')]
 
1425
 
 
1426
        def name_search(self, cr, user, name='', args=[], operator='ilike', context={}, limit=80):
 
1427
                if name:
 
1428
                        args += [(self._rec_name,operator,name)]
 
1429
                ids = self.search(cr, user, args, limit=limit)
 
1430
                res = self.name_get(cr, user, ids, context)
 
1431
                return res
 
1432
 
 
1433
        def copy(self, cr, uid, id, default=None, context={}):
 
1434
                if not default:
 
1435
                        default = {}
 
1436
                if 'state' not in default:
 
1437
                        if 'state' in self._defaults:
 
1438
                                default['state'] = self._defaults['state'](self, cr, uid, context)
 
1439
                data = self.read(cr, uid, [id], context=context)[0]
 
1440
                fields = self.fields_get(cr, uid)
 
1441
                for f in fields:
 
1442
                        ftype = fields[f]['type']
 
1443
                        if f in default:
 
1444
                                data[f] = default[f]
 
1445
                        elif ftype == 'function':
 
1446
                                del data[f]
 
1447
                        elif ftype == 'many2one':
 
1448
                                try:
 
1449
                                        data[f] = data[f] and data[f][0]
 
1450
                                except:
 
1451
                                        pass
 
1452
                        elif ftype in ('one2many', 'one2one'):
 
1453
                                res = []
 
1454
                                rel = self.pool.get(fields[f]['relation'])
 
1455
                                for rel_id in data[f]:
 
1456
                                        # the lines are first duplicated using the wrong (old) 
 
1457
                                        # parent but then are reassigned to the correct one thanks
 
1458
                                        # to the (4, ...)
 
1459
                                        res.append((4, rel.copy(cr, uid, rel_id, context=context)))
 
1460
                                data[f] = res
 
1461
                        elif ftype == 'many2many':
 
1462
                                data[f] = [(6, 0, data[f])]
 
1463
                del data['id']
 
1464
                for v in self._inherits:
 
1465
                        del data[self._inherits[v]]
 
1466
                return self.create(cr, uid, data)
 
1467
 
 
1468
# vim:noexpandtab:ts=4