diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 2f4d99be238..964ed2d7ad0 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -195,7 +195,7 @@ def load_module_graph(cr, graph, status=None, **kwargs): logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s' % m) sys.stdout.flush() pool = pooler.get_pool(cr.dbname) - modules = pool.instanciate(m) + modules = pool.instanciate(m, cr) cr.execute('select state, demo from ir_module_module where name=%s', (m,)) (package_state, package_demo) = (cr.rowcount and cr.fetchone()) or ('uninstalled', False) idref = {} diff --git a/bin/addons/base/base.sql b/bin/addons/base/base.sql index 958ea871031..76e6d29f425 100644 --- a/bin/addons/base/base.sql +++ b/bin/addons/base/base.sql @@ -59,6 +59,7 @@ CREATE TABLE ir_model_fields ( field_description varchar(256), ttype varchar(64), group_name varchar(64), + state varchar(64) default 'base', view_load boolean, relate boolean default False, primary key(id) diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index 9f0b24a2618..0bcebedb146 100644 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -709,12 +709,23 @@ - +
- - - - + + + + + + + + + + + + + + + @@ -749,6 +760,17 @@ + + + + + + + + + + + @@ -770,6 +792,7 @@ Objects ir.model form + {'manual':True} @@ -778,6 +801,7 @@ Fields ir.model.fields form + {'manual':True} diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index 92212927456..a8ce229d095 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -34,6 +34,10 @@ import time import tools import pooler +def _get_fields_type(self, cr, uid, context=None): + cr.execute('select distinct ttype,ttype from ir_model_fields') + return cr.fetchall() + class ir_model(osv.osv): _name = 'ir.model' _rec_name = 'model' @@ -51,18 +55,24 @@ ir_model() class ir_model_fields(osv.osv): _name = 'ir.model.fields' _columns = { - 'name': fields.char('Name', size=64), + 'name': fields.char('Name', required=True, size=64, select=1), 'model': fields.char('Model Name', size=64, required=True), -# on pourrait egalement changer ca en many2one, mais le prob c'est qu'alors faut -# faire une jointure a chaque fois qu'on recherche vu que le client ne connait que le nom -# de l'objet et pas son id 'relation': fields.char('Model Relation', size=64), 'model_id': fields.many2one('ir.model', 'Model id', required=True, select=True, ondelete='cascade'), -# in fact, this is the field label - 'field_description': fields.char('Field Description', size=256), - 'ttype': fields.char('Field Type', size=64), + 'field_description': fields.char('Field Label', required=True, size=256), 'relate': fields.boolean('Click and Relate'), + 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True), + 'selection': fields.char('Field Selection',size=128), + 'required': fields.boolean('Required'), + 'readonly': fields.boolean('Readonly'), + 'select': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search')],'Searchable', required=True), + 'translate': fields.boolean('Translate'), + 'size': fields.integer('Size'), + 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Manualy Created'), + 'on_delete': fields.selection([('no','Nothing'),('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'), + 'domain': fields.char('Domain', size=256), + 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'), 'group_name': fields.char('Group Name', size=128), 'view_load': fields.boolean('View Auto-Load'), @@ -70,10 +80,38 @@ class ir_model_fields(osv.osv): _defaults = { 'relate': lambda *a: 0, 'view_load': lambda *a: 0, - 'name': lambda *a: 'No Name', - 'field_description': lambda *a: 'No description available', + 'selection': lambda *a: "[]", + 'domain': lambda *a: "[]", + 'name': lambda *a: 'x_', + 'state': lambda self,cr,uid,ctx={}: ctx.get('manual',False) and 'manual' or 'base', + 'on_delete': lambda *a: 'no', + 'select': lambda *a: '0', + 'size': lambda *a: 64, + 'field_description': lambda *a: '', } _order = "id" + def unlink(self, cr, user, ids, context=None): + for field in self.browse(cr, uid, ids, context): + if field.state <> 'manual': + raise except_orm('Error', "You can not remove the field '%s' !" %(field.name,)) + # + # MAY BE ADD A ALTER TABLE DROP ? + # + return super(ir_model_fields, self).unlink(cr, user, ids, context) + + def create(self, cr, user, vals, context=None): + if 'model_id' in vals: + model_data=self.pool.get('ir.model').read(cr,user,vals['model_id']) + vals['model']=model_data['model'] + if context and context.get('manual',False): + vals['state']='manual' + res = super(ir_model_fields,self).create(cr, user, vals, context) + if vals.get('state','base')=='manual': + if not vals['name'].startswith('x_'): + raise except_orm('Error', "Custom fields must have a name that starts with 'x_' !") + self.pool.get(vals['model']).__init__(self.pool, cr) + self.pool.get(vals['model'])._auto_init(cr) + return res ir_model_fields() class ir_model_access(osv.osv): @@ -182,8 +220,8 @@ class ir_model_data(osv.osv): 'noupdate': lambda *a: False } - def __init__(self, pool): - osv.osv.__init__(self, pool) + def __init__(self, pool, cr): + osv.osv.__init__(self, pool, cr) self.loads = {} self.doinit = True self.unlink_mark = {} diff --git a/bin/osv/orm.py b/bin/osv/orm.py index cd226b6efbc..9e3500838dd 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -303,7 +303,7 @@ class orm(object): if not cr.rowcount: cr.execute("select id from ir_model where model='%s'" % self._name) model_id = cr.fetchone()[0] - 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')) + cr.execute("INSERT INTO ir_model_fields (model_id, model, name, field_description, ttype, relate,relation,group_name,view_load,state) VALUES (%d,%s,%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', 'base')) else: id, relate = cr.fetchone() if relate != f.relate: @@ -528,15 +528,37 @@ class orm(object): cr.execute(line2) cr.commit() - def __init__(self): + def __init__(self, cr): if not self._table: self._table=self._name.replace('.','_') if not self._description: self._description = self._name for (key,_,msg) in self._sql_constraints: self.pool._sql_error[self._table+'_'+key] = msg - -# if self.__class__.__name__ != 'fake_class': + + # Load manual fields + + cr.execute("select id from ir_model_fields where name=%s and model=%s", ('state','ir.model.fields')) + if cr.fetchone(): + cr.execute('select * from ir_model_fields where model=%s and state=%s', (self._name,'manual')) + for field in cr.dictfetchall(): + if field['name'] in self._columns: + continue + attrs = { + 'string': field['field_description'], + 'required': bool(field['required']), + 'readonly': bool(field['readonly']), + 'domain': field['domain'] or None, + 'size': field['size'], + 'ondelete': field['on_delete'], + 'translate': (field['translate']), + 'select': int(field['select']) + } + if field['relation']: + attrs['relation'] = field['relation'] + self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs) + + # if self.__class__.__name__ != 'fake_class': self._inherits_reload() if not self._sequence: self._sequence = self._table+'_id_seq' @@ -544,18 +566,7 @@ class orm(object): assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,) for f in self._columns: self._columns[f].restart() - # FIXME: does not work at all -# if self._log_access: -# self._columns.update({ -# 'create_uid': fields.many2one('res.users','Creation user',required=True, readonly=True), -# 'create_date': fields.datetime('Creation date',required=True, readonly=True), -# 'write_uid': fields.many2one('res.users','Last modification by', readonly=True), -# 'write_date': fields.datetime('Last modification date', readonly=True), -# }) -# self._defaults.update({ -# 'create_uid': lambda self,cr,uid,context : uid, -# 'create_date': lambda *a : time.strftime("%Y-%m-%d %H:%M:%S") -# }) + # # Update objects that uses this one to update their _inherits fields # diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 8a62b5d385e..848b3e5729e 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -168,7 +168,7 @@ class osv_pool(netsvc.Service): return obj #TODO: pass a list of modules to load - def instanciate(self, module): + def instanciate(self, module, cr): # print "module list:", module_list # for module in module_list: res = [] @@ -176,7 +176,7 @@ class osv_pool(netsvc.Service): # if module not in self.module_object_list: # print "%s class_list:" % module, class_list for klass in class_list: - res.append(klass.createInstance(self, module)) + res.append(klass.createInstance(self, module, cr)) return res # else: # print "skipping module", module @@ -225,7 +225,7 @@ class osv(orm.orm): # Goal: try to apply inheritancy at the instanciation level and # put objects in the pool var # - def createInstance(cls, pool, module): + def createInstance(cls, pool, module, cr): # obj = cls() parent_name = hasattr(cls, '_inherit') and cls._inherit if parent_name: @@ -245,18 +245,17 @@ class osv(orm.orm): name = hasattr(cls,'_name') and cls._name or cls._inherit #name = str(cls) cls = type(name, (cls, parent_class), nattr) - obj = object.__new__(cls) - obj.__init__(pool) + obj.__init__(pool, cr) return obj # return object.__new__(cls, pool) createInstance = classmethod(createInstance) - def __init__(self, pool): + def __init__(self, pool, cr): # print "__init__", self._name, pool pool.add(self._name, self) self.pool = pool - orm.orm.__init__(self) + orm.orm.__init__(self, cr) # pooler.get_pool(cr.dbname).add(self._name, self) # print self._name, module