# -*- coding: latin1 -*- ############################################################################## # # Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. # # $Id: orm.py 1008 2005-07-25 14:03:55Z pinky $ # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## # # Object relationnal mapping to postgresql module # . Hierarchical structure # . Constraints consistency, validations # . Object meta Data depends on its status # . Optimised processing by complex query (multiple actions at once) # . Default fields value # . Permissions optimisation # . Persistant object: DB postgresql # . Datas conversions # . Fields: # - classicals (varchar, integer, boolean, ...) # - relations (one2many, many2one, many2many) # - functions # from xml import dom from xml.parsers import expat import string import sql_db import psycopg import netsvc import re import fields import ir import tools prof = 0 def intersect(la, lb): return filter(lambda x: x in lb, la) class except_orm(Exception): def __init__(self, name, value): self.name = name self.value = value self.args='no error args' class find_fields(object): def _start_el(self,name, attrs): if name == 'field' and attrs.get('name', False): self.datas[str(attrs['name'])] = attrs.get('preload','') def __init__(self): self.datas = {} def parse(self, datas): p = expat.ParserCreate() p.StartElementHandler = self._start_el p.Parse(datas, 1) return self.datas def get_inherit_id(obj_type, obj_id): cr=sql_db.db.cursor() cr.execute('select inst_type, inst_id from inherit where obj_type=%s and obj_id=%d', (obj_type, obj_id)) result = cr.dictfetchone() cr.close() return result # # TODO: trigger pour chaque action # # Readonly python database object browser class browse_null(object): def __init__(self): self.id=False def __getitem__(self, name): return False def __int__(self): return False def __str__(self): return '' def __nonzero__(self): return False # # TODO: execute an object method on browse_record_list # class browse_record_list(list): def __init__(self, lst, context={}): super(browse_record_list, self).__init__(lst) self.context = context # # table : the object (inherited from orm) # context : a dictionnary with an optionnal context # default to : browse_record_list # class browse_record(object): def __init__(self, cr, uid, id, table, cache, context={}, list_class = None): self._list_class = list_class or browse_record_list self._cr = cr self._uid = uid self._id = id self._table = table self._table_name = self._table._name self._context = context cache.setdefault(table._name, {}) self._data = cache[table._name] if not id in self._data: self._data[id] = {'id':id} self._cache = cache def __getitem__(self, name): if name=='id': return self._id if not self._data[self._id].has_key(name): # build the list of fields we will fetch # fetch the definition of the field which was asked for if name in self._table._columns: col = self._table._columns[name] elif name in self._table._inherit_fields: col = self._table._inherit_fields[name][2] else: print "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name) return False # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields if col._classic_write: # gen the list of "local" (ie not inherited) fields which are classic or many2one ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items()) # gen the list of inherited fields inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items()) # complete the field list with the inherited fields which are classic or many2one ffields += filter(lambda x: x[1]._classic_write, inherits) # otherwise we fetch only that field else: ffields = [(name,col)] if isinstance(col, fields.function): ids = [self._id] else: # filter out all ids which were already fetched (and are already in _data) ids = filter(lambda id: not self._data[id].has_key(name), self._data.keys()) # read the data datas = self._table.read(self._cr, self._uid, ids, map(lambda x: x[0], ffields), context=self._context, load="_classic_write") # create browse records for 'remote' objects for data in datas: for n,f in ffields: if isinstance(f, fields.many2one) or isinstance(f, fields.one2one): if data[n]: obj = self._table.pool.get(f._obj) data[n] = browse_record(self._cr, self._uid, data[n], obj, self._cache, context=self._context, list_class=self._list_class) else: data[n]=browse_null() elif isinstance(f, (fields.one2many, fields.many2many)) and len(data[n]): 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) self._data[data['id']].update(data) return self._data[self._id][name] def __getattr__(self, name): # raise an AttributeError exception. return self[name] def __int__(self): return self._id def __unicode__(self): return self.name def __str__(self): return "browse_record(" + self._table_name + ", " + str(self._id) + ")" __repr__ = __str__ # returns a tuple (type returned by postgres when the column was created, type expression to create the column) def get_pg_type(f): 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'} if type_dict.has_key(type(f)): f_type = (type_dict[type(f)], type_dict[type(f)]) elif isinstance(f, fields.float): if f.digits: f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0],f.digits[1])) else: f_type = ('float8', 'DOUBLE PRECISION') elif isinstance(f, (fields.char, fields.reference)): f_type = ('varchar', 'VARCHAR(%d)' % (f.size,)) elif isinstance(f, fields.selection): if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)): f_size = reduce(lambda x,y: max(x,len(y[0])), f.selection, 16) elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int): f_size = -1 else: f_size = (hasattr(f,'size') and f.size) or 16 if f_size == -1: f_type = ('integer', 'INTEGER') else: f_type = ('varchar', 'VARCHAR(%d)' % f_size) else: print "WARNING: type not supported!" f_type = None return f_type class orm(object): _columns = {} _constraints = [] _defaults = {} _log_access = True _table = None _name = None _rec_name = 'name' _order = 'id' _inherits = {} _sequence = None _description = None _protected = ['read','write','create','default_set','default_get','perm_read','perm_write','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy'] def _auto_init(self, cr): create = False if (not hasattr(self,"_auto")) or self._auto: cr.execute("SELECT model FROM ir_model WHERE model='%s'"%self._name) if not cr.dictfetchall(): #reference model in order to have a description of its fonctionnality in custom_report cr.execute("INSERT INTO ir_model (model, name) VALUES (%s, %s)", (self._name, self._description)) cr.commit() cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'" % self._table) if not cr.dictfetchall(): cr.execute("CREATE TABLE %s(id SERIAL NOT NULL, perm_id INTEGER, PRIMARY KEY(id))" % self._table) create = True cr.commit() if self._log_access: logs = {'create_uid':'INTEGER REFERENCES res_users ON DELETE SET NULL', 'create_date':'TIMESTAMP', 'write_uid':'INTEGER REFERENCES res_users ON DELETE SET NULL', 'write_date':'TIMESTAMP'} for k in logs: 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) cr.execute(q) if not cr.dictfetchall(): cr.execute(("ALTER TABLE %s ADD COLUMN %s "+logs[k]) % (self._table,k)) cr.commit() #TODO: we should also iterate on the existing columns on the database (for example to drop the NOT NULL constraint) #DROP NULL constraint #ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL; for k in self._columns: if k in ('id','perm_id', 'write_uid','write_date','create_uid','create_date'): raise 'Can not define a column %s. Reserved keyword !' % (k,) f = self._columns[k] cr.execute("select id from ir_model_fields where model=%s and name=%s", (self._name,k)) res = cr.fetchone() if not res: cr.execute("select id from ir_model where model='%s'"%self._name) res = cr.fetchone() model_id = res[0] cr.execute("INSERT INTO ir_model_fields (model_id, model, name, field_description, ttype, relate,relation) VALUES (%d,%s,%s,%s,%s,%s,%s)", (model_id, self._name, k, re.sub('\'',' ',f.string ), f._type, (f.relate and 'True') or 'False', f._obj or 'NULL')) if isinstance(f,fields.one2many): cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'"%f._obj) if cr.fetchone(): 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) cr.execute(q) res = cr.fetchone()[0] if not res: cr.execute(("ALTER TABLE %s ADD FOREIGN KEY (%s) REFERENCES %s ON DELETE SET NULL") % (self._obj, f._fields_id, f._table)) elif isinstance(f,fields.many2many): cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='%s'"%f._rel) if not cr.dictfetchall(): #FIXME: Remove this try/except try: ref = self.pool.get(f._obj)._table except AttributeError: ref = f._obj.replace('.','_') cr.execute("CREATE TABLE %s (%s INTEGER NOT NULL REFERENCES %s ON DELETE CASCADE, %s INTEGER NOT NULL REFERENCES %s ON DELETE CASCADE)"%(f._rel,f._id1,self._table,f._id2,ref)) cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (f._rel,f._id1,f._rel,f._id1)) cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (f._rel,f._id2,f._rel,f._id2)) cr.commit() else: 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) cr.execute(q) res = cr.dictfetchall() if not res: if not isinstance(f,fields.function): # add the missing field cr.execute("ALTER TABLE %s ADD COLUMN %s %s" % (self._table, k, get_pg_type(f)[1])) # initialize it # maybe we should skip this step if it is the first run of the server and the database is initialized # if self._defaults.has_key(k): # default = self._defaults[k](self, cr, 1, {}) # if not default and isinstance(default, bool) and not isinstance(f, fields.boolean): # cr.execute("UPDATE %s SET %s=NULL" % (self._table, k)) # else: # cr.execute("UPDATE %s SET %s='%s'" % (self._table, k, default)) # and add constraints if needed if isinstance(f,fields.many2one): #FIXME: Remove this try/except try: ref = self.pool.get(f._obj)._table except AttributeError: ref = f._obj.replace('.','_') cr.execute("ALTER TABLE %s ADD FOREIGN KEY (%s) REFERENCES %s ON DELETE %s" % (self._table, k, ref, f.ondelete)) cr.execute("CREATE INDEX %s_%s_index ON %s (%s)" % (self._table, k, self._table, k)) if f.required: cr.commit() try: cr.execute("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k)) except: print '-'*50 print 'WARNING: unable to set column %s of table %s not null !' % (k,self._table) print '\tTry to re-run: tinyerp-server.py --update=module' print '\tIf it doesn\'t work, update records and execute manually:' print "\tALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k) cr.commit() elif len(res)==1: type_dict = {'boolean':'bool', 'integer':'int4', 'char':'varchar', 'text':'text', 'date':'date', 'time':'time', 'datetime':'timestamp', 'binary':'bytea', 'many2one':'int4'} f_pg_def = res[0] f_pg_type = f_pg_def['typname'] f_pg_size = f_pg_def['size'] f_pg_notnull = f_pg_def['attnotnull'] f_obj_type = get_pg_type(f) and get_pg_type(f)[0] if f_obj_type: if f_pg_type != f_obj_type: print "WARNING: column '%s' in table '%s' has changed type (DB = %s, def = %s) !" % (k, self._table, f_pg_type, f._type) if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size: # columns with the name 'type' cannot be changed for an unknown reason?! if k != 'type': if f_pg_size > f.size: 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) #TODO: check si y a des donnees qui vont poser probleme (select char_length(...)) cr.execute("ALTER TABLE %s RENAME COLUMN %s TO temp_change_size" % (self._table,k)) cr.execute("ALTER TABLE %s ADD COLUMN %s VARCHAR(%d)" % (self._table,k,f.size)) cr.execute("UPDATE %s SET %s=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size)) cr.execute("ALTER TABLE %s DROP COLUMN temp_change_size" % (self._table,)) cr.commit() if f.required and f_pg_notnull == 0: cr.execute("SELECT * FROM %s WHERE %s is NULL" % (self._table,k)) if cr.fetchone(): if self._defaults.has_key(k): default = self._defaults[k](self, cr, 1, {}) cr.execute("UPDATE %s SET %s='%s' WHERE %s is NULL" % (self._table, k, default, k)) try: cr.commit() cr.execute("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table,k)) except: print '-'*50 print 'WARNING: unable to set column %s of table %s not null !' % (k,self._table) print '\tTry to re-run: tinyerp-server.py --update=module' print '\tIf it doesn\'t work, update records and execute manually:' print "\tALTER TABLE %s ALTER COLUMN %s SET NOT NULL" % (self._table, k) cr.commit() elif not f.required and f_pg_notnull == 1: cr.execute("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL" % (self._table,k)) cr.commit() else: print "ERROR" if create and hasattr(self,"_sql"): for line in self._sql.split('\n'): try: cr.execute(line) cr.commit() except: pass def __init__(self): if not self._table: self._table=self._name.replace('.','_') if not self._description: self._description = self._name module = str(self.__class__)[6:] module = module[:len(module)-1] module = module.split('.')[0][2:] if hasattr(self,'init'): cr=sql_db.db.cursor() self.init(cr) cr.commit() cr.close() del cr if hasattr(self,'_auto_init') and ((module in tools.config['update']) or (module in tools.config['init'])): cr=sql_db.db.cursor() self._auto_init(cr) cr.commit() cr.close() del cr res = {} for table in self._inherits: res.update(self.pool.get(table)._inherit_fields) for col in self.pool.get(table)._columns.keys(): res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col]) for col in self.pool.get(table)._inherit_fields.keys(): res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2]) self._inherit_fields=res if not self._sequence: self._sequence = self._table+'_id_seq' def browse(self, cr, uid, select, context={}, list_class = None): self._list_class = list_class or browse_record_list cache = {} if isinstance(select,int): return browse_record(cr,uid,select,self,cache, context=context, list_class=self._list_class) elif isinstance(select,list): return self._list_class([browse_record(cr,uid,id,self,cache, context=context, list_class=self._list_class) for id in select], context) else: return [] def read(self, cr, user, ids, fields=None, context={}, load='_classic_read'): self.pool.get('ir.model.access').check(cr, user, self._name, 'read') if not fields: fields = self._columns.keys() + self._inherit_fields.keys() result = self._read_flat(cr, user, ids, fields, context, load) for r in result: for key,v in r.items(): if v == None: r[key]=False return result def _read_flat(self, cr, user, ids, fields, context={}, load='_classic_read'): if ids==[]: return [] if fields==None: fields = self._columns.keys() fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],load), fields) + self._inherits.values() cr.execute('select %s from %s where id in (%s) order by %s' % (','.join(fields_pre + ['id']), self._table, ','.join([str(x) for x in ids]), self._order)) res = cr.dictfetchall() if not len(res): return [] if context.get('lang',False): for f in fields_pre: if self._columns[f].translate: ids = map(lambda x: x['id'], res) res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context['lang'], ids) for r in res: r[f] = res_trans.get(r['id'], r[f]) for table in self._inherits: col = self._inherits[table] cols = intersect(self._inherit_fields.keys(), fields) res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load) res3 = {} for r in res2: res3[r['id']] = r del r['id'] for record in res: record.update(res3[record[col]]) if col not in fields: del record[col] ids = map(lambda x: x['id'], res) # fields_post = all fields which are not classic and not inherited fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields) for f in fields_post: # read that field res2 = self._columns[f].get(cr, self, ids, f, user, context=context) for record in res: record[f] = res2[record['id']] return res def _validate(self, cr, uid, ids): field_error = [] field_err_str = [] for field in self._constraints: if not field[0](self, cr, uid, ids): if len(field)>1: field_error+=field[2] field_err_str.append(field[1]) if len(field_err_str): cr.rollback() raise except_orm('ValidateError', ('\n'.join(field_err_str), ','.join(field_error))) # # Give fields defaults, depending on # uid, reference (id, table), page # def default_get(self, cr, uid, fields, context={}): value = {} for t in self._inherits.keys(): value.update(self.pool.get(t).default_get(cr, uid, fields, context)) res = ir.ir_get(cr, uid, 'default', False, [self._name]) for f in fields: if f in self._defaults: value[f]=self._defaults[f](self, cr, uid, context) for r in res: if r[1] in fields: value[r[1]]=r[2] return value def default_set(self, cr, uid, field, value, for_user=False): ins = {'field_tbl':self._name, 'field_name':field, 'value':value} if for_user!=False: where = ['uid=%d' % uid] ins['uid']=uid else: where = ['uid is null'] if reference: where.append("ref_table='%s'" % reference[1]) where.append("ref_id=%d" % reference[0]) ins['ref_table']=reference[1] ins['ref_id']=reference[0] else: where.append("ref_id is null") if for_window: where.append("page='%s'" % for_window) ins['page']=for_window else: where.append("page is null") cr.execute('delete from ir_default where field_tbl=%s and field_name=%s and '+string.join(where,' and '), (self._name, field)) keys = ins.keys() cr.execute('insert into ir_default ('+','.join(keys)+') values ('+','.join(['%('+x+')s' for x in keys])+')', ins) return True def perm_read(self, cr, user, ids, context={}): fields = ', p.level, p.uid, p.gid' if self._log_access: fields +=', u.create_uid, u.create_date, u.write_uid, u.write_date' ids_str = string.join(map(lambda x:str(x), ids),',') cr.execute('select u.id'+fields+' from perm p right join '+self._table+' u on u.perm_id=p.id where u.id in ('+ids_str+')') res = cr.dictfetchall() # for record in res: # for f in ('ox','ux','gx','uid','gid'): # if record[f]==None: # record[f]=False for r in res: for key in r: r[key] = r[key] or False if key in ('write_uid','create_uid','uid'): if r[key]: r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0] return res def unlink(self, cr, uid, ids): self.pool.get('ir.model.access').check(cr, uid, self._name, 'create') if not len(ids): return True wf_service = netsvc.LocalService("workflow") for id in ids: wf_service.trg_delete(uid, self._name, id, cr) str_d = string.join(('%d',)*len(ids),',') cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids) res = cr.dictfetchall() #for key in self._inherits: # ids2 = [x[self._inherits[key]] for x in res] # self.pool.get(key).unlink(cr, uid, ids2) 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)) cr.execute('delete from '+self._table+' where id in ('+str_d+')', ids) return True # # TODO: Validate # def write(self, cr, user, ids, vals, context={}): self.pool.get('ir.model.access').check(cr, user, self._name, 'write') #for v in self._inherits.values(): # assert v not in vals, (v, vals) if not ids: return ids_str = string.join(map(lambda x:str(x), ids),',') upd0=[] upd1=[] upd_todo=[] updend=[] direct = [] totranslate = context.get('lang',False) and (context['lang']!='en') for field in vals: if (field in self._columns) and self._columns[field]._classic_write: if (not totranslate) or not self._columns[field].translate: upd0.append(field+'='+self._columns[field]._symbol_set[0]) upd1.append(self._columns[field]._symbol_set[1](vals[field])) direct.append(field) elif field in self._columns: upd_todo.append(field) else: updend.append(field) if self._log_access: upd0.append('write_uid=%d') upd0.append('write_date=current_timestamp') upd1.append(user) if len(upd0): cr.execute('update '+self._table+' set '+string.join(upd0,',')+' where id in ('+ids_str+')', upd1) if totranslate: for f in direct: if self._columns[f].translate: self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model', context['lang'], ids,vals[f]) upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority) for field in upd_todo: for id in ids: self._columns[field].set(cr, self, id, field, vals[field], user) for table in self._inherits: col = self._inherits[table] cr.execute('select distinct '+col+' from '+self._table+' where id in ('+ids_str+')', upd1) nids = [x[0] for x in cr.fetchall()] v = {} for val in updend: if self._inherit_fields[val][0]==table: v[val]=vals[val] self.pool.get(table).write(cr, user, nids, v, context) self._validate(cr, user, ids) wf_service = netsvc.LocalService("workflow") for id in ids: wf_service.trg_write(user, self._name, id, cr) return True # # TODO: Should set perm to user.xxx # # TODO: faudrait utiliser le context pour créer les champs ds ir_translation # def create(self, cr, user, vals, context={}): self.pool.get('ir.model.access').check(cr, user, self._name, 'create') """ create(cr, user, vals) -> int cr = dbcursor user = user id vals = dictionary of the form {'field_name':field_value, ...} """ default = [] for f in self._columns.keys(): if not f in vals: default.append(f) if len(default): vals.update(self.default_get(cr, user, default, context)) tocreate={} for v in self._inherits: if self._inherits[v] not in vals: tocreate[v] = {} cr.execute('select perm_id from res_users where id=%d', (user,)) perm = cr.fetchone()[0] (upd0, upd1, upd2) = ('', '', []) upd_todo=[] for v in vals.keys(): if v in self._inherit_fields: (table,col,col_detail) = self._inherit_fields[v] tocreate[table][v] = vals[v] del vals[v] cr.execute("select nextval('"+self._sequence+"')") id_new = cr.fetchone()[0] for table in tocreate: id = self.pool.get(table).create(cr, user, tocreate[table]) upd0 += ','+self._inherits[table] upd1 += ',%d' upd2.append(id) cr.execute('insert into inherit (obj_type,obj_id,inst_type,inst_id) values (%s,%d,%s,%d)', (table,id,self._name,id_new)) for field in vals: if self._columns[field]._classic_write: upd0=upd0+','+field upd1=upd1+','+self._columns[field]._symbol_set[0] upd2.append(self._columns[field]._symbol_set[1](vals[field])) else: upd_todo.append(field) if self._log_access: upd0+=',create_uid,create_date' upd1+=',%d,current_timestamp' upd2.append(user) cr.execute('insert into '+self._table+' (id,perm_id'+upd0+") values ("+str(id_new)+',%d'+upd1+')', tuple([perm]+upd2)) upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority) for field in upd_todo: self._columns[field].set(cr, self, id_new, field, vals[field], user) self._validate(cr, user, [id_new]) wf_service = netsvc.LocalService("workflow") wf_service.trg_create(user, self._name, id_new, cr) return id_new # # TODO: Validate # def perm_write(self, cr, user, ids, fields, context={}): query = [] vals = [] keys = fields.keys() for x in keys: query.append(x+'=%d') vals.append(fields[x]) cr.execute('select id from perm where '+string.join(query,' and ')+' limit 1', vals) res = cr.fetchone() if res: id = res[0] else: cr.execute("select nextval('perm_id_seq')") id = cr.fetchone()[0] cr.execute('insert into perm (id,'+string.join(keys,',')+') values ('+str(id)+','+('%d,'*(len(keys)-1)+'%d')+')', vals) ids_str = string.join(map(lambda x:str(x), ids),',') cr.execute('update '+self._table+' set perm_id=%d where id in ('+ids_str+')', (id,)) return True def fields_get(self, cr, user, fields = None, context={}): res = {} for parent in self._inherits: res.update(self.pool.get(parent).fields_get(cr, user, fields, context)) for f in self._columns.keys(): res[f]={'type': self._columns[f]._type} for arg in ('string','readonly','states','size','required','change_default','translate'): if getattr(self._columns[f], arg): res[f][arg] = getattr(self._columns[f], arg) if context.get('lang',False): res_trans = self.pool.get('ir.translation')._get_source(cr, user, self._name+','+f, 'field', context['lang']) if res_trans: res[f]['string'] = res_trans for arg in ('digits', 'invisible'): if hasattr(self._columns[f], arg) and getattr(self._columns[f],arg): res[f][arg] = getattr(self._columns[f],arg) if hasattr(self._columns[f], 'selection'): if (type(self._columns[f].selection)==type([])) or (type(self._columns[f].selection)==type( () )): sel = self._columns[f].selection if context.get('lang',False): sel2 = [] for (key,val) in sel: val2 = self.pool.get('ir.translation')._get_source(cr, user, self._name+','+f, 'selection', context['lang'], val) sel2.append((key,val2 or val)) sel = sel2 res[f]['selection']=sel else: res[f]['selection']=self._columns[f].selection(self,cr,user) if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'): res[f]['relation']=self._columns[f]._obj res[f]['domain']=self._columns[f]._domain res[f]['context']=self._columns[f]._context if fields: for r in res.keys(): if r not in fields: del res[r] return res # # Overload this method if you need a title of a window that depends of a context. # def view_header_get(self, cr, user, view_id=None, view_type='form', context={}): return False # Should read inheritancy def fields_view_get(self, cr, user, view_id=None, view_type='form', context={}): def _inherit_apply(src, inherit): def _find(node,node2): if node.nodeType==node.ELEMENT_NODE and node.localName==node2.localName: for attr in node2.attributes.keys(): if attr=='position': continue if node.hasAttribute(attr): if node.getAttribute(attr)==node2.getAttribute(attr): continue return False return node for child in node.childNodes: res = _find(child, node2) if res: return res return None doc_src = dom.minidom.parseString(src) doc_dest = dom.minidom.parseString(inherit) for node2 in doc_dest.childNodes: if not node2.nodeType==node2.ELEMENT_NODE: continue node = _find(doc_src, node2) if node: pos = 'inside' if node2.hasAttribute('position'): pos = node2.getAttribute('position') for child in node2.childNodes: if child.nodeType==child.ELEMENT_NODE: if pos=='inside': node.appendChild(child) elif pos=='after': sib = node.nextSibling if sib: node.parentNode.insertBefore(child, sib) else: node.parentNode.appendChild(child) elif pos=='before': node.parentNode.insertBefore(child, node) elif pos=='replace': node.parentNode.replaceChild(child, node) else: raise 'Unknown position in inherited view %s !'% pos else: raise 'PROCESSING', node2.localName, 'but not found !' return doc_src.toxml() def look_dom(node): result = False fields = {} if node.nodeType==node.ELEMENT_NODE and node.localName=='field': if node.hasAttribute('name'): fields[node.getAttribute('name')] = '' elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form','tree'): result = self.view_header_get(cr, user, view_id, view_type, context) if result: node.setAttribute('string', result) if node.nodeType==node.ELEMENT_NODE: if ('lang' in context) and node.hasAttribute('string') and node.getAttribute('string') and not result: trans = tools.translate(cr, user, self._name,'view',context['lang'], node.getAttribute('string').encode('utf8')) if trans: node.setAttribute('string', trans.decode('utf8')) for f in node.childNodes: fields.update( look_dom(f) ) return fields result = {'type':view_type, 'model':self._name} if view_id: cr.execute('select arch,name,field_parent,id from ir_ui_view where type=%s and model=%s and id=%d and inherit_id is null', (view_type, self._name,view_id)) else: cr.execute('select arch,name,field_parent,id from ir_ui_view where model=%s and type=%s and inherit_id is null order by priority', (self._name,view_type)) sql_res = cr.fetchone() if sql_res: result['arch'] = sql_res[0] cr.execute('select arch from ir_ui_view where inherit_id=%d order by priority', (sql_res[3],)) sql_inherit = cr.fetchall() for (inherit,) in sql_inherit: result['arch'] = _inherit_apply(result['arch'], inherit) result['name'] = sql_res[1] ff = find_fields() all_fields = ff.parse(result['arch']).keys() del ff result['field_parent']=sql_res[2] or False else: if view_type=='form': res = self.fields_get(cr, user, context=context) xml = "\n
" elif view_type=='tree': res = self.fields_get(cr, user, ['name'], context) if self._rec_name in res: xml = '''\n